Skip to content

Commit af2a835

Browse files
committed
Merge remote-tracking branch 'giteaofficial/main'
* giteaofficial/main: Fix typo (go-gitea#21695) Remove template previewer (go-gitea#21701) Revert "[skip ci] Updated translations via Crowdin" (go-gitea#21689) [skip ci] Updated translations via Crowdin Fix docs relref (go-gitea#21685) Add "Copy" button to file view of raw text (go-gitea#21629) Improve valid user name check (go-gitea#20136)
2 parents cc7c2c0 + a0367c2 commit af2a835

File tree

18 files changed

+120
-51
lines changed

18 files changed

+120
-51
lines changed

docs/content/doc/installation/from-source.fr-fr.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ git checkout pr-xyz
5353

5454
## Compilation
5555

56-
Comme nous regroupons déjà toutes les bibliothèques requises pour compiler Gitea, vous pouvez continuer avec le processus de compilation lui-même. Nous fournissons diverses [tâches Make](https://github.com/go-gitea/gitea/blob/master/Makefile) pour rendre le processus de construction aussi simple que possible. [Voyez ici comment obtenir Make]({{< relref "doc/developers/hacking-on-gitea.fr-fr.md" >}}#installing-make). Selon vos besoins, vous pourrez éventuellement ajouter diverses options de compilation, vous pouvez choisir entre ces options :
56+
Comme nous regroupons déjà toutes les bibliothèques requises pour compiler Gitea, vous pouvez continuer avec le processus de compilation lui-même. Nous fournissons diverses [tâches Make](https://github.com/go-gitea/gitea/blob/master/Makefile) pour rendre le processus de construction aussi simple que possible. [Voyez ici comment obtenir Make](/fr-fr/hacking-on-gitea/). Selon vos besoins, vous pourrez éventuellement ajouter diverses options de compilation, vous pouvez choisir entre ces options :
5757

5858
* `bindata`: Intègre toutes les ressources nécessaires à l'exécution d'une instance de Gitea, ce qui rend un déploiement facile car il n'est pas nécessaire de se préoccuper des fichiers supplémentaires.
5959
* `sqlite sqlite_unlock_notify`: Active la prise en charge d'une base de données [SQLite3](https://sqlite.org/), ceci n'est recommandé que pour les petites installations de Gitea.

docs/content/doc/installation/from-source.zh-cn.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ git checkout v{{< version >}}
5454

5555
- `go` {{< min-go-version >}} 或以上版本, 详见[这里](https://golang.google.cn/doc/install)
5656
- `node` {{< min-node-version >}} 或以上版本,并且安装 `npm`, 详见[这里](https://nodejs.org/zh-cn/download/)
57-
- `make`, 详见[这里]({{< relref "doc/developers/hacking-on-gitea.zh-cn.md" >}})</a>
57+
- `make`, 详见[这里](/zh-cn/hacking-on-gitea/)
5858

5959
各种可用的 [make 任务](https://github.com/go-gitea/gitea/blob/main/Makefile)
6060
可以用来使编译过程更方便。

models/user/user.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"code.gitea.io/gitea/modules/structs"
3030
"code.gitea.io/gitea/modules/timeutil"
3131
"code.gitea.io/gitea/modules/util"
32+
"code.gitea.io/gitea/modules/validation"
3233

3334
"golang.org/x/crypto/argon2"
3435
"golang.org/x/crypto/bcrypt"
@@ -621,7 +622,7 @@ var (
621622
// IsUsableUsername returns an error when a username is reserved
622623
func IsUsableUsername(name string) error {
623624
// Validate username make sure it satisfies requirement.
624-
if db.AlphaDashDotPattern.MatchString(name) {
625+
if !validation.IsValidUsername(name) {
625626
// Note: usually this error is normally caught up earlier in the UI
626627
return db.ErrNameCharsNotAllowed{Name: name}
627628
}

modules/structs/admin_user.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ type CreateUserOption struct {
1010
SourceID int64 `json:"source_id"`
1111
LoginName string `json:"login_name"`
1212
// required: true
13-
Username string `json:"username" binding:"Required;AlphaDashDot;MaxSize(40)"`
13+
Username string `json:"username" binding:"Required;Username;MaxSize(40)"`
1414
FullName string `json:"full_name" binding:"MaxSize(100)"`
1515
// required: true
1616
// swagger:strfmt email

modules/validation/binding.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ const (
2424

2525
// ErrRegexPattern is returned when a regex pattern is invalid
2626
ErrRegexPattern = "RegexPattern"
27+
28+
// ErrUsername is username error
29+
ErrUsername = "UsernameError"
2730
)
2831

2932
// AddBindingRules adds additional binding rules
@@ -34,6 +37,7 @@ func AddBindingRules() {
3437
addGlobPatternRule()
3538
addRegexPatternRule()
3639
addGlobOrRegexPatternRule()
40+
addUsernamePatternRule()
3741
}
3842

3943
func addGitRefNameBindingRule() {
@@ -148,6 +152,22 @@ func addGlobOrRegexPatternRule() {
148152
})
149153
}
150154

155+
func addUsernamePatternRule() {
156+
binding.AddRule(&binding.Rule{
157+
IsMatch: func(rule string) bool {
158+
return rule == "Username"
159+
},
160+
IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) {
161+
str := fmt.Sprintf("%v", val)
162+
if !IsValidUsername(str) {
163+
errs.Add([]string{name}, ErrUsername, "invalid username")
164+
return false, errs
165+
}
166+
return true, errs
167+
},
168+
})
169+
}
170+
151171
func portOnly(hostport string) string {
152172
colon := strings.IndexByte(hostport, ':')
153173
if colon == -1 {

modules/validation/helpers.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,15 @@ func IsValidExternalTrackerURLFormat(uri string) bool {
9191

9292
return true
9393
}
94+
95+
var (
96+
validUsernamePattern = regexp.MustCompile(`^[\da-zA-Z][-.\w]*$`)
97+
invalidUsernamePattern = regexp.MustCompile(`[-._]{2,}|[-._]$`) // No consecutive or trailing non-alphanumeric chars
98+
)
99+
100+
// IsValidUsername checks if username is valid
101+
func IsValidUsername(name string) bool {
102+
// It is difficult to find a single pattern that is both readable and effective,
103+
// but it's easier to use positive and negative checks.
104+
return validUsernamePattern.MatchString(name) && !invalidUsernamePattern.MatchString(name)
105+
}

modules/validation/helpers_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,34 @@ func Test_IsValidExternalTrackerURLFormat(t *testing.T) {
155155
})
156156
}
157157
}
158+
159+
func TestIsValidUsername(t *testing.T) {
160+
tests := []struct {
161+
arg string
162+
want bool
163+
}{
164+
{arg: "a", want: true},
165+
{arg: "abc", want: true},
166+
{arg: "0.b-c", want: true},
167+
{arg: "a.b-c_d", want: true},
168+
{arg: "", want: false},
169+
{arg: ".abc", want: false},
170+
{arg: "abc.", want: false},
171+
{arg: "a..bc", want: false},
172+
{arg: "a...bc", want: false},
173+
{arg: "a.-bc", want: false},
174+
{arg: "a._bc", want: false},
175+
{arg: "a_-bc", want: false},
176+
{arg: "a/bc", want: false},
177+
{arg: "☁️", want: false},
178+
{arg: "-", want: false},
179+
{arg: "--diff", want: false},
180+
{arg: "-im-here", want: false},
181+
{arg: "a space", want: false},
182+
}
183+
for _, tt := range tests {
184+
t.Run(tt.arg, func(t *testing.T) {
185+
assert.Equalf(t, tt.want, IsValidUsername(tt.arg), "IsValidUsername(%v)", tt.arg)
186+
})
187+
}
188+
}

modules/web/middleware/binding.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ func Validate(errs binding.Errors, data map[string]interface{}, f Form, l transl
135135
data["ErrorMsg"] = trName + l.Tr("form.glob_pattern_error", errs[0].Message)
136136
case validation.ErrRegexPattern:
137137
data["ErrorMsg"] = trName + l.Tr("form.regex_pattern_error", errs[0].Message)
138+
case validation.ErrUsername:
139+
data["ErrorMsg"] = trName + l.Tr("form.username_error")
138140
default:
139141
msg := errs[0].Classification
140142
if msg != "" && errs[0].Message != "" {

options/locale/locale_en-US.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ edit = Edit
8888

8989
copy = Copy
9090
copy_url = Copy URL
91+
copy_content = Copy content
9192
copy_branch = Copy branch name
9293
copy_success = Copied!
9394
copy_error = Copy failed
@@ -463,6 +464,7 @@ url_error = `'%s' is not a valid URL.`
463464
include_error = ` must contain substring '%s'.`
464465
glob_pattern_error = ` glob pattern is invalid: %s.`
465466
regex_pattern_error = ` regex pattern is invalid: %s.`
467+
username_error = ` can only contain alphanumeric chars ('0-9','a-z','A-Z'), dash ('-'), underscore ('_') and dot ('.'). It cannot begin or end with non-alphanumeric chars, and consecutive non-alphanumeric chars are also forbidden.`
466468
unknown_error = Unknown error:
467469
captcha_incorrect = The CAPTCHA code is incorrect.
468470
password_not_match = The passwords do not match.
@@ -1089,6 +1091,7 @@ editor.cannot_edit_non_text_files = Binary files cannot be edited in the web int
10891091
editor.edit_this_file = Edit File
10901092
editor.this_file_locked = File is locked
10911093
editor.must_be_on_a_branch = You must be on a branch to make or propose changes to this file.
1094+
editor.only_copy_raw = You may only copy raw text files.
10921095
editor.fork_before_edit = You must fork this repository to make or propose changes to this file.
10931096
editor.delete_this_file = Delete File
10941097
editor.must_have_write_access = You must have write access to make or propose changes to this file.

routers/web/dev/template.go

Lines changed: 0 additions & 29 deletions
This file was deleted.

routers/web/web.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import (
2828
"code.gitea.io/gitea/modules/web/routing"
2929
"code.gitea.io/gitea/routers/web/admin"
3030
"code.gitea.io/gitea/routers/web/auth"
31-
"code.gitea.io/gitea/routers/web/dev"
3231
"code.gitea.io/gitea/routers/web/events"
3332
"code.gitea.io/gitea/routers/web/explore"
3433
"code.gitea.io/gitea/routers/web/feed"
@@ -619,10 +618,6 @@ func RegisterRoutes(m *web.Route) {
619618

620619
m.Post("/{username}", reqSignIn, context_service.UserAssignmentWeb(), user.Action)
621620

622-
if !setting.IsProd {
623-
m.Get("/template/*", dev.TemplatePreview)
624-
}
625-
626621
reqRepoAdmin := context.RequireRepoAdmin()
627622
reqRepoCodeWriter := context.RequireRepoWriter(unit.TypeCode)
628623
canEnableEditor := context.CanEnableEditor()

services/forms/admin.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
type AdminCreateUserForm struct {
1919
LoginType string `binding:"Required"`
2020
LoginName string
21-
UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"`
21+
UserName string `binding:"Required;Username;MaxSize(40)"`
2222
Email string `binding:"Required;Email;MaxSize(254)"`
2323
Password string `binding:"MaxSize(255)"`
2424
SendNotify bool
@@ -35,7 +35,7 @@ func (f *AdminCreateUserForm) Validate(req *http.Request, errs binding.Errors) b
3535
// AdminEditUserForm form for admin to create user
3636
type AdminEditUserForm struct {
3737
LoginType string `binding:"Required"`
38-
UserName string `binding:"AlphaDashDot;MaxSize(40)"`
38+
UserName string `binding:"Username;MaxSize(40)"`
3939
LoginName string
4040
FullName string `binding:"MaxSize(100)"`
4141
Email string `binding:"Required;Email;MaxSize(254)"`

services/forms/org.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424

2525
// CreateOrgForm form for creating organization
2626
type CreateOrgForm struct {
27-
OrgName string `binding:"Required;AlphaDashDot;MaxSize(40)" locale:"org.org_name_holder"`
27+
OrgName string `binding:"Required;Username;MaxSize(40)" locale:"org.org_name_holder"`
2828
Visibility structs.VisibleType
2929
RepoAdminChangeTeamAccess bool
3030
}
@@ -37,7 +37,7 @@ func (f *CreateOrgForm) Validate(req *http.Request, errs binding.Errors) binding
3737

3838
// UpdateOrgSettingForm form for updating organization settings
3939
type UpdateOrgSettingForm struct {
40-
Name string `binding:"Required;AlphaDashDot;MaxSize(40)" locale:"org.org_name_holder"`
40+
Name string `binding:"Required;Username;MaxSize(40)" locale:"org.org_name_holder"`
4141
FullName string `binding:"MaxSize(100)"`
4242
Description string `binding:"MaxSize(255)"`
4343
Website string `binding:"ValidUrl;MaxSize(255)"`

services/forms/user_form.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ type InstallForm struct {
6565

6666
PasswordAlgorithm string
6767

68-
AdminName string `binding:"OmitEmpty;AlphaDashDot;MaxSize(30)" locale:"install.admin_name"`
68+
AdminName string `binding:"OmitEmpty;Username;MaxSize(30)" locale:"install.admin_name"`
6969
AdminPasswd string `binding:"OmitEmpty;MaxSize(255)" locale:"install.admin_password"`
7070
AdminConfirmPasswd string
7171
AdminEmail string `binding:"OmitEmpty;MinSize(3);MaxSize(254);Include(@)" locale:"install.admin_email"`
@@ -91,7 +91,7 @@ func (f *InstallForm) Validate(req *http.Request, errs binding.Errors) binding.E
9191

9292
// RegisterForm form for registering
9393
type RegisterForm struct {
94-
UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"`
94+
UserName string `binding:"Required;Username;MaxSize(40)"`
9595
Email string `binding:"Required;MaxSize(254)"`
9696
Password string `binding:"MaxSize(255)"`
9797
Retype string
@@ -243,7 +243,7 @@ func (f *IntrospectTokenForm) Validate(req *http.Request, errs binding.Errors) b
243243

244244
// UpdateProfileForm form for updating profile
245245
type UpdateProfileForm struct {
246-
Name string `binding:"AlphaDashDot;MaxSize(40)"`
246+
Name string `binding:"Username;MaxSize(40)"`
247247
FullName string `binding:"MaxSize(100)"`
248248
KeepEmailPrivate bool
249249
Website string `binding:"ValidSiteUrl;MaxSize(255)"`

services/forms/user_form_auth_openid.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func (f *SignInOpenIDForm) Validate(req *http.Request, errs binding.Errors) bind
2727

2828
// SignUpOpenIDForm form for signin up with OpenID
2929
type SignUpOpenIDForm struct {
30-
UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"`
30+
UserName string `binding:"Required;Username;MaxSize(40)"`
3131
Email string `binding:"Required;Email;MaxSize(254)"`
3232
GRecaptchaResponse string `form:"g-recaptcha-response"`
3333
HcaptchaResponse string `form:"h-captcha-response"`

templates/repo/view_file.tmpl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@
6060
{{end}}
6161
</div>
6262
<a download href="{{$.RawFileLink}}"><span class="btn-octicon tooltip" data-content="{{.locale.Tr "repo.download_file"}}" data-position="bottom center">{{svg "octicon-download"}}</span></a>
63+
{{if or .IsMarkup .IsRenderedHTML (not .IsTextSource)}}
64+
<span class="btn-octicon tooltip disabled" id="copy-file-content" data-content="{{.locale.Tr "repo.editor.only_copy_raw"}}" aria-label="{{.locale.Tr "repo.editor.only_copy_raw"}}">{{svg "octicon-copy" 14}}</span>
65+
{{else}}
66+
<a class="btn-octicon tooltip" id="copy-file-content" data-content="{{.locale.Tr "copy_content"}}" aria-label="{{.locale.Tr "copy_content"}}">{{svg "octicon-copy" 14}}</a>
67+
{{end}}
6368
{{if .Repository.CanEnableEditor}}
6469
{{if .CanEditFile}}
6570
<a href="{{.RepoLink}}/_edit/{{PathEscapeSegments .BranchName}}/{{PathEscapeSegments .TreePath}}"><span class="btn-octicon tooltip" data-content="{{.EditFileTooltip}}" data-position="bottom center">{{svg "octicon-pencil"}}</span></a>

tests/integration/user_test.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,22 @@ func TestRenameInvalidUsername(t *testing.T) {
5353
"%00",
5454
"thisHas ASpace",
5555
"p<A>tho>lo<gical",
56+
".",
57+
"..",
58+
".well-known",
59+
".abc",
60+
"abc.",
61+
"a..bc",
62+
"a...bc",
63+
"a.-bc",
64+
"a._bc",
65+
"a_-bc",
66+
"a/bc",
67+
"☁️",
68+
"-",
69+
"--diff",
70+
"-im-here",
71+
"a space",
5672
}
5773

5874
session := loginUser(t, "user2")
@@ -68,7 +84,7 @@ func TestRenameInvalidUsername(t *testing.T) {
6884
htmlDoc := NewHTMLParser(t, resp.Body)
6985
assert.Contains(t,
7086
htmlDoc.doc.Find(".ui.negative.message").Text(),
71-
translation.NewLocale("en-US").Tr("form.alpha_dash_dot_error"),
87+
translation.NewLocale("en-US").Tr("form.username_error"),
7288
)
7389

7490
unittest.AssertNotExistsBean(t, &user_model.User{Name: invalidUsername})
@@ -79,9 +95,7 @@ func TestRenameReservedUsername(t *testing.T) {
7995
defer tests.PrepareTestEnv(t)()
8096

8197
reservedUsernames := []string{
82-
".",
83-
"..",
84-
".well-known",
98+
// ".", "..", ".well-known", // The names are not only reserved but also invalid
8599
"admin",
86100
"api",
87101
"assets",

web_src/js/features/repo-code.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import $ from 'jquery';
22
import {svg} from '../svg.js';
33
import {invertFileFolding} from './file-fold.js';
4-
import {createTippy} from '../modules/tippy.js';
4+
import {createTippy, showTemporaryTooltip} from '../modules/tippy.js';
55
import {copyToClipboard} from './clipboard.js';
66

7+
const {i18n} = window.config;
8+
79
function changeHash(hash) {
810
if (window.history.pushState) {
911
window.history.pushState(null, null, hash);
@@ -110,6 +112,18 @@ function showLineButton() {
110112
});
111113
}
112114

115+
function initCopyFileContent() {
116+
// get raw text for copy content button, at the moment, only one button (and one related file content) is supported.
117+
const copyFileContent = document.querySelector('#copy-file-content');
118+
if (!copyFileContent) return;
119+
120+
copyFileContent.addEventListener('click', async () => {
121+
const text = Array.from(document.querySelectorAll('.file-view .lines-code')).map((el) => el.textContent).join('');
122+
const success = await copyToClipboard(text);
123+
showTemporaryTooltip(copyFileContent, success ? i18n.copy_success : i18n.copy_error);
124+
});
125+
}
126+
113127
export function initRepoCodeView() {
114128
if ($('.code-view .lines-num').length > 0) {
115129
$(document).on('click', '.lines-num span', function (e) {
@@ -185,4 +199,5 @@ export function initRepoCodeView() {
185199
if (!success) return;
186200
document.querySelector('.code-line-button')?._tippy?.hide();
187201
});
202+
initCopyFileContent();
188203
}

0 commit comments

Comments
 (0)