Compare commits

...

24 Commits

Author SHA1 Message Date
6543
713bc6c8dc Changelog for 1.16.9 (update) (#20341)
* Changelog for 1.16.9 (update)

* update security section
2022-07-12 19:26:27 +01:00
Lunny Xiao
6b7e860b0f Hide notify mail setting ui if not enabled (#20138) (#20337)
Backport #20138
2022-07-12 18:13:31 +01:00
Gusted
0f89417d75 Add write check for creating Commit status (#20332) (#20334)
- Backport #20332
  - Add write code checks for creating new commit status
  - Regression from #5314
  - Resolves #20331
2022-07-12 14:52:20 +02:00
zeripath
7c80a0b630 Ensure that drone tags 1.16.x and 1.16 on push to v1.16.x tag (#20304)
We need pushes to v1.16.9 to create tags to 1.16.9 and 1.16 but not 1 or latest.

We have previously adjusted the manifest to remove the latest tag, and have removed
auto_tags so that 1 does not get tagged but in doing so we also stopped 1.16 being
tagged. So here we just state the that we tag x.yy in addition to x.yyz*.

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-07-11 17:15:43 +08:00
zeripath
b42df3105d Only show Followers that current user can access (#20220) (#20253)
Backport #20220

Users who are following or being followed by a user should only be
displayed if the viewing user can see them.

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-07-06 09:47:16 +08:00
Gusted
6162fb0a19 Check for permission when fetching user controlled issues (#20133) (#20196)
* Check if project has the same repository id with issue when assign project to issue

* Check if issue's repository id match project's repository id

* Add more permission checking

* Remove invalid argument

* Fix errors

* Add generic check

* Remove duplicated check

* Return error + add check for new issues

* Apply suggestions from code review

Co-authored-by: Gusted <williamzijl7@hotmail.com>
Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
Co-authored-by: 6543 <6543@obermui.de>
2022-07-01 17:39:10 +02:00
6543
df0b330af7 CI: disable auto_tag (#20062) 2022-06-22 00:51:27 +02:00
6543
51db7b03dd Release page show all tags in compare dropdown (#20070) (#20071)
Backport #20070 

Just get all tags when creating the compare dropdown. (Also updates the changelog.)
Fix #19936
2022-06-21 19:09:24 +01:00
zeripath
a7b1e20b76 Changelog for 1.16.9 (#20059)
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: delvh <dev.lh@web.de>
Co-authored-by: Gusted <williamzijl7@hotmail.com>
2022-06-20 22:09:09 +02:00
6543
de79d2a235 CI: disable push to latest docker tag (#20025) 2022-06-18 21:02:25 +02:00
a1012112796
4b7f0c6c38 fix permission check for delete tag (#19985) (#20001)
fix #19970

by the way, fix some error response about protected tags.

Signed-off-by: a1012112796 <1012112796@qq.com>
2022-06-17 22:52:47 +01:00
Lunny Xiao
ae91913132 Only log non ErrNotExist errors in git.GetNote (#19884) (#19905)
* Fix GetNote

* Only log errors if the error is not ErrNotExist

Signed-off-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: Andrew Thornton <art27@cantab.net>
2022-06-07 21:39:08 +08:00
wxiaoguang
0e7791174d use exact search instead of fuzzy search for branch filter dropdown (#19893) 2022-06-05 09:10:30 +01:00
zeripath
736b7b25a4 Set Setpgid on child git processes (#19865) (#19881) 2022-06-03 23:39:15 -04:00
zeripath
daf14b275a Ensure responses are context.ResponseWriters (#19843) (#19859)
* Ensure responses are context.ResponseWriters (#19843)

Backport #19843

In order for web.Wrap to be able to detect if a response has been written
we need to wrap any non-context.ResponseWriters as a such. Otherwise
responses will be incorrectly detected as non-written to and handlers can
double run.

In the case of GZip this handler will change the response to a non-context.RW
and this failure to correctly detect response writing causes fallthrough and
a NPE.

Fix #19839

Signed-off-by: Andrew Thornton <art27@cantab.net>

* fix test

Signed-off-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2022-06-03 17:38:29 -04:00
singuliere
cf6694e815 git 2.36 is needed for safe.directory = '*' to work (#19876) 2022-06-03 13:33:18 -04:00
Lunny Xiao
704f809e90 Fix count bug (#19850)
* Fix count bug

* Fix bug

* Fix test
2022-06-01 23:18:04 +01:00
Lauris BH
0e9499ada7 Fix raw endpoint PDF file headers (#19825) (#19826) 2022-05-28 18:40:03 +03:00
Ondřej Čertík
675f658721 Make WIP prefixes case insensitive, e.g. allow Draft as a WIP prefix (#19780) (#19811)
Backport #19780

The issue was that only the actual title was converted to uppercase, but
not the prefix as specified in `WORK_IN_PROGRESS_PREFIXES`. As a result,
the following did not work:

    WORK_IN_PROGRESS_PREFIXES=Draft:,[Draft],WIP:,[WIP]

One possible workaround was:

    WORK_IN_PROGRESS_PREFIXES=DRAFT:,[DRAFT],WIP:,[WIP]

Then indeed one could use `Draft` (as well as `DRAFT`) in the title.
However, the link `Start the title with DRAFT: to prevent the pull request
from being merged accidentally.` showed the suggestion in uppercase; so
it is not possible to show it as `Draft`. This PR fixes it, and allows
to use `Draft` in `WORK_IN_PROGRESS_PREFIXES`.

Fixes #19779.

Co-authored-by: zeripath <art27@cantab.net>
2022-05-26 18:55:26 +03:00
zeripath
ccc11c1e77 Prevent NPE when cache service is disabled (#19703) (#19783)
Backport #19703

The cache service can be disabled - at which point ctx.Cache will be nil
and the use of it will cause an NPE.

The main part of this PR is that the cache is used for restricting
resending of activation mails and without this we cache we cannot
restrict this. Whilst this code could be re-considered to use the db and
probably should be, I think we can simply disable this code in the case
that the cache is disabled.

Signed-off-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: Lauris BH <lauris@nix.lv>
2022-05-25 19:49:59 +08:00
Lunny Xiao
336e1ac779 Fix NotificationUnreadCount (#19802) 2022-05-25 07:38:21 +03:00
zeripath
be99eb26a2 Detect truncated utf-8 characters at the end of content as still representing utf-8 (#19773) (#19774)
Backport #19773

Our character detection algorithm can potentially incorrectly detect utf-8 as iso-8859-x
if there is a truncated character at the end of the partially read file.

This PR changes the detection algorithm to truncated utf8 characters at the end of the
buffer.

Fix #19743

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-05-21 22:26:08 +08:00
silentcodeg
fe9458591a [doctor] pq: syntax error at or near "." quote user table name (#19765) (#19770)
Backport #19765
2022-05-21 02:00:52 +02:00
Lunny Xiao
57e816311b Fix bug (#19757) 2022-05-20 00:03:52 +02:00
53 changed files with 577 additions and 137 deletions

View File

@@ -902,8 +902,11 @@ steps:
image: techknowlogick/drone-docker:latest
pull: always
settings:
auto_tag: true
auto_tag: false
auto_tag_suffix: linux-amd64
tags:
- ${DRONE_TAG##v}-linux-amd64
- ${DRONE_TAG:1:4}-linux-amd64
repo: gitea/gitea
build_args:
- GOPROXY=https://goproxy.io
@@ -920,8 +923,11 @@ steps:
image: techknowlogick/drone-docker:latest
settings:
dockerfile: Dockerfile.rootless
auto_tag: true
auto_tag: false
auto_tag_suffix: linux-amd64-rootless
tags:
- ${DRONE_TAG##v}-linux-amd64-rootless
- ${DRONE_TAG:1:4}-linux-amd64-rootless
repo: gitea/gitea
build_args:
- GOPROXY=https://goproxy.io
@@ -1126,8 +1132,11 @@ steps:
image: techknowlogick/drone-docker:latest
pull: always
settings:
auto_tag: true
auto_tag: false
auto_tag_suffix: linux-arm64
tags:
- ${DRONE_TAG##v}-linux-arm64
- ${DRONE_TAG:1:4}-linux-arm64
repo: gitea/gitea
build_args:
- GOPROXY=https://goproxy.io
@@ -1144,8 +1153,11 @@ steps:
image: techknowlogick/drone-docker:latest
settings:
dockerfile: Dockerfile.rootless
auto_tag: true
auto_tag: false
auto_tag_suffix: linux-arm64-rootless
tags:
- ${DRONE_TAG##v}-linux-arm64-rootless
- ${DRONE_TAG:1:4}-linux-arm64-rootless
repo: gitea/gitea
build_args:
- GOPROXY=https://goproxy.io
@@ -1299,7 +1311,7 @@ steps:
image: plugins/manifest
pull: always
settings:
auto_tag: true
auto_tag: false
ignore_missing: true
spec: docker/manifest.rootless.tmpl
password:
@@ -1310,7 +1322,7 @@ steps:
- name: manifest
image: plugins/manifest
settings:
auto_tag: true
auto_tag: false
ignore_missing: true
spec: docker/manifest.tmpl
password:

View File

@@ -4,6 +4,31 @@ This changelog goes through all the changes that have been made in each release
without substantial changes to our git log; to see the highlights of what has
been added to each release, please refer to the [blog](https://blog.gitea.io).
## [1.16.9](https://github.com/go-gitea/gitea/releases/tag/v1.16.9) - 2022-07-12
* SECURITY
* Add write check for creating Commit status (#20332) (#20334)
* Check for permission when fetching user controlled issues (#20133) (#20196)
* BUGFIXES
* Hide notify mail setting ui if not enabled (#20138) (#20337)
* Add write check for creating Commit status (#20332) (#20334)
* Only show Followers that current user can access (#20220) (#20253)
* Release page show all tags in compare dropdown (#20070) (#20071)
* Fix permission check for delete tag (#19985) (#20001)
* Only log non ErrNotExist errors in git.GetNote (#19884) (#19905)
* Use exact search instead of fuzzy search for branch filter dropdown (#19885) (#19893)
* Set Setpgid on child git processes (#19865) (#19881)
* Import git from alpine 3.16 repository as 2.30.4 is needed for `safe.directory = '*'` to work but alpine 3.13 has 2.30.3 (#19876)
* Ensure responses are context.ResponseWriters (#19843) (#19859)
* Fix incorrect usage of `Count` function (#19850)
* Fix raw endpoint PDF file headers (#19825) (#19826)
* Make WIP prefixes case insensitive, e.g. allow `Draft` as a WIP prefix (#19780) (#19811)
* Don't return 500 on NotificationUnreadCount (#19802)
* Prevent NPE when cache service is disabled (#19703) (#19783)
* Detect truncated utf-8 characters at the end of content as still representing utf-8 (#19773) (#19774)
* Fix doctor pq: syntax error at or near "." quote user table name (#19765) (#19770)
* Fix bug with assigneees (#19757)
## [1.16.8](https://github.com/go-gitea/gitea/releases/tag/v1.16.8) - 2022-05-16
* ENHANCEMENTS

View File

@@ -33,7 +33,6 @@ RUN apk --no-cache add \
ca-certificates \
curl \
gettext \
git \
linux-pam \
openssh \
s6 \
@@ -41,6 +40,8 @@ RUN apk --no-cache add \
su-exec \
gnupg
RUN apk add git --repository=http://dl-cdn.alpinelinux.org/alpine/v3.16/main
RUN addgroup \
-S -g 1000 \
git && \

View File

@@ -32,10 +32,11 @@ RUN apk --no-cache add \
bash \
ca-certificates \
gettext \
git \
curl \
gnupg
RUN apk add git --repository=http://dl-cdn.alpinelinux.org/alpine/v3.16/main
RUN addgroup \
-S -g 1000 \
git && \

View File

@@ -24,6 +24,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/pprof"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/lfs"
@@ -247,7 +248,7 @@ func runServ(c *cli.Context) error {
os.Setenv(models.EnvKeyID, fmt.Sprintf("%d", results.KeyID))
os.Setenv(models.EnvAppURL, setting.AppURL)
//LFS token authentication
// LFS token authentication
if verb == lfsAuthenticateVerb {
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
@@ -306,6 +307,7 @@ func runServ(c *cli.Context) error {
}
}
process.SetSysProcAttribute(gitcmd)
gitcmd.Dir = setting.RepoRootPath
gitcmd.Stdout = os.Stdout
gitcmd.Stdin = os.Stdin

View File

@@ -880,7 +880,7 @@ PATH =
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; List of prefixes used in Pull Request title to mark them as Work In Progress
;; List of prefixes used in Pull Request title to mark them as Work In Progress (matched in a case-insensitive manner)
;WORK_IN_PROGRESS_PREFIXES = WIP:,[WIP]
;;
;; List of keywords used in Pull Request comments to automatically close a related issue

View File

@@ -4,7 +4,6 @@ tags:
{{#each build.tags}}
- {{this}}-rootless
{{/each}}
- "latest-rootless"
{{/if}}
manifests:
-

View File

@@ -4,7 +4,6 @@ tags:
{{#each build.tags}}
- {{this}}
{{/each}}
- "latest"
{{/if}}
manifests:
-

View File

@@ -87,7 +87,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
### Repository - Pull Request (`repository.pull-request`)
- `WORK_IN_PROGRESS_PREFIXES`: **WIP:,\[WIP\]**: List of prefixes used in Pull Request
title to mark them as Work In Progress
title to mark them as Work In Progress. These are matched in a case-insensitive manner.
- `CLOSE_KEYWORDS`: **close**, **closes**, **closed**, **fix**, **fixes**, **fixed**, **resolve**, **resolves**, **resolved**: List of
keywords used in Pull Request comments to automatically close a related issue
- `REOPEN_KEYWORDS`: **reopen**, **reopens**, **reopened**: List of keywords used in Pull Request comments to automatically reopen

View File

@@ -95,7 +95,8 @@ func CountOrphanedIssues() (int64, error) {
return db.GetEngine(db.DefaultContext).Table("issue").
Join("LEFT", "repository", "issue.repo_id=repository.id").
Where(builder.IsNull{"repository.id"}).
Count("id")
Select("COUNT(`issue`.`id`)").
Count()
}
// DeleteOrphanedIssues delete issues without a repo
@@ -140,8 +141,9 @@ func DeleteOrphanedIssues() error {
func CountOrphanedObjects(subject, refobject, joinCond string) (int64, error) {
return db.GetEngine(db.DefaultContext).Table("`"+subject+"`").
Join("LEFT", "`"+refobject+"`", joinCond).
Where(builder.IsNull{"`" + refobject + "`.id"}).
Count("id")
Where(builder.IsNull{"`" + refobject + "`.`id`"}).
Select("COUNT(`" + subject + "`.`id`)").
Count()
}
// DeleteOrphanedObjects delete subjects with have no existing refobject anymore
@@ -241,7 +243,6 @@ func FixIssueLabelWithOutsideLabels() (int64, error) {
WHERE
(label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id)
) AS il_too )`)
if err != nil {
return 0, err
}

View File

@@ -155,31 +155,26 @@ func toggleUserAssignee(e db.Engine, issue *Issue, assigneeID int64) (removed bo
}
// Check if the submitted user is already assigned, if yes delete him otherwise add him
var i int
for i = 0; i < len(issue.Assignees); i++ {
found := false
i := 0
for ; i < len(issue.Assignees); i++ {
if issue.Assignees[i].ID == assigneeID {
found = true
break
}
}
assigneeIn := IssueAssignees{AssigneeID: assigneeID, IssueID: issue.ID}
toBeDeleted := i < len(issue.Assignees)
if toBeDeleted {
issue.Assignees = append(issue.Assignees[:i], issue.Assignees[i:]...)
if found {
issue.Assignees = append(issue.Assignees[:i], issue.Assignees[i+1:]...)
_, err = e.Delete(assigneeIn)
if err != nil {
return toBeDeleted, err
}
} else {
issue.Assignees = append(issue.Assignees, assignee)
_, err = e.Insert(assigneeIn)
if err != nil {
return toBeDeleted, err
}
}
return toBeDeleted, nil
return found, err
}
// MakeIDsFromAPIAssigneesToAdd returns an array with all assignee IDs

View File

@@ -116,6 +116,11 @@ func getMilestoneByRepoID(e db.Engine, repoID, id int64) (*Milestone, error) {
return m, nil
}
// HasMilestoneByRepoID returns if the milestone exists in the repository.
func HasMilestoneByRepoID(repoID, id int64) (bool, error) {
return db.GetEngine(db.DefaultContext).ID(id).Where("repo_id=?", repoID).Exist(new(Milestone))
}
// GetMilestoneByRepoID returns the milestone in a repository.
func GetMilestoneByRepoID(repoID, id int64) (*Milestone, error) {
return getMilestoneByRepoID(db.GetEngine(db.DefaultContext), repoID, id)
@@ -251,6 +256,17 @@ func changeMilestoneStatus(ctx context.Context, m *Milestone, isClosed bool) err
}
func changeMilestoneAssign(ctx context.Context, doer *user_model.User, issue *Issue, oldMilestoneID int64) error {
// Only check if milestone exists if we don't remove it.
if issue.MilestoneID > 0 {
has, err := HasMilestoneByRepoID(issue.RepoID, issue.MilestoneID)
if err != nil {
return fmt.Errorf("HasMilestoneByRepoID: %v", err)
}
if !has {
return fmt.Errorf("HasMilestoneByRepoID: issue doesn't exist")
}
}
if err := updateIssueCols(ctx, issue, "milestone_id"); err != nil {
return err
}

View File

@@ -150,6 +150,17 @@ func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.U
e := db.GetEngine(ctx)
oldProjectID := issue.projectID(e)
// Only check if we add a new project and not remove it.
if newProjectID > 0 {
newProject, err := GetProjectByID(newProjectID)
if err != nil {
return err
}
if newProject.RepoID != issue.RepoID {
return fmt.Errorf("issue's repository is not the same as project's repository")
}
}
if _, err := e.Where("project_issue.issue_id=?", issue.ID).Delete(&ProjectIssue{}); err != nil {
return err
}

View File

@@ -622,7 +622,7 @@ func (pr *PullRequest) IsWorkInProgress() bool {
// HasWorkInProgressPrefix determines if the given PR title has a Work In Progress prefix
func HasWorkInProgressPrefix(title string) bool {
for _, prefix := range setting.Repository.PullRequest.WorkInProgressPrefixes {
if strings.HasPrefix(strings.ToUpper(title), prefix) {
if strings.HasPrefix(strings.ToUpper(title), strings.ToUpper(prefix)) {
return true
}
}
@@ -643,7 +643,7 @@ func (pr *PullRequest) GetWorkInProgressPrefix() string {
}
for _, prefix := range setting.Repository.PullRequest.WorkInProgressPrefixes {
if strings.HasPrefix(strings.ToUpper(pr.Issue.Title), prefix) {
if strings.HasPrefix(strings.ToUpper(pr.Issue.Title), strings.ToUpper(prefix)) {
return pr.Issue.Title[0:len(prefix)]
}
}

View File

@@ -316,37 +316,45 @@ func (u *User) GenerateEmailActivateCode(email string) string {
}
// GetUserFollowers returns range of user's followers.
func GetUserFollowers(u *User, listOptions db.ListOptions) ([]*User, error) {
sess := db.GetEngine(db.DefaultContext).
func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) {
sess := db.GetEngine(ctx).
Select("`user`.*").
Join("LEFT", "follow", "`user`.id=follow.user_id").
Where("follow.follow_id=?", u.ID).
Join("LEFT", "follow", "`user`.id=follow.user_id")
And(isUserVisibleToViewerCond(viewer))
if listOptions.Page != 0 {
sess = db.SetSessionPagination(sess, &listOptions)
users := make([]*User, 0, listOptions.PageSize)
return users, sess.Find(&users)
count, err := sess.FindAndCount(&users)
return users, count, err
}
users := make([]*User, 0, 8)
return users, sess.Find(&users)
count, err := sess.FindAndCount(&users)
return users, count, err
}
// GetUserFollowing returns range of user's following.
func GetUserFollowing(u *User, listOptions db.ListOptions) ([]*User, error) {
func GetUserFollowing(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) {
sess := db.GetEngine(db.DefaultContext).
Select("`user`.*").
Join("LEFT", "follow", "`user`.id=follow.follow_id").
Where("follow.user_id=?", u.ID).
Join("LEFT", "follow", "`user`.id=follow.follow_id")
And(isUserVisibleToViewerCond(viewer))
if listOptions.Page != 0 {
sess = db.SetSessionPagination(sess, &listOptions)
users := make([]*User, 0, listOptions.PageSize)
return users, sess.Find(&users)
count, err := sess.FindAndCount(&users)
return users, count, err
}
users := make([]*User, 0, 8)
return users, sess.Find(&users)
count, err := sess.FindAndCount(&users)
return users, count, err
}
// NewGitSig generates and returns the signature of given user.
@@ -1231,3 +1239,36 @@ func GetAdminUser() (*User, error) {
return &admin, nil
}
func isUserVisibleToViewerCond(viewer *User) builder.Cond {
if viewer != nil && viewer.IsAdmin {
return builder.NewCond()
}
if viewer == nil || viewer.IsRestricted {
return builder.Eq{
"`user`.visibility": structs.VisibleTypePublic,
}
}
return builder.Neq{
"`user`.visibility": structs.VisibleTypePrivate,
}.Or(
builder.In("`user`.id",
builder.
Select("`follow`.user_id").
From("follow").
Where(builder.Eq{"`follow`.follow_id": viewer.ID})),
builder.In("`user`.id",
builder.
Select("`team_user`.uid").
From("team_user").
Join("INNER", "`team_user` AS t2", "`team_user`.id = `t2`.id").
Where(builder.Eq{"`t2`.uid": viewer.ID})),
builder.In("`user`.id",
builder.
Select("`team_user`.uid").
From("team_user").
Join("INNER", "`team_user` AS t2", "`team_user`.org_id = `t2`.org_id").
Where(builder.Eq{"`t2`.uid": viewer.ID})))
}

View File

@@ -131,7 +131,26 @@ func RemoveBOMIfPresent(content []byte) []byte {
// DetectEncoding detect the encoding of content
func DetectEncoding(content []byte) (string, error) {
if utf8.Valid(content) {
// First we check if the content represents valid utf8 content excepting a truncated character at the end.
// Now we could decode all the runes in turn but this is not necessarily the cheapest thing to do
// instead we walk backwards from the end to trim off a the incomplete character
toValidate := content
end := len(toValidate) - 1
if end < 0 {
// no-op
} else if toValidate[end]>>5 == 0b110 {
// Incomplete 1 byte extension e.g. © <c2><a9> which has been truncated to <c2>
toValidate = toValidate[:end]
} else if end > 0 && toValidate[end]>>6 == 0b10 && toValidate[end-1]>>4 == 0b1110 {
// Incomplete 2 byte extension e.g. ⛔ <e2><9b><94> which has been truncated to <e2><9b>
toValidate = toValidate[:end-1]
} else if end > 1 && toValidate[end]>>6 == 0b10 && toValidate[end-1]>>6 == 0b10 && toValidate[end-2]>>3 == 0b11110 {
// Incomplete 3 byte extension e.g. 💩 <f0><9f><92><a9> which has been truncated to <f0><9f><92>
toValidate = toValidate[:end-2]
}
if utf8.Valid(toValidate) {
log.Debug("Detected encoding: utf-8 (fast)")
return "UTF-8", nil
}

View File

@@ -5,6 +5,8 @@
package charset
import (
"bytes"
"io"
"strings"
"testing"
@@ -272,3 +274,145 @@ func stringMustEndWith(t *testing.T, expected, value string) {
func bytesMustStartWith(t *testing.T, expected, value []byte) {
assert.Equal(t, expected, value[:len(expected)])
}
func TestToUTF8WithFallbackReader(t *testing.T) {
resetDefaultCharsetsOrder()
for testLen := 0; testLen < 2048; testLen++ {
pattern := " test { () }\n"
input := ""
for len(input) < testLen {
input += pattern
}
input = input[:testLen]
input += "// Выключаем"
rd := ToUTF8WithFallbackReader(bytes.NewReader([]byte(input)))
r, _ := io.ReadAll(rd)
assert.EqualValuesf(t, input, string(r), "testing string len=%d", testLen)
}
truncatedOneByteExtension := failFastBytes
encoding, _ := DetectEncoding(truncatedOneByteExtension)
assert.Equal(t, "UTF-8", encoding)
truncatedTwoByteExtension := failFastBytes
truncatedTwoByteExtension[len(failFastBytes)-1] = 0x9b
truncatedTwoByteExtension[len(failFastBytes)-2] = 0xe2
encoding, _ = DetectEncoding(truncatedTwoByteExtension)
assert.Equal(t, "UTF-8", encoding)
truncatedThreeByteExtension := failFastBytes
truncatedThreeByteExtension[len(failFastBytes)-1] = 0x92
truncatedThreeByteExtension[len(failFastBytes)-2] = 0x9f
truncatedThreeByteExtension[len(failFastBytes)-3] = 0xf0
encoding, _ = DetectEncoding(truncatedThreeByteExtension)
assert.Equal(t, "UTF-8", encoding)
}
var failFastBytes = []byte{
0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x6f, 0x72, 0x67, 0x2e, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x74, 0x6f,
0x6f, 0x6c, 0x73, 0x2e, 0x61, 0x6e, 0x74, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x64, 0x65, 0x66, 0x73, 0x2e, 0x63, 0x6f, 0x6e,
0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4f, 0x73, 0x0a, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x6f, 0x72, 0x67,
0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x62, 0x6f, 0x6f,
0x74, 0x2e, 0x67, 0x72, 0x61, 0x64, 0x6c, 0x65, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2e, 0x72, 0x75, 0x6e, 0x2e, 0x42,
0x6f, 0x6f, 0x74, 0x52, 0x75, 0x6e, 0x0a, 0x0a, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x69, 0x64, 0x28, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d,
0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x22, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x65,
0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65,
0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 0x61, 0x70, 0x69, 0x22, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d,
0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74,
0x28, 0x22, 0x3a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 0x61, 0x70, 0x69, 0x2d, 0x64, 0x6f, 0x63, 0x73, 0x22, 0x29,
0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 0x64, 0x62,
0x22, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a,
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65,
0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x66,
0x73, 0x22, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x3a, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x6d, 0x71, 0x22, 0x29, 0x29, 0x0a, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22,
0x6a, 0x66, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x65, 0x3a, 0x70, 0x65, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
0x2d, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2d, 0x73, 0x74, 0x61, 0x72, 0x74,
0x65, 0x72, 0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6a, 0x66, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x65, 0x3a, 0x70, 0x65, 0x2d, 0x63,
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2d, 0x68, 0x61, 0x6c, 0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c,
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6a, 0x66, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x2e,
0x70, 0x65, 0x3a, 0x70, 0x65, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x22, 0x29, 0x0a,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28,
0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b,
0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x6f, 0x6f, 0x74, 0x2d, 0x73, 0x74,
0x61, 0x72, 0x74, 0x65, 0x72, 0x2d, 0x77, 0x65, 0x62, 0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c,
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69,
0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x3a, 0x73, 0x70, 0x72,
0x69, 0x6e, 0x67, 0x2d, 0x62, 0x6f, 0x6f, 0x74, 0x2d, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x72, 0x2d, 0x61, 0x6f, 0x70,
0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x28, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f,
0x72, 0x6b, 0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x6f, 0x6f, 0x74, 0x2d,
0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x72, 0x2d, 0x61, 0x63, 0x74, 0x75, 0x61, 0x74, 0x6f, 0x72, 0x22, 0x29, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f,
0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x63,
0x6c, 0x6f, 0x75, 0x64, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x73, 0x74,
0x61, 0x72, 0x74, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x22, 0x29, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f, 0x72,
0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x63, 0x6c,
0x6f, 0x75, 0x64, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x73, 0x74, 0x61,
0x72, 0x74, 0x65, 0x72, 0x2d, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2d, 0x61, 0x6c, 0x6c, 0x22, 0x29, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f, 0x72,
0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x63, 0x6c,
0x6f, 0x75, 0x64, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x73, 0x74, 0x61,
0x72, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x6c, 0x65, 0x75, 0x74, 0x68, 0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d,
0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70,
0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x72, 0x65, 0x74, 0x72, 0x79, 0x3a,
0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x72, 0x65, 0x74, 0x72, 0x79, 0x22, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x63, 0x68, 0x2e, 0x71,
0x6f, 0x73, 0x2e, 0x6c, 0x6f, 0x67, 0x62, 0x61, 0x63, 0x6b, 0x3a, 0x6c, 0x6f, 0x67, 0x62, 0x61, 0x63, 0x6b, 0x2d, 0x63,
0x6c, 0x61, 0x73, 0x73, 0x69, 0x63, 0x22, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d,
0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x69, 0x6f, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x6d, 0x65,
0x74, 0x65, 0x72, 0x3a, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x2d, 0x72, 0x65, 0x67, 0x69, 0x73,
0x74, 0x72, 0x79, 0x2d, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x22, 0x29, 0x0a, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6b, 0x6f, 0x74,
0x6c, 0x69, 0x6e, 0x28, 0x22, 0x73, 0x74, 0x64, 0x6c, 0x69, 0x62, 0x22, 0x29, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
0x2f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64,
0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74,
0x65, 0x73, 0x74, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6a,
0x66, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x65, 0x3a, 0x70, 0x65, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2d,
0x74, 0x65, 0x73, 0x74, 0x22, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x20, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4a,
0x61, 0x72, 0x20, 0x62, 0x79, 0x20, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
0x69, 0x6e, 0x67, 0x28, 0x4a, 0x61, 0x72, 0x3a, 0x3a, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2e,
0x73, 0x65, 0x74, 0x28, 0x22, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x22, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x76, 0x61, 0x6c, 0x20, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x70, 0x61, 0x74, 0x68,
0x20, 0x62, 0x79, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x67,
0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74,
0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65,
0x73, 0x28, 0x22, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x2d, 0x50, 0x61, 0x74, 0x68, 0x22, 0x20, 0x74, 0x6f, 0x20, 0x6f, 0x62,
0x6a, 0x65, 0x63, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70,
0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x76, 0x61, 0x6c, 0x20, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x3d,
0x20, 0x22, 0x66, 0x69, 0x6c, 0x65, 0x3a, 0x2f, 0x2b, 0x22, 0x2e, 0x74, 0x6f, 0x52, 0x65, 0x67, 0x65, 0x78, 0x28, 0x29,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64,
0x65, 0x20, 0x66, 0x75, 0x6e, 0x20, 0x74, 0x6f, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x28, 0x29, 0x3a, 0x20, 0x53, 0x74,
0x72, 0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x70,
0x61, 0x74, 0x68, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x6a, 0x6f, 0x69, 0x6e, 0x54, 0x6f, 0x53, 0x74, 0x72, 0x69,
0x6e, 0x67, 0x28, 0x22, 0x20, 0x22, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x74, 0x2e, 0x74, 0x6f, 0x55, 0x52, 0x49, 0x28, 0x29, 0x2e, 0x74, 0x6f, 0x55,
0x52, 0x4c, 0x28, 0x29, 0x2e, 0x74, 0x6f, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x28, 0x29, 0x2e, 0x72, 0x65, 0x70, 0x6c,
0x61, 0x63, 0x65, 0x46, 0x69, 0x72, 0x73, 0x74, 0x28, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x2c, 0x20, 0x22, 0x2f,
0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x61, 0x73,
0x6b, 0x73, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x3c, 0x42, 0x6f, 0x6f, 0x74, 0x52, 0x75, 0x6e, 0x3e, 0x28, 0x22, 0x62,
0x6f, 0x6f, 0x74, 0x52, 0x75, 0x6e, 0x22, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x4f,
0x73, 0x2e, 0x69, 0x73, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x28, 0x4f, 0x73, 0x2e, 0x46, 0x41, 0x4d, 0x49, 0x4c, 0x59,
0x5f, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x53, 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x70, 0x61, 0x74, 0x68, 0x20, 0x3d, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x28, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x28, 0x22, 0x6d, 0x61, 0x69,
0x6e, 0x22, 0x29, 0x2e, 0x6d, 0x61, 0x70, 0x20, 0x7b, 0x20, 0x69, 0x74, 0x2e, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x20,
0x7d, 0x2c, 0x20, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4a, 0x61, 0x72, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0xd0,
}

View File

@@ -188,10 +188,10 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
"action", "repository", "action.repo_id=repository.id"),
// find OAuth2Grant without existing user
genericOrphanCheck("Orphaned OAuth2Grant without existing User",
"oauth2_grant", "user", "oauth2_grant.user_id=user.id"),
"oauth2_grant", "user", "oauth2_grant.user_id=`user`.id"),
// find OAuth2Application without existing user
genericOrphanCheck("Orphaned OAuth2Application without existing User",
"oauth2_application", "user", "oauth2_application.uid=user.id"),
"oauth2_application", "user", "oauth2_application.uid=`user`.id"),
// find OAuth2AuthorizationCode without existing OAuth2Grant
genericOrphanCheck("Orphaned OAuth2AuthorizationCode without existing OAuth2Grant",
"oauth2_authorization_code", "oauth2_grant", "oauth2_authorization_code.grant_id=oauth2_grant.id"),

View File

@@ -130,6 +130,7 @@ func createBlameReader(ctx context.Context, dir string, command ...string) (*Bla
cmd := exec.CommandContext(ctx, command[0], command[1:]...)
cmd.Dir = dir
cmd.Stderr = os.Stderr
process.SetSysProcAttribute(cmd)
stdout, err := cmd.StdoutPipe()
if err != nil {

View File

@@ -188,6 +188,7 @@ func (c *Command) RunWithContext(rc *RunContext) error {
if goVersionLessThan115 {
cmd.Env = append(cmd.Env, "GODEBUG=asyncpreemptoff=1")
}
process.SetSysProcAttribute(cmd)
cmd.Dir = rc.Dir
cmd.Stdout = rc.Stdout
cmd.Stderr = rc.Stderr

View File

@@ -47,7 +47,10 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note)
commitID = commitID[2:]
}
if err != nil {
log.Error("Unable to find git note corresponding to the commit %q. Error: %v", originalCommitID, err)
// Err may have been updated by the SubTree we need to recheck if it's again an ErrNotExist
if !IsErrNotExist(err) {
log.Error("Unable to find git note corresponding to the commit %q. Error: %v", originalCommitID, err)
}
return err
}
}

View File

@@ -119,6 +119,8 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
cmd.Stdin = input
}
cmd.Stdout = output
process.SetSysProcAttribute(cmd)
if err := cmd.Run(); err != nil {
return fmt.Errorf("%s render run command %s %v failed: %v", p.Name(), commands[0], args, err)
}

View File

@@ -254,13 +254,13 @@ func (pm *Manager) ExecDirEnvStdIn(timeout time.Duration, dir, desc string, env
if stdIn != nil {
cmd.Stdin = stdIn
}
SetSysProcAttribute(cmd)
if err := cmd.Start(); err != nil {
return "", "", err
}
err := cmd.Wait()
if err != nil {
err = &Error{
PID: GetPID(ctx),

View File

@@ -0,0 +1,19 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
//go:build !windows
// +build !windows
package process
import (
"os/exec"
"syscall"
)
// SetSysProcAttribute sets the common SysProcAttrs for commands
func SetSysProcAttribute(cmd *exec.Cmd) {
// When Gitea runs SubProcessA -> SubProcessB and SubProcessA gets killed by context timeout, use setpgid to make sure the sub processes can be reaped instead of leaving defunct(zombie) processes.
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
}

View File

@@ -0,0 +1,17 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
//go:build windows
// +build windows
package process
import (
"os/exec"
)
// SetSysProcAttribute sets the common SysProcAttrs for commands
func SetSysProcAttribute(cmd *exec.Cmd) {
// Do nothing
}

View File

@@ -24,6 +24,7 @@ import (
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
@@ -100,6 +101,8 @@ func sessionHandler(session ssh.Session) {
}
defer stdin.Close()
process.SetSysProcAttribute(cmd)
wg := &sync.WaitGroup{}
wg.Add(2)
@@ -330,7 +333,7 @@ func GenKeyPair(keyPath string) error {
}
privateKeyPEM := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}
f, err := os.OpenFile(keyPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
f, err := os.OpenFile(keyPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
if err != nil {
return err
}
@@ -351,7 +354,7 @@ func GenKeyPair(keyPath string) error {
}
public := gossh.MarshalAuthorizedKey(pub)
p, err := os.OpenFile(keyPath+".pub", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
p, err := os.OpenFile(keyPath+".pub", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
if err != nil {
return err
}

View File

@@ -17,8 +17,12 @@ import (
// Use at most this many bytes to determine Content Type.
const sniffLen = 1024
// SvgMimeType MIME type of SVG images.
const SvgMimeType = "image/svg+xml"
const (
// SvgMimeType MIME type of SVG images.
SvgMimeType = "image/svg+xml"
// ApplicationOctetStream MIME type of binary files.
ApplicationOctetStream = "application/octet-stream"
)
var svgTagRegex = regexp.MustCompile(`(?si)\A\s*(?:(<!--.*?-->|<!DOCTYPE\s+svg([\s:]+.*?>|>))\s*)*<svg[\s>\/]`)
var svgTagInXMLRegex = regexp.MustCompile(`(?si)\A<\?xml\b.*?\?>\s*(?:(<!--.*?-->|<!DOCTYPE\s+svg([\s:]+.*?>|>))\s*)*<svg[\s>\/]`)

View File

@@ -42,11 +42,17 @@ func Wrap(handlers ...interface{}) http.HandlerFunc {
handler := handlers[i]
switch t := handler.(type) {
case http.HandlerFunc:
if _, ok := resp.(context.ResponseWriter); !ok {
resp = context.NewResponse(resp)
}
t(resp, req)
if r, ok := resp.(context.ResponseWriter); ok && r.Status() > 0 {
return
}
case func(http.ResponseWriter, *http.Request):
if _, ok := resp.(context.ResponseWriter); !ok {
resp = context.NewResponse(resp)
}
t(resp, req)
if r, ok := resp.(context.ResponseWriter); ok && r.Status() > 0 {
return
@@ -88,7 +94,7 @@ func Wrap(handlers ...interface{}) http.HandlerFunc {
return
}
case func(http.Handler) http.Handler:
var next = http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})
next := http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})
if len(handlers) > i+1 {
next = Wrap(handlers[i+1:]...)
}
@@ -148,7 +154,7 @@ func MiddleAPI(f func(ctx *context.APIContext)) func(netx http.Handler) http.Han
// Bind binding an obj to a handler
func Bind(obj interface{}) http.HandlerFunc {
var tp = reflect.TypeOf(obj)
tp := reflect.TypeOf(obj)
if tp.Kind() == reflect.Ptr {
tp = tp.Elem()
}
@@ -156,7 +162,7 @@ func Bind(obj interface{}) http.HandlerFunc {
panic("Only structs are allowed to bind")
}
return Wrap(func(ctx *context.Context) {
var theObj = reflect.New(tp).Interface() // create a new form obj for every request but not use obj directly
theObj := reflect.New(tp).Interface() // create a new form obj for every request but not use obj directly
binding.Bind(ctx.Req, theObj)
SetForm(ctx, theObj)
middleware.AssignForm(theObj, ctx.Data)
@@ -214,8 +220,8 @@ func (r *Route) Use(middlewares ...interface{}) {
// Group mounts a sub-Router along a `pattern` string.
func (r *Route) Group(pattern string, fn func(), middlewares ...interface{}) {
var previousGroupPrefix = r.curGroupPrefix
var previousMiddlewares = r.curMiddlewares
previousGroupPrefix := r.curGroupPrefix
previousMiddlewares := r.curMiddlewares
r.curGroupPrefix += pattern
r.curMiddlewares = append(r.curMiddlewares, middlewares...)
@@ -238,7 +244,7 @@ func (r *Route) getPattern(pattern string) string {
// Mount attaches another Route along ./pattern/*
func (r *Route) Mount(pattern string, subR *Route) {
var middlewares = make([]interface{}, len(r.curMiddlewares))
middlewares := make([]interface{}, len(r.curMiddlewares))
copy(middlewares, r.curMiddlewares)
subR.Use(middlewares...)
r.R.Mount(r.getPattern(pattern), subR.R)
@@ -246,7 +252,7 @@ func (r *Route) Mount(pattern string, subR *Route) {
// Any delegate requests for all methods
func (r *Route) Any(pattern string, h ...interface{}) {
var middlewares = r.getMiddlewares(h)
middlewares := r.getMiddlewares(h)
r.R.HandleFunc(r.getPattern(pattern), Wrap(middlewares...))
}
@@ -254,7 +260,7 @@ func (r *Route) Any(pattern string, h ...interface{}) {
func (r *Route) Route(pattern, methods string, h ...interface{}) {
p := r.getPattern(pattern)
ms := strings.Split(methods, ",")
var middlewares = r.getMiddlewares(h)
middlewares := r.getMiddlewares(h)
for _, method := range ms {
r.R.MethodFunc(strings.TrimSpace(method), p, Wrap(middlewares...))
}
@@ -262,12 +268,12 @@ func (r *Route) Route(pattern, methods string, h ...interface{}) {
// Delete delegate delete method
func (r *Route) Delete(pattern string, h ...interface{}) {
var middlewares = r.getMiddlewares(h)
middlewares := r.getMiddlewares(h)
r.R.Delete(r.getPattern(pattern), Wrap(middlewares...))
}
func (r *Route) getMiddlewares(h []interface{}) []interface{} {
var middlewares = make([]interface{}, len(r.curMiddlewares), len(r.curMiddlewares)+len(h))
middlewares := make([]interface{}, len(r.curMiddlewares), len(r.curMiddlewares)+len(h))
copy(middlewares, r.curMiddlewares)
middlewares = append(middlewares, h...)
return middlewares
@@ -275,51 +281,51 @@ func (r *Route) getMiddlewares(h []interface{}) []interface{} {
// Get delegate get method
func (r *Route) Get(pattern string, h ...interface{}) {
var middlewares = r.getMiddlewares(h)
middlewares := r.getMiddlewares(h)
r.R.Get(r.getPattern(pattern), Wrap(middlewares...))
}
// Options delegate options method
func (r *Route) Options(pattern string, h ...interface{}) {
var middlewares = r.getMiddlewares(h)
middlewares := r.getMiddlewares(h)
r.R.Options(r.getPattern(pattern), Wrap(middlewares...))
}
// GetOptions delegate get and options method
func (r *Route) GetOptions(pattern string, h ...interface{}) {
var middlewares = r.getMiddlewares(h)
middlewares := r.getMiddlewares(h)
r.R.Get(r.getPattern(pattern), Wrap(middlewares...))
r.R.Options(r.getPattern(pattern), Wrap(middlewares...))
}
// PostOptions delegate post and options method
func (r *Route) PostOptions(pattern string, h ...interface{}) {
var middlewares = r.getMiddlewares(h)
middlewares := r.getMiddlewares(h)
r.R.Post(r.getPattern(pattern), Wrap(middlewares...))
r.R.Options(r.getPattern(pattern), Wrap(middlewares...))
}
// Head delegate head method
func (r *Route) Head(pattern string, h ...interface{}) {
var middlewares = r.getMiddlewares(h)
middlewares := r.getMiddlewares(h)
r.R.Head(r.getPattern(pattern), Wrap(middlewares...))
}
// Post delegate post method
func (r *Route) Post(pattern string, h ...interface{}) {
var middlewares = r.getMiddlewares(h)
middlewares := r.getMiddlewares(h)
r.R.Post(r.getPattern(pattern), Wrap(middlewares...))
}
// Put delegate put method
func (r *Route) Put(pattern string, h ...interface{}) {
var middlewares = r.getMiddlewares(h)
middlewares := r.getMiddlewares(h)
r.R.Put(r.getPattern(pattern), Wrap(middlewares...))
}
// Patch delegate patch method
func (r *Route) Patch(pattern string, h ...interface{}) {
var middlewares = r.getMiddlewares(h)
middlewares := r.getMiddlewares(h)
r.R.Patch(r.getPattern(pattern), Wrap(middlewares...))
}

View File

@@ -53,6 +53,7 @@ func TestRoute2(t *testing.T) {
tp := chi.URLParam(req, "type")
assert.EqualValues(t, "issues", tp)
route = 0
resp.WriteHeader(200)
})
r.Get("/{type:issues|pulls}/{index}", func(resp http.ResponseWriter, req *http.Request) {
@@ -65,9 +66,8 @@ func TestRoute2(t *testing.T) {
index := chi.URLParam(req, "index")
assert.EqualValues(t, "1", index)
route = 1
resp.WriteHeader(200)
})
}, func(resp http.ResponseWriter, req *http.Request) {
resp.WriteHeader(200)
})
r.Group("/issues/{index}", func() {
@@ -79,6 +79,7 @@ func TestRoute2(t *testing.T) {
index := chi.URLParam(req, "index")
assert.EqualValues(t, "1", index)
route = 2
resp.WriteHeader(200)
})
})
})

View File

@@ -954,7 +954,7 @@ func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
}, mustAllowPulls, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(false))
m.Group("/statuses", func() {
m.Combo("/{sha}").Get(repo.GetCommitStatuses).
Post(reqToken(), bind(api.CreateStatusOption{}), repo.NewCommitStatus)
Post(reqToken(), reqRepoWriter(unit.TypeCode), bind(api.CreateStatusOption{}), repo.NewCommitStatus)
}, reqRepoReader(unit.TypeCode))
m.Group("/commits", func() {
m.Get("", repo.GetAllCommits)

View File

@@ -883,7 +883,7 @@ func dismissReview(ctx *context.APIContext, msg string, isDismiss bool) {
return
}
_, err := pull_service.DismissReview(review.ID, msg, ctx.User, isDismiss)
_, err := pull_service.DismissReview(review.ID, ctx.Repo.Repository.ID, msg, ctx.User, isDismiss)
if err != nil {
ctx.Error(http.StatusInternalServerError, "pull_service.DismissReview", err)
return

View File

@@ -344,6 +344,8 @@ func DeleteRelease(ctx *context.APIContext) {
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
// "405":
// "$ref": "#/responses/empty"
id := ctx.ParamsInt64(":id")
rel, err := models.GetReleaseByID(id)
@@ -357,6 +359,10 @@ func DeleteRelease(ctx *context.APIContext) {
return
}
if err := releaseservice.DeleteReleaseByID(id, ctx.User, false); err != nil {
if models.IsErrProtectedTagName(err) {
ctx.Error(http.StatusMethodNotAllowed, "delTag", "user not allowed to delete protected tag")
return
}
ctx.Error(http.StatusInternalServerError, "DeleteReleaseByID", err)
return
}

View File

@@ -92,6 +92,8 @@ func DeleteReleaseByTag(ctx *context.APIContext) {
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
// "405":
// "$ref": "#/responses/empty"
tag := ctx.Params(":tag")
@@ -111,7 +113,12 @@ func DeleteReleaseByTag(ctx *context.APIContext) {
}
if err = releaseservice.DeleteReleaseByID(release.ID, ctx.User, false); err != nil {
if models.IsErrProtectedTagName(err) {
ctx.Error(http.StatusMethodNotAllowed, "delTag", "user not allowed to delete protected tag")
return
}
ctx.Error(http.StatusInternalServerError, "DeleteReleaseByID", err)
return
}
ctx.Status(http.StatusNoContent)

View File

@@ -176,6 +176,8 @@ func CreateTag(ctx *context.APIContext) {
// "$ref": "#/responses/Tag"
// "404":
// "$ref": "#/responses/notFound"
// "405":
// "$ref": "#/responses/empty"
// "409":
// "$ref": "#/responses/conflict"
form := web.GetForm(ctx).(*api.CreateTagOption)
@@ -196,6 +198,11 @@ func CreateTag(ctx *context.APIContext) {
ctx.Error(http.StatusConflict, "tag exist", err)
return
}
if models.IsErrProtectedTagName(err) {
ctx.Error(http.StatusMethodNotAllowed, "CreateNewTag", "user not allowed to create protected tag")
return
}
ctx.InternalServerError(err)
return
}
@@ -236,6 +243,8 @@ func DeleteTag(ctx *context.APIContext) {
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
// "405":
// "$ref": "#/responses/empty"
// "409":
// "$ref": "#/responses/conflict"
tagName := ctx.Params("*")
@@ -256,7 +265,12 @@ func DeleteTag(ctx *context.APIContext) {
}
if err = releaseservice.DeleteReleaseByID(tag.ID, ctx.User, true); err != nil {
if models.IsErrProtectedTagName(err) {
ctx.Error(http.StatusMethodNotAllowed, "delTag", "user not allowed to delete protected tag")
return
}
ctx.Error(http.StatusInternalServerError, "DeleteReleaseByID", err)
return
}
ctx.Status(http.StatusNoContent)

View File

@@ -24,13 +24,13 @@ func responseAPIUsers(ctx *context.APIContext, users []*user_model.User) {
}
func listUserFollowers(ctx *context.APIContext, u *user_model.User) {
users, err := user_model.GetUserFollowers(u, utils.GetListOptions(ctx))
users, count, err := user_model.GetUserFollowers(ctx, u, ctx.User, utils.GetListOptions(ctx))
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetUserFollowers", err)
return
}
ctx.SetTotalCountHeader(int64(u.NumFollowers))
ctx.SetTotalCountHeader(count)
responseAPIUsers(ctx, users)
}
@@ -90,13 +90,13 @@ func ListFollowers(ctx *context.APIContext) {
}
func listUserFollowing(ctx *context.APIContext, u *user_model.User) {
users, err := user_model.GetUserFollowing(u, utils.GetListOptions(ctx))
users, count, err := user_model.GetUserFollowing(ctx, u, ctx.User, utils.GetListOptions(ctx))
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetUserFollowing", err)
return
}
ctx.SetTotalCountHeader(int64(u.NumFollowing))
ctx.SetTotalCountHeader(count)
responseAPIUsers(ctx, users)
}

View File

@@ -87,10 +87,14 @@ func ServeData(ctx *context.Context, name string, size int64, reader io.Reader)
}
if (st.IsImage() || st.IsPDF()) && (setting.UI.SVG.Enabled || !st.IsSvgImage()) {
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, name))
if st.IsSvgImage() {
if st.IsSvgImage() || st.IsPDF() {
ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox")
ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff")
ctx.Resp.Header().Set("Content-Type", typesniffer.SvgMimeType)
if st.IsSvgImage() {
ctx.Resp.Header().Set("Content-Type", typesniffer.SvgMimeType)
} else {
ctx.Resp.Header().Set("Content-Type", typesniffer.ApplicationOctetStream)
}
}
} else {
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, name))

View File

@@ -417,7 +417,7 @@ func SignUp(ctx *context.Context) {
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
ctx.Data["PageIsSignUp"] = true
//Show Disabled Registration message if DisableRegistration or AllowOnlyExternalRegistration options are true
// Show Disabled Registration message if DisableRegistration or AllowOnlyExternalRegistration options are true
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration || setting.Service.AllowOnlyExternalRegistration
ctx.HTML(http.StatusOK, tplSignUp)
@@ -438,7 +438,7 @@ func SignUpPost(ctx *context.Context) {
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
ctx.Data["PageIsSignUp"] = true
//Permission denied if DisableRegistration or AllowOnlyExternalRegistration options are true
// Permission denied if DisableRegistration or AllowOnlyExternalRegistration options are true
if setting.Service.DisableRegistration || setting.Service.AllowOnlyExternalRegistration {
ctx.Error(http.StatusForbidden)
return
@@ -632,8 +632,10 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language())
ctx.HTML(http.StatusOK, TplActivate)
if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
if setting.CacheService.Enabled {
if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
}
}
return
}
@@ -653,14 +655,15 @@ func Activate(ctx *context.Context) {
}
// Resend confirmation email.
if setting.Service.RegisterEmailConfirm {
if ctx.Cache.IsExist("MailResendLimit_" + ctx.User.LowerName) {
if setting.CacheService.Enabled && ctx.Cache.IsExist("MailResendLimit_"+ctx.User.LowerName) {
ctx.Data["ResendLimited"] = true
} else {
ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language())
mailer.SendActivateAccountMail(ctx.Locale, ctx.User)
if err := ctx.Cache.Put("MailResendLimit_"+ctx.User.LowerName, ctx.User.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
if setting.CacheService.Enabled {
if err := ctx.Cache.Put("MailResendLimit_"+ctx.User.LowerName, ctx.User.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
}
}
}
} else {
@@ -789,7 +792,7 @@ func ActivateEmail(ctx *context.Context) {
if u, err := user_model.GetUserByID(email.UID); err != nil {
log.Warn("GetUserByID: %d", email.UID)
} else {
} else if setting.CacheService.Enabled {
// Allow user to validate more emails
_ = ctx.Cache.Delete("MailResendLimit_" + u.LowerName)
}

View File

@@ -80,7 +80,7 @@ func ForgotPasswdPost(ctx *context.Context) {
return
}
if ctx.Cache.IsExist("MailResendLimit_" + u.LowerName) {
if setting.CacheService.Enabled && ctx.Cache.IsExist("MailResendLimit_"+u.LowerName) {
ctx.Data["ResendLimited"] = true
ctx.HTML(http.StatusOK, tplForgotPassword)
return
@@ -88,8 +88,10 @@ func ForgotPasswdPost(ctx *context.Context) {
mailer.SendResetPasswordMail(u)
if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
if setting.CacheService.Enabled {
if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
}
}
ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale.Language())

View File

@@ -370,6 +370,12 @@ func CreateBranch(ctx *context.Context) {
err = repo_service.CreateNewBranchFromCommit(ctx.User, ctx.Repo.Repository, ctx.Repo.CommitID, form.NewBranchName)
}
if err != nil {
if models.IsErrProtectedTagName(err) {
ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected"))
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
return
}
if models.IsErrTagAlreadyExists(err) {
e := err.(models.ErrTagAlreadyExists)
ctx.Flash.Error(ctx.Tr("repo.branch.tag_collision", e.TagName))

View File

@@ -57,17 +57,15 @@ const (
issueTemplateTitleKey = "IssueTemplateTitle"
)
var (
// IssueTemplateCandidates issue templates
IssueTemplateCandidates = []string{
"ISSUE_TEMPLATE.md",
"issue_template.md",
".gitea/ISSUE_TEMPLATE.md",
".gitea/issue_template.md",
".github/ISSUE_TEMPLATE.md",
".github/issue_template.md",
}
)
// IssueTemplateCandidates issue templates
var IssueTemplateCandidates = []string{
"ISSUE_TEMPLATE.md",
"issue_template.md",
".gitea/ISSUE_TEMPLATE.md",
".gitea/issue_template.md",
".github/ISSUE_TEMPLATE.md",
".github/issue_template.md",
}
// MustAllowUserComment checks to make sure if an issue is locked.
// If locked and user has permissions to write to the repository,
@@ -245,7 +243,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
}
}
var issueList = models.IssueList(issues)
issueList := models.IssueList(issues)
approvalCounts, err := issueList.GetApprovalCounts()
if err != nil {
ctx.ServerError("ApprovalCounts", err)
@@ -311,8 +309,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
assigneeID = 0 // Reset ID to prevent unexpected selection of assignee.
}
ctx.Data["IssueRefEndNames"], ctx.Data["IssueRefURLs"] =
issue_service.GetRefEndNamesAndURLs(issues, ctx.Repo.RepoLink)
ctx.Data["IssueRefEndNames"], ctx.Data["IssueRefURLs"] = issue_service.GetRefEndNamesAndURLs(issues, ctx.Repo.RepoLink)
ctx.Data["ApprovalCounts"] = func(issueID int64, typ string) int64 {
counts, ok := approvalCounts[issueID]
@@ -442,7 +439,6 @@ func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *repo_model.R
}
func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) {
var err error
ctx.Data["OpenProjects"], _, err = models.GetProjects(models.ProjectSearchOptions{
@@ -796,7 +792,8 @@ func NewIssue(ctx *context.Context) {
body := ctx.FormString("body")
ctx.Data["BodyQuery"] = body
ctx.Data["IsProjectsEnabled"] = ctx.Repo.CanRead(unit.TypeProjects)
isProjectsEnabled := ctx.Repo.CanRead(unit.TypeProjects)
ctx.Data["IsProjectsEnabled"] = isProjectsEnabled
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
upload.AddUploadContext(ctx, "comment")
@@ -812,7 +809,7 @@ func NewIssue(ctx *context.Context) {
}
projectID := ctx.FormInt64("project")
if projectID > 0 {
if projectID > 0 && isProjectsEnabled {
project, err := models.GetProjectByID(projectID)
if err != nil {
log.Error("GetProjectByID: %d: %v", projectID, err)
@@ -1017,6 +1014,12 @@ func NewIssuePost(ctx *context.Context) {
}
if projectID > 0 {
if !ctx.Repo.CanRead(unit.TypeProjects) {
// User must also be able to see the project.
ctx.Error(http.StatusBadRequest, "user hasn't permissions to read projects")
return
}
if err := models.ChangeProjectAssign(issue, ctx.User, projectID); err != nil {
ctx.ServerError("ChangeProjectAssign", err)
return
@@ -1713,6 +1716,11 @@ func getActionIssues(ctx *context.Context) []*models.Issue {
issueUnitEnabled := ctx.Repo.CanRead(unit.TypeIssues)
prUnitEnabled := ctx.Repo.CanRead(unit.TypePullRequests)
for _, issue := range issues {
if issue.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound("some issue's RepoID is incorrect", errors.New("some issue's RepoID is incorrect"))
return nil
}
if issue.IsPull && !prUnitEnabled || !issue.IsPull && !issueUnitEnabled {
ctx.NotFound("IssueOrPullRequestUnitNotAllowed", nil)
return nil
@@ -2515,7 +2523,7 @@ func filterXRefComments(ctx *context.Context, issue *models.Issue) error {
// GetIssueAttachments returns attachments for the issue
func GetIssueAttachments(ctx *context.Context) {
issue := GetActionIssue(ctx)
var attachments = make([]*api.Attachment, len(issue.Attachments))
attachments := make([]*api.Attachment, len(issue.Attachments))
for i := 0; i < len(issue.Attachments); i++ {
attachments[i] = convert.ToReleaseAttachment(issue.Attachments[i])
}
@@ -2529,7 +2537,7 @@ func GetCommentAttachments(ctx *context.Context) {
ctx.NotFoundOrServerError("GetCommentByID", models.IsErrCommentNotExist, err)
return
}
var attachments = make([]*api.Attachment, 0)
attachments := make([]*api.Attachment, 0)
if comment.Type == models.CommentTypeComment {
if err := comment.LoadAttachments(); err != nil {
ctx.ServerError("LoadAttachments", err)
@@ -2674,7 +2682,7 @@ func handleTeamMentions(ctx *context.Context) {
var isAdmin bool
var err error
var teams []*models.Team
var org = models.OrgFromUser(ctx.Repo.Owner)
org := models.OrgFromUser(ctx.Repo.Owner)
// Admin has super access.
if ctx.User.IsAdmin {
isAdmin = true

View File

@@ -5,6 +5,7 @@
package repo
import (
"errors"
"fmt"
"net/http"
"net/url"
@@ -531,7 +532,6 @@ func EditProjectBoard(ctx *context.Context) {
// SetDefaultProjectBoard set default board for uncategorized issues/pulls
func SetDefaultProjectBoard(ctx *context.Context) {
project, board := checkProjectBoardChangePermissions(ctx)
if ctx.Written() {
return
@@ -631,10 +631,17 @@ func MoveIssues(ctx *context.Context) {
}
if len(movedIssues) != len(form.Issues) {
ctx.ServerError("IssuesNotFound", err)
ctx.ServerError("some issues do not exist", errors.New("some issues do not exist"))
return
}
for _, issue := range movedIssues {
if issue.RepoID != project.RepoID {
ctx.ServerError("Some issue's repoID is not equal to project's repoID", errors.New("Some issue's repoID is not equal to project's repoID"))
return
}
}
if err = models.MoveIssuesOnProjectBoard(board, sortedIssueIDs); err != nil {
ctx.ServerError("MoveIssuesOnProjectBoard", err)
return

View File

@@ -5,6 +5,7 @@
package repo
import (
"errors"
"fmt"
"net/http"
@@ -116,6 +117,11 @@ func UpdateResolveConversation(ctx *context.Context) {
return
}
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound("comment's repoID is incorrect", errors.New("comment's repoID is incorrect"))
return
}
var permResult bool
if permResult, err = models.CanMarkConversation(comment.Issue, ctx.User); err != nil {
ctx.ServerError("CanMarkConversation", err)
@@ -234,7 +240,7 @@ func SubmitReview(ctx *context.Context) {
// DismissReview dismissing stale review by repo admin
func DismissReview(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.DismissReviewForm)
comm, err := pull_service.DismissReview(form.ReviewID, form.Message, ctx.User, true)
comm, err := pull_service.DismissReview(form.ReviewID, ctx.Repo.Repository.ID, form.Message, ctx.User, true)
if err != nil {
ctx.ServerError("pull_service.DismissReview", err)
return

View File

@@ -98,7 +98,14 @@ func releasesOrTags(ctx *context.Context, isTagList bool) {
listOptions.PageSize = setting.API.MaxResponseItems
}
tags, err := ctx.Repo.GitRepo.GetTags(listOptions.GetStartEnd())
// TODO(20073) tags are used for compare feature witch needs all tags
// filtering is doen at the client side atm
tagListStart, tagListEnd := 0, 0
if isTagList {
tagListStart, tagListEnd = listOptions.GetStartEnd()
}
tags, err := ctx.Repo.GitRepo.GetTags(tagListStart, tagListEnd)
if err != nil {
ctx.ServerError("GetTags", err)
return
@@ -519,7 +526,11 @@ func DeleteTag(ctx *context.Context) {
func deleteReleaseOrTag(ctx *context.Context, isDelTag bool) {
if err := releaseservice.DeleteReleaseByID(ctx.FormInt64("id"), ctx.User, isDelTag); err != nil {
ctx.Flash.Error("DeleteReleaseByID: " + err.Error())
if models.IsErrProtectedTagName(err) {
ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected"))
} else {
ctx.Flash.Error("DeleteReleaseByID: " + err.Error())
}
} else {
if isDelTag {
ctx.Flash.Success(ctx.Tr("repo.release.deletion_tag_success"))

View File

@@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
@@ -35,7 +36,7 @@ func GetNotificationCount(c *context.Context) {
c.Data["NotificationUnreadCount"] = func() int64 {
count, err := models.GetNotificationCount(c.User, models.NotificationStatusUnread)
if err != nil {
c.ServerError("GetNotificationCount", err)
log.Error("Unable to GetNotificationCount for user:%-v: %v", c.User, err)
return -1
}

View File

@@ -234,7 +234,7 @@ func Profile(ctx *context.Context) {
ctx.Data["Keyword"] = keyword
switch tab {
case "followers":
items, err := user_model.GetUserFollowers(ctxUser, db.ListOptions{
items, count, err := user_model.GetUserFollowers(ctx, ctxUser, ctx.User, db.ListOptions{
PageSize: setting.UI.User.RepoPagingNum,
Page: page,
})
@@ -244,9 +244,9 @@ func Profile(ctx *context.Context) {
}
ctx.Data["Cards"] = items
total = ctxUser.NumFollowers
total = int(count)
case "following":
items, err := user_model.GetUserFollowing(ctxUser, db.ListOptions{
items, count, err := user_model.GetUserFollowing(ctx, ctxUser, ctx.User, db.ListOptions{
PageSize: setting.UI.User.RepoPagingNum,
Page: page,
})
@@ -256,9 +256,10 @@ func Profile(ctx *context.Context) {
}
ctx.Data["Cards"] = items
total = ctxUser.NumFollowing
total = int(count)
case "activity":
ctx.Data["Feeds"] = feed.RetrieveFeeds(ctx, models.GetFeedsOptions{RequestedUser: ctxUser,
ctx.Data["Feeds"] = feed.RetrieveFeeds(ctx, models.GetFeedsOptions{
RequestedUser: ctxUser,
Actor: ctx.User,
IncludePrivate: showPrivate,
OnlyPerformedBy: true,

View File

@@ -35,6 +35,7 @@ func Account(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsAccount"] = true
ctx.Data["Email"] = ctx.User.Email
ctx.Data["EnableNotifyMail"] = setting.Service.EnableNotifyMail
loadAccountData(ctx)
@@ -106,7 +107,7 @@ func EmailPost(ctx *context.Context) {
// Send activation Email
if ctx.FormString("_method") == "SENDACTIVATION" {
var address string
if ctx.Cache.IsExist("MailResendLimit_" + ctx.User.LowerName) {
if setting.CacheService.Enabled && ctx.Cache.IsExist("MailResendLimit_"+ctx.User.LowerName) {
log.Error("Send activation: activation still pending")
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
return
@@ -142,8 +143,10 @@ func EmailPost(ctx *context.Context) {
}
address = email.Email
if err := ctx.Cache.Put("MailResendLimit_"+ctx.User.LowerName, ctx.User.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
if setting.CacheService.Enabled {
if err := ctx.Cache.Put("MailResendLimit_"+ctx.User.LowerName, ctx.User.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
}
}
ctx.Flash.Info(ctx.Tr("settings.add_email_confirmation_sent", address, timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language())))
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
@@ -202,8 +205,10 @@ func EmailPost(ctx *context.Context) {
// Send confirmation email
if setting.Service.RegisterEmailConfirm {
mailer.SendActivateEmailMail(ctx.User, email)
if err := ctx.Cache.Put("MailResendLimit_"+ctx.User.LowerName, ctx.User.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
if setting.CacheService.Enabled {
if err := ctx.Cache.Put("MailResendLimit_"+ctx.User.LowerName, ctx.User.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
}
}
ctx.Flash.Info(ctx.Tr("settings.add_email_confirmation_sent", email.Email, timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language())))
} else {
@@ -271,7 +276,7 @@ func loadAccountData(ctx *context.Context) {
user_model.EmailAddress
CanBePrimary bool
}
pendingActivation := ctx.Cache.IsExist("MailResendLimit_" + ctx.User.LowerName)
pendingActivation := setting.CacheService.Enabled && ctx.Cache.IsExist("MailResendLimit_"+ctx.User.LowerName)
emails := make([]*UserEmail, len(emlist))
for i, em := range emlist {
var email UserEmail

View File

@@ -16,9 +16,10 @@ import (
// DeleteNotPassedAssignee deletes all assignees who aren't passed via the "assignees" array
func DeleteNotPassedAssignee(issue *models.Issue, doer *user_model.User, assignees []*user_model.User) (err error) {
var found bool
oriAssignes := make([]*user_model.User, len(issue.Assignees))
_ = copy(oriAssignes, issue.Assignees)
for _, assignee := range issue.Assignees {
for _, assignee := range oriAssignes {
found = false
for _, alreadyAssignee := range assignees {
if assignee.ID == alreadyAssignee.ID {

View File

@@ -284,6 +284,7 @@ func (s *sendmailSender) Send(from string, to []string, msg io.WriterTo) error {
if err != nil {
return err
}
process.SetSysProcAttribute(cmd)
if err = cmd.Start(); err != nil {
_ = pipe.Close()

View File

@@ -271,7 +271,7 @@ func SubmitReview(doer *user_model.User, gitRepo *git.Repository, issue *models.
}
// DismissReview dismissing stale review by repo admin
func DismissReview(reviewID int64, message string, doer *user_model.User, isDismiss bool) (comment *models.Comment, err error) {
func DismissReview(reviewID, repoID int64, message string, doer *user_model.User, isDismiss bool) (comment *models.Comment, err error) {
review, err := models.GetReviewByID(reviewID)
if err != nil {
return
@@ -281,6 +281,16 @@ func DismissReview(reviewID int64, message string, doer *user_model.User, isDism
return nil, fmt.Errorf("not need to dismiss this review because it's type is not Approve or change request")
}
// load data for notify
if err = review.LoadAttributes(); err != nil {
return nil, err
}
// Check if the review's repoID is the one we're currently expecting.
if review.Issue.RepoID != repoID {
return nil, fmt.Errorf("reviews's repository is not the same as the one we expect")
}
if err = models.DismissReview(review, isDismiss); err != nil {
return
}
@@ -289,10 +299,6 @@ func DismissReview(reviewID int64, message string, doer *user_model.User, isDism
return nil, nil
}
// load data for notify
if err = review.LoadAttributes(); err != nil {
return
}
if err = review.Issue.LoadPullRequest(); err != nil {
return
}

View File

@@ -295,6 +295,20 @@ func DeleteReleaseByID(id int64, doer *user_model.User, delTag bool) error {
}
if delTag {
protectedTags, err := models.GetProtectedTags(rel.RepoID)
if err != nil {
return fmt.Errorf("GetProtectedTags: %v", err)
}
isAllowed, err := models.IsUserAllowedToControlTag(protectedTags, rel.TagName, rel.PublisherID)
if err != nil {
return err
}
if !isAllowed {
return models.ErrProtectedTagName{
TagName: rel.TagName,
}
}
if stdout, err := git.NewCommand("tag", "-d", rel.TagName).
SetDescription(fmt.Sprintf("DeleteReleaseByID (git tag -d): %d", rel.ID)).
RunInDir(repo.RepoPath()); err != nil && !strings.Contains(err.Error(), "not found") {

View File

@@ -8515,6 +8515,9 @@
},
"404": {
"$ref": "#/responses/notFound"
},
"405": {
"$ref": "#/responses/empty"
}
}
}
@@ -8598,6 +8601,9 @@
},
"404": {
"$ref": "#/responses/notFound"
},
"405": {
"$ref": "#/responses/empty"
}
}
},
@@ -9366,6 +9372,9 @@
"404": {
"$ref": "#/responses/notFound"
},
"405": {
"$ref": "#/responses/empty"
},
"409": {
"$ref": "#/responses/conflict"
}
@@ -9453,6 +9462,9 @@
"404": {
"$ref": "#/responses/notFound"
},
"405": {
"$ref": "#/responses/empty"
},
"409": {
"$ref": "#/responses/conflict"
}

View File

@@ -43,6 +43,7 @@
</h4>
<div class="ui attached segment">
<div class="ui email list">
{{if $.EnableNotifyMail}}
<div class="item">
<form action="{{AppSubUrl}}/user/settings/account/email" class="ui form" method="post">
{{.i18n.Tr "settings.email_desc"}}
@@ -69,6 +70,7 @@
</div>
</form>
</div>
{{end}}
{{range .Emails}}
<div class="item">
{{if not .IsPrimary}}

View File

@@ -78,14 +78,14 @@ export function initRepoCommonBranchOrTagDropdown(selector) {
export function initRepoCommonFilterSearchDropdown(selector) {
const $dropdown = $(selector);
$dropdown.dropdown({
fullTextSearch: true,
fullTextSearch: 'exact',
selectOnKeydown: false,
onChange(_text, _value, $choice) {
if ($choice.data('url')) {
window.location.href = $choice.data('url');
if ($choice.attr('data-url')) {
window.location.href = $choice.attr('data-url');
}
},
message: {noResults: $dropdown.data('no-results')},
message: {noResults: $dropdown.attr('data-no-results')},
});
}