mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-06 04:01:05 +09:00
Follow up #37327. See the comments. * Root problem: the design of OAuth2 providers is a mess, the display name is used as provider's name and used in the URL directly * The regressions: * When trying to fix https://github.com/go-gitea/gitea/issues/36409 , it introduced inconsistent URL escaping for the "path" part. * This fix: always use "path escaping" for the path part, add more tests to cover all escaping cases. Now, frontend "pathEscape" and "pathEscapeSegments" generate exactly the same result as backend.
191 lines
6.9 KiB
Go
191 lines
6.9 KiB
Go
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package templates
|
|
|
|
import (
|
|
"html/template"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
|
|
"code.gitea.io/gitea/modules/util"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestSubjectBodySeparator(t *testing.T) {
|
|
test := func(input, subject, body string) {
|
|
loc := mailSubjectSplit.FindStringIndex(input)
|
|
if loc == nil {
|
|
assert.Empty(t, subject, "no subject found, but one expected")
|
|
assert.Equal(t, body, input)
|
|
} else {
|
|
assert.Equal(t, subject, input[0:loc[0]])
|
|
assert.Equal(t, body, input[loc[1]:])
|
|
}
|
|
}
|
|
|
|
test("Simple\n---------------\nCase",
|
|
"Simple\n",
|
|
"\nCase")
|
|
test("Only\nBody",
|
|
"",
|
|
"Only\nBody")
|
|
test("Minimal\n---\nseparator",
|
|
"Minimal\n",
|
|
"\nseparator")
|
|
test("False --- separator",
|
|
"",
|
|
"False --- separator")
|
|
test("False\n--- separator",
|
|
"",
|
|
"False\n--- separator")
|
|
test("False ---\nseparator",
|
|
"",
|
|
"False ---\nseparator")
|
|
test("With extra spaces\n----- \t \nBody",
|
|
"With extra spaces\n",
|
|
"\nBody")
|
|
test("With leading spaces\n -------\nOnly body",
|
|
"",
|
|
"With leading spaces\n -------\nOnly body")
|
|
test("Multiple\n---\n-------\n---\nSeparators",
|
|
"Multiple\n",
|
|
"\n-------\n---\nSeparators")
|
|
test("Insufficient\n--\nSeparators",
|
|
"",
|
|
"Insufficient\n--\nSeparators")
|
|
}
|
|
|
|
func TestSanitizeHTML(t *testing.T) {
|
|
assert.Equal(t, template.HTML(`<a href="/" rel="nofollow">link</a> xss <div>inline</div>`), sanitizeHTML(`<a href="/">link</a> <a href="javascript:">xss</a> <div style="dangerous">inline</div>`))
|
|
}
|
|
|
|
func TestTemplateIif(t *testing.T) {
|
|
tmpl := template.New("test")
|
|
tmpl.Funcs(template.FuncMap{"Iif": iif})
|
|
template.Must(tmpl.Parse(`{{if .Value}}true{{else}}false{{end}}:{{Iif .Value "true" "false"}}`))
|
|
|
|
cases := []any{nil, false, true, "", "string", 0, 1}
|
|
w := &strings.Builder{}
|
|
truthyCount := 0
|
|
for i, v := range cases {
|
|
w.Reset()
|
|
assert.NoError(t, tmpl.Execute(w, struct{ Value any }{v}), "case %d (%T) %#v fails", i, v, v)
|
|
out := w.String()
|
|
truthyCount += util.Iif(out == "true:true", 1, 0)
|
|
truthyMatches := out == "true:true" || out == "false:false"
|
|
assert.True(t, truthyMatches, "case %d (%T) %#v fail: %s", i, v, v, out)
|
|
}
|
|
assert.True(t, truthyCount != 0 && truthyCount != len(cases))
|
|
}
|
|
|
|
func TestTemplateEscape(t *testing.T) {
|
|
execTmpl := func(code string) string {
|
|
tmpl := template.New("test")
|
|
tmpl.Funcs(template.FuncMap{"QueryBuild": QueryBuild, "HTMLFormat": htmlFormat})
|
|
template.Must(tmpl.Parse(code))
|
|
w := &strings.Builder{}
|
|
assert.NoError(t, tmpl.Execute(w, nil))
|
|
return w.String()
|
|
}
|
|
|
|
t.Run("Golang URL Escape", func(t *testing.T) {
|
|
// Golang template considers "href", "*src*", "*uri*", "*url*" (and more) ... attributes as contentTypeURL and does auto-escaping
|
|
actual := execTmpl(`<a href="?a={{"%"}}"></a>`)
|
|
assert.Equal(t, `<a href="?a=%25"></a>`, actual)
|
|
actual = execTmpl(`<a data-xxx-url="?a={{"%"}}"></a>`)
|
|
assert.Equal(t, `<a data-xxx-url="?a=%25"></a>`, actual)
|
|
})
|
|
t.Run("Golang URL No-escape", func(t *testing.T) {
|
|
// non-URL content isn't auto-escaped
|
|
actual := execTmpl(`<a data-link="?a={{"%"}}"></a>`)
|
|
assert.Equal(t, `<a data-link="?a=%"></a>`, actual)
|
|
})
|
|
t.Run("QueryBuild", func(t *testing.T) {
|
|
actual := execTmpl(`<a href="{{QueryBuild "?" "a" "%"}}"></a>`)
|
|
assert.Equal(t, `<a href="?a=%25"></a>`, actual)
|
|
actual = execTmpl(`<a href="?{{QueryBuild "a" "%"}}"></a>`)
|
|
assert.Equal(t, `<a href="?a=%25"></a>`, actual)
|
|
})
|
|
t.Run("HTMLFormat", func(t *testing.T) {
|
|
actual := execTmpl("{{HTMLFormat `<a k=\"%s\">%s</a>` `\"` `<>`}}")
|
|
assert.Equal(t, `<a k="""><></a>`, actual)
|
|
})
|
|
}
|
|
|
|
func TestQueryBuild(t *testing.T) {
|
|
t.Run("construct", func(t *testing.T) {
|
|
assert.Empty(t, string(QueryBuild()))
|
|
assert.Empty(t, string(QueryBuild("a", nil, "b", false, "c", 0, "d", "")))
|
|
assert.Equal(t, "a=1&b=true", string(QueryBuild("a", 1, "b", "true")))
|
|
|
|
// path with query parameters
|
|
assert.Equal(t, "/?k=1", string(QueryBuild("/", "k", 1)))
|
|
assert.Equal(t, "/", string(QueryBuild("/?k=a", "k", 0)))
|
|
|
|
// no path but question mark with query parameters
|
|
assert.Equal(t, "?k=1", string(QueryBuild("?", "k", 1)))
|
|
assert.Equal(t, "?", string(QueryBuild("?", "k", 0)))
|
|
assert.Equal(t, "path?k=1", string(QueryBuild("path?", "k", 1)))
|
|
assert.Equal(t, "path", string(QueryBuild("path?", "k", 0)))
|
|
|
|
// only query parameters
|
|
assert.Equal(t, "&k=1", string(QueryBuild("&", "k", 1)))
|
|
assert.Empty(t, string(QueryBuild("&", "k", 0)))
|
|
assert.Empty(t, string(QueryBuild("&k=a", "k", 0)))
|
|
assert.Empty(t, string(QueryBuild("k=a&", "k", 0)))
|
|
assert.Equal(t, "a=1&b=2", string(QueryBuild("a=1", "b", 2)))
|
|
assert.Equal(t, "&a=1&b=2", string(QueryBuild("&a=1", "b", 2)))
|
|
assert.Equal(t, "a=1&b=2&", string(QueryBuild("a=1&", "b", 2)))
|
|
})
|
|
|
|
t.Run("replace", func(t *testing.T) {
|
|
assert.Equal(t, "a=1&c=d&e=f", string(QueryBuild("a=b&c=d&e=f", "a", 1)))
|
|
assert.Equal(t, "a=b&c=1&e=f", string(QueryBuild("a=b&c=d&e=f", "c", 1)))
|
|
assert.Equal(t, "a=b&c=d&e=1", string(QueryBuild("a=b&c=d&e=f", "e", 1)))
|
|
assert.Equal(t, "a=b&c=d&e=f&k=1", string(QueryBuild("a=b&c=d&e=f", "k", 1)))
|
|
})
|
|
|
|
t.Run("replace-&", func(t *testing.T) {
|
|
assert.Equal(t, "&a=1&c=d&e=f", string(QueryBuild("&a=b&c=d&e=f", "a", 1)))
|
|
assert.Equal(t, "&a=b&c=1&e=f", string(QueryBuild("&a=b&c=d&e=f", "c", 1)))
|
|
assert.Equal(t, "&a=b&c=d&e=1", string(QueryBuild("&a=b&c=d&e=f", "e", 1)))
|
|
assert.Equal(t, "&a=b&c=d&e=f&k=1", string(QueryBuild("&a=b&c=d&e=f", "k", 1)))
|
|
})
|
|
|
|
t.Run("delete", func(t *testing.T) {
|
|
assert.Equal(t, "c=d&e=f", string(QueryBuild("a=b&c=d&e=f", "a", "")))
|
|
assert.Equal(t, "a=b&e=f", string(QueryBuild("a=b&c=d&e=f", "c", "")))
|
|
assert.Equal(t, "a=b&c=d", string(QueryBuild("a=b&c=d&e=f", "e", "")))
|
|
assert.Equal(t, "a=b&c=d&e=f", string(QueryBuild("a=b&c=d&e=f", "k", "")))
|
|
})
|
|
|
|
t.Run("delete-&", func(t *testing.T) {
|
|
assert.Equal(t, "&c=d&e=f", string(QueryBuild("&a=b&c=d&e=f", "a", "")))
|
|
assert.Equal(t, "&a=b&e=f", string(QueryBuild("&a=b&c=d&e=f", "c", "")))
|
|
assert.Equal(t, "&a=b&c=d", string(QueryBuild("&a=b&c=d&e=f", "e", "")))
|
|
assert.Equal(t, "&a=b&c=d&e=f", string(QueryBuild("&a=b&c=d&e=f", "k", "")))
|
|
})
|
|
}
|
|
|
|
const queryNonASCII = " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" // all non-letter & non-number chars
|
|
|
|
func TestQueryEscape(t *testing.T) {
|
|
// this test is a reference for "urlQueryEscape" in JS
|
|
// Special case for space encoding:
|
|
// * RFC 3986: Uniform Resource Identifier (URI): %20
|
|
// * WHATWG HTML: application/x-www-form-urlencoded: +
|
|
// * JavaScript: encodeURIComponent() uses "%20". URLSearchParams uses "+"
|
|
// * Golang: QueryEscape uses "+"
|
|
expected := "+%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F%3A%3B%3C%3D%3E%3F%40%5B%5C%5D%5E_%60%7B%7C%7D~"
|
|
assert.Equal(t, expected, url.QueryEscape(queryNonASCII))
|
|
}
|
|
|
|
func TestPathEscape(t *testing.T) {
|
|
// this test is a reference for "pathEscape" in JS
|
|
expected := "%20%21%22%23$%25&%27%28%29%2A+%2C-.%2F:%3B%3C=%3E%3F@%5B%5C%5D%5E_%60%7B%7C%7D~"
|
|
assert.Equal(t, expected, url.PathEscape(queryNonASCII))
|
|
}
|