Skip to content

Commit ce00c53

Browse files
authored
Merge branch 'master' into milestones
2 parents d25e1f3 + 3da6d25 commit ce00c53

File tree

21 files changed

+1202
-58
lines changed

21 files changed

+1202
-58
lines changed

custom/conf/app.ini.sample

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,12 @@ SHOW_FOOTER_VERSION = true
877877
; Show template execution time in the footer
878878
SHOW_FOOTER_TEMPLATE_LOAD_TIME = true
879879

880+
[markup.sanitizer]
881+
; The following keys can be used multiple times to define sanitation policy rules.
882+
;ELEMENT = span
883+
;ALLOW_ATTR = class
884+
;REGEXP = ^(info|warning|error)$
885+
880886
[markup.asciidoc]
881887
ENABLED = false
882888
; List of file extensions that should be rendered by an external command

docs/content/doc/advanced/config-cheat-sheet.en-us.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,24 @@ Two special environment variables are passed to the render command:
578578
- `GITEA_PREFIX_SRC`, which contains the current URL prefix in the `src` path tree. To be used as prefix for links.
579579
- `GITEA_PREFIX_RAW`, which contains the current URL prefix in the `raw` path tree. To be used as prefix for image paths.
580580

581+
582+
Gitea supports customizing the sanitization policy for rendered HTML. The example below will support KaTeX output from pandoc.
583+
584+
```ini
585+
[markup.sanitizer]
586+
; Pandoc renders TeX segments as <span>s with the "math" class, optionally
587+
; with "inline" or "display" classes depending on context.
588+
ELEMENT = span
589+
ALLOW_ATTR = class
590+
REGEXP = ^\s*((math(\s+|$)|inline(\s+|$)|display(\s+|$)))+
591+
```
592+
593+
- `ELEMENT`: The element this policy applies to. Must be non-empty.
594+
- `ALLOW_ATTR`: The attribute this policy allows. Must be non-empty.
595+
- `REGEXP`: A regex to match the contents of the attribute against. Must be present but may be empty for unconditional whitelisting of this attribute.
596+
597+
You may redefine `ELEMENT`, `ALLOW_ATTR`, and `REGEXP` multiple times; each time all three are defined is a single policy entry.
598+
581599
## Time (`time`)
582600

583601
- `FORMAT`: Time format to diplay on UI. i.e. RFC1123 or 2006-01-02 15:04:05

docs/content/doc/advanced/external-renderers.en-us.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,22 @@ RENDER_COMMAND = rst2html.py
6868
IS_INPUT_FILE = false
6969
```
7070

71+
If your external markup relies on additional classes and attributes on the generated HTML elements, you might need to enable custom sanitizer policies. Gitea uses the [`bluemonday`](https://godoc.org/github.com/microcosm-cc/bluemonday) package as our HTML sanitizier. The example below will support [KaTeX](https://katex.org/) output from [`pandoc`](https://pandoc.org/).
72+
73+
```ini
74+
[markup.sanitizer]
75+
; Pandoc renders TeX segments as <span>s with the "math" class, optionally
76+
; with "inline" or "display" classes depending on context.
77+
ELEMENT = span
78+
ALLOW_ATTR = class
79+
REGEXP = ^\s*((math(\s+|$)|inline(\s+|$)|display(\s+|$)))+
80+
81+
[markup.markdown]
82+
ENABLED = true
83+
FILE_EXTENSIONS = .md,.markdown
84+
RENDER_COMMAND = pandoc -f markdown -t html --katex
85+
```
86+
87+
You may redefine `ELEMENT`, `ALLOW_ATTR`, and `REGEXP` multiple times; each time all three are defined is a single policy entry. All three must be defined, but `REGEXP` may be blank to allow unconditional whitelisting of that attribute.
88+
7189
Once your configuration changes have been made, restart Gitea to have changes take effect.
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// Copyright 2019 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package integrations
6+
7+
import (
8+
"fmt"
9+
"net/http"
10+
"testing"
11+
"time"
12+
13+
"code.gitea.io/gitea/models"
14+
api "code.gitea.io/gitea/modules/structs"
15+
16+
"github.com/stretchr/testify/assert"
17+
)
18+
19+
func TestAPIIssuesReactions(t *testing.T) {
20+
defer prepareTestEnv(t)()
21+
22+
issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 1}).(*models.Issue)
23+
_ = issue.LoadRepo()
24+
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: issue.Repo.OwnerID}).(*models.User)
25+
26+
session := loginUser(t, owner.Name)
27+
token := getTokenForLoggedInUser(t, session)
28+
29+
user1 := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User)
30+
user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
31+
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/reactions?token=%s",
32+
owner.Name, issue.Repo.Name, issue.Index, token)
33+
34+
//Try to add not allowed reaction
35+
req := NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
36+
Reaction: "wrong",
37+
})
38+
resp := session.MakeRequest(t, req, http.StatusForbidden)
39+
40+
//Delete not allowed reaction
41+
req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{
42+
Reaction: "zzz",
43+
})
44+
resp = session.MakeRequest(t, req, http.StatusOK)
45+
46+
//Add allowed reaction
47+
req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
48+
Reaction: "rocket",
49+
})
50+
resp = session.MakeRequest(t, req, http.StatusCreated)
51+
var apiNewReaction api.ReactionResponse
52+
DecodeJSON(t, resp, &apiNewReaction)
53+
54+
//Add existing reaction
55+
resp = session.MakeRequest(t, req, http.StatusForbidden)
56+
57+
//Get end result of reaction list of issue #1
58+
req = NewRequestf(t, "GET", urlStr)
59+
resp = session.MakeRequest(t, req, http.StatusOK)
60+
var apiReactions []*api.ReactionResponse
61+
DecodeJSON(t, resp, &apiReactions)
62+
expectResponse := make(map[int]api.ReactionResponse)
63+
expectResponse[0] = api.ReactionResponse{
64+
User: user1.APIFormat(),
65+
Reaction: "zzz",
66+
Created: time.Unix(1573248002, 0),
67+
}
68+
expectResponse[1] = api.ReactionResponse{
69+
User: user2.APIFormat(),
70+
Reaction: "eyes",
71+
Created: time.Unix(1573248003, 0),
72+
}
73+
expectResponse[2] = apiNewReaction
74+
assert.Len(t, apiReactions, 3)
75+
for i, r := range apiReactions {
76+
assert.Equal(t, expectResponse[i].Reaction, r.Reaction)
77+
assert.Equal(t, expectResponse[i].Created.Unix(), r.Created.Unix())
78+
assert.Equal(t, expectResponse[i].User.ID, r.User.ID)
79+
}
80+
}
81+
82+
func TestAPICommentReactions(t *testing.T) {
83+
defer prepareTestEnv(t)()
84+
85+
comment := models.AssertExistsAndLoadBean(t, &models.Comment{ID: 2}).(*models.Comment)
86+
_ = comment.LoadIssue()
87+
issue := comment.Issue
88+
_ = issue.LoadRepo()
89+
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: issue.Repo.OwnerID}).(*models.User)
90+
91+
session := loginUser(t, owner.Name)
92+
token := getTokenForLoggedInUser(t, session)
93+
94+
user1 := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User)
95+
user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
96+
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/reactions?token=%s",
97+
owner.Name, issue.Repo.Name, comment.ID, token)
98+
99+
//Try to add not allowed reaction
100+
req := NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
101+
Reaction: "wrong",
102+
})
103+
resp := session.MakeRequest(t, req, http.StatusForbidden)
104+
105+
//Delete none existing reaction
106+
req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{
107+
Reaction: "eyes",
108+
})
109+
resp = session.MakeRequest(t, req, http.StatusOK)
110+
111+
//Add allowed reaction
112+
req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
113+
Reaction: "+1",
114+
})
115+
resp = session.MakeRequest(t, req, http.StatusCreated)
116+
var apiNewReaction api.ReactionResponse
117+
DecodeJSON(t, resp, &apiNewReaction)
118+
119+
//Add existing reaction
120+
resp = session.MakeRequest(t, req, http.StatusForbidden)
121+
122+
//Get end result of reaction list of issue #1
123+
req = NewRequestf(t, "GET", urlStr)
124+
resp = session.MakeRequest(t, req, http.StatusOK)
125+
var apiReactions []*api.ReactionResponse
126+
DecodeJSON(t, resp, &apiReactions)
127+
expectResponse := make(map[int]api.ReactionResponse)
128+
expectResponse[0] = api.ReactionResponse{
129+
User: user2.APIFormat(),
130+
Reaction: "laugh",
131+
Created: time.Unix(1573248004, 0),
132+
}
133+
expectResponse[1] = api.ReactionResponse{
134+
User: user1.APIFormat(),
135+
Reaction: "laugh",
136+
Created: time.Unix(1573248005, 0),
137+
}
138+
expectResponse[2] = apiNewReaction
139+
assert.Len(t, apiReactions, 3)
140+
for i, r := range apiReactions {
141+
assert.Equal(t, expectResponse[i].Reaction, r.Reaction)
142+
assert.Equal(t, expectResponse[i].Created.Unix(), r.Created.Unix())
143+
assert.Equal(t, expectResponse[i].User.ID, r.User.ID)
144+
}
145+
}

integrations/benchmarks_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func BenchmarkRepo(b *testing.B) {
2525
{url: "https://github.com/golang/go.git", name: "go", skipShort: true},
2626
{url: "https://github.com/torvalds/linux.git", name: "linux", skipShort: true},
2727
}
28-
prepareTestEnv(b)
28+
defer prepareTestEnv(b)()
2929
session := loginUser(b, "user2")
3030
b.ResetTimer()
3131

@@ -75,7 +75,7 @@ func StringWithCharset(length int, charset string) string {
7575

7676
func BenchmarkRepoBranchCommit(b *testing.B) {
7777
samples := []int64{1, 3, 15, 16}
78-
prepareTestEnv(b)
78+
defer prepareTestEnv(b)()
7979
b.ResetTimer()
8080

8181
for _, repoID := range samples {

integrations/git_helper_for_declarative_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func allowLFSFilters() []string {
7878

7979
func onGiteaRun(t *testing.T, callback func(*testing.T, *url.URL), prepare ...bool) {
8080
if len(prepare) == 0 || prepare[0] {
81-
prepareTestEnv(t, 1)
81+
defer prepareTestEnv(t, 1)()
8282
}
8383
s := http.Server{
8484
Handler: mac,

integrations/gpg_git_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
)
2424

2525
func TestGPGGit(t *testing.T) {
26-
prepareTestEnv(t)
26+
defer prepareTestEnv(t)()
2727
username := "user2"
2828

2929
// OK Set a new GPG home

models/error.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,6 +1121,21 @@ func (err ErrNewIssueInsert) Error() string {
11211121
return err.OriginalError.Error()
11221122
}
11231123

1124+
// ErrForbiddenIssueReaction is used when a forbidden reaction was try to created
1125+
type ErrForbiddenIssueReaction struct {
1126+
Reaction string
1127+
}
1128+
1129+
// IsErrForbiddenIssueReaction checks if an error is a ErrForbiddenIssueReaction.
1130+
func IsErrForbiddenIssueReaction(err error) bool {
1131+
_, ok := err.(ErrForbiddenIssueReaction)
1132+
return ok
1133+
}
1134+
1135+
func (err ErrForbiddenIssueReaction) Error() string {
1136+
return fmt.Sprintf("'%s' is not an allowed reaction", err.Reaction)
1137+
}
1138+
11241139
// __________ .__ .__ __________ __
11251140
// \______ \__ __| | | |\______ \ ____ ________ __ ____ _______/ |_
11261141
// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\

models/fixtures/reaction.yml

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,39 @@
1-
[] # empty
1+
-
2+
id: 1 #issue reaction
3+
type: zzz # not allowed reaction (added before allowed reaction list has changed)
4+
issue_id: 1
5+
comment_id: 0
6+
user_id: 2
7+
created_unix: 1573248001
8+
9+
-
10+
id: 2 #issue reaction
11+
type: zzz # not allowed reaction (added before allowed reaction list has changed)
12+
issue_id: 1
13+
comment_id: 0
14+
user_id: 1
15+
created_unix: 1573248002
16+
17+
-
18+
id: 3 #issue reaction
19+
type: eyes # allowed reaction
20+
issue_id: 1
21+
comment_id: 0
22+
user_id: 2
23+
created_unix: 1573248003
24+
25+
-
26+
id: 4 #comment reaction
27+
type: laugh # allowed reaction
28+
issue_id: 1
29+
comment_id: 2
30+
user_id: 2
31+
created_unix: 1573248004
32+
33+
-
34+
id: 5 #comment reaction
35+
type: laugh # allowed reaction
36+
issue_id: 1
37+
comment_id: 2
38+
user_id: 1
39+
created_unix: 1573248005

models/issue_reaction.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,38 @@ type FindReactionsOptions struct {
3333
}
3434

3535
func (opts *FindReactionsOptions) toConds() builder.Cond {
36+
//If Issue ID is set add to Query
3637
var cond = builder.NewCond()
3738
if opts.IssueID > 0 {
3839
cond = cond.And(builder.Eq{"reaction.issue_id": opts.IssueID})
3940
}
41+
//If CommentID is > 0 add to Query
42+
//If it is 0 Query ignore CommentID to select
43+
//If it is -1 it explicit search of Issue Reactions where CommentID = 0
4044
if opts.CommentID > 0 {
4145
cond = cond.And(builder.Eq{"reaction.comment_id": opts.CommentID})
46+
} else if opts.CommentID == -1 {
47+
cond = cond.And(builder.Eq{"reaction.comment_id": 0})
4248
}
49+
4350
return cond
4451
}
4552

53+
// FindCommentReactions returns a ReactionList of all reactions from an comment
54+
func FindCommentReactions(comment *Comment) (ReactionList, error) {
55+
return findReactions(x, FindReactionsOptions{
56+
IssueID: comment.IssueID,
57+
CommentID: comment.ID})
58+
}
59+
60+
// FindIssueReactions returns a ReactionList of all reactions from an issue
61+
func FindIssueReactions(issue *Issue) (ReactionList, error) {
62+
return findReactions(x, FindReactionsOptions{
63+
IssueID: issue.ID,
64+
CommentID: -1,
65+
})
66+
}
67+
4668
func findReactions(e Engine, opts FindReactionsOptions) ([]*Reaction, error) {
4769
reactions := make([]*Reaction, 0, 10)
4870
sess := e.Where(opts.toConds())
@@ -77,6 +99,10 @@ type ReactionOptions struct {
7799

78100
// CreateReaction creates reaction for issue or comment.
79101
func CreateReaction(opts *ReactionOptions) (reaction *Reaction, err error) {
102+
if !setting.UI.ReactionsMap[opts.Type] {
103+
return nil, ErrForbiddenIssueReaction{opts.Type}
104+
}
105+
80106
sess := x.NewSession()
81107
defer sess.Close()
82108
if err = sess.Begin(); err != nil {
@@ -160,6 +186,19 @@ func DeleteCommentReaction(doer *User, issue *Issue, comment *Comment, content s
160186
})
161187
}
162188

189+
// LoadUser load user of reaction
190+
func (r *Reaction) LoadUser() (*User, error) {
191+
if r.User != nil {
192+
return r.User, nil
193+
}
194+
user, err := getUserByID(x, r.UserID)
195+
if err != nil {
196+
return nil, err
197+
}
198+
r.User = user
199+
return user, nil
200+
}
201+
163202
// ReactionList represents list of reactions
164203
type ReactionList []*Reaction
165204

0 commit comments

Comments
 (0)