Follow #29165. * Introduce JSONTemplate to help to render JSON templates * Introduce JSEscapeSafe for templates. Now only use `{{ ... | JSEscape}}` instead of `{{ ... | JSEscape | Safe}}` * Simplify "UserLocationMapURL" useagetags/v1.22.0-rc0
@@ -164,8 +164,8 @@ ifdef DEPS_PLAYWRIGHT | |||
endif | |||
SWAGGER_SPEC := templates/swagger/v1_json.tmpl | |||
SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|g | |||
SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|"basePath": "/api/v1"|g | |||
SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape}}/api/v1"|g | |||
SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape}}/api/v1"|"basePath": "/api/v1"|g | |||
SWAGGER_EXCLUDE := code.gitea.io/sdk | |||
SWAGGER_NEWLINE_COMMAND := -e '$$a\' | |||
@@ -90,6 +90,20 @@ func (ctx *Context) HTML(status int, name base.TplName) { | |||
} | |||
} | |||
// JSONTemplate renders the template as JSON response | |||
// keep in mind that the template is processed in HTML context, so JSON-things should be handled carefully, eg: by JSEscape | |||
func (ctx *Context) JSONTemplate(tmpl base.TplName) { | |||
t, err := ctx.Render.TemplateLookup(string(tmpl), nil) | |||
if err != nil { | |||
ctx.ServerError("unable to find template", err) | |||
return | |||
} | |||
ctx.Resp.Header().Set("Content-Type", "application/json") | |||
if err = t.Execute(ctx.Resp, ctx.Data); err != nil { | |||
ctx.ServerError("unable to execute template", err) | |||
} | |||
} | |||
// RenderToString renders the template content to a string | |||
func (ctx *Context) RenderToString(name base.TplName, data map[string]any) (string, error) { | |||
var buf strings.Builder |
@@ -38,7 +38,7 @@ func NewFuncMap() template.FuncMap { | |||
"Safe": Safe, | |||
"Escape": Escape, | |||
"QueryEscape": url.QueryEscape, | |||
"JSEscape": template.JSEscapeString, | |||
"JSEscape": JSEscapeSafe, | |||
"Str2html": Str2html, // TODO: rename it to SanitizeHTML | |||
"URLJoin": util.URLJoin, | |||
"DotEscape": DotEscape, | |||
@@ -211,6 +211,10 @@ func Escape(s any) template.HTML { | |||
panic(fmt.Sprintf("unexpected type %T", s)) | |||
} | |||
func JSEscapeSafe(s string) template.HTML { | |||
return template.HTML(template.JSEscapeString(s)) | |||
} | |||
func RenderEmojiPlain(s any) any { | |||
switch v := s.(type) { | |||
case string: |
@@ -52,3 +52,7 @@ func TestSubjectBodySeparator(t *testing.T) { | |||
"", | |||
"Insuficient\n--\nSeparators") | |||
} | |||
func TestJSEscapeSafe(t *testing.T) { | |||
assert.EqualValues(t, `\u0026\u003C\u003E\'\"`, JSEscapeSafe(`&<>'"`)) | |||
} |
@@ -6,9 +6,9 @@ | |||
// | |||
// This documentation describes the Gitea API. | |||
// | |||
// Schemes: http, https | |||
// Schemes: https, http | |||
// BasePath: /api/v1 | |||
// Version: {{AppVer | JSEscape | Safe}} | |||
// Version: {{AppVer | JSEscape}} | |||
// License: MIT http://opensource.org/licenses/MIT | |||
// | |||
// Consumes: |
@@ -579,16 +579,8 @@ func GrantApplicationOAuth(ctx *context.Context) { | |||
// OIDCWellKnown generates JSON so OIDC clients know Gitea's capabilities | |||
func OIDCWellKnown(ctx *context.Context) { | |||
t, err := ctx.Render.TemplateLookup("user/auth/oidc_wellknown", nil) | |||
if err != nil { | |||
ctx.ServerError("unable to find template", err) | |||
return | |||
} | |||
ctx.Resp.Header().Set("Content-Type", "application/json") | |||
ctx.Data["SigningKey"] = oauth2.DefaultSigningKey | |||
if err = t.Execute(ctx.Resp, ctx.Data); err != nil { | |||
ctx.ServerError("unable to execute template", err) | |||
} | |||
ctx.JSONTemplate("user/auth/oidc_wellknown") | |||
} | |||
// OIDCKeys generates the JSON Web Key Set |
@@ -4,6 +4,8 @@ | |||
package user | |||
import ( | |||
"net/url" | |||
"code.gitea.io/gitea/models/db" | |||
"code.gitea.io/gitea/models/organization" | |||
access_model "code.gitea.io/gitea/models/perm/access" | |||
@@ -36,7 +38,7 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) { | |||
ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID) | |||
ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail && ctx.ContextUser.Email != "" && ctx.IsSigned && !ctx.ContextUser.KeepEmailPrivate | |||
ctx.Data["UserLocationMapURL"] = setting.Service.UserLocationMapURL | |||
ctx.Data["ContextUserLocationMapURL"] = setting.Service.UserLocationMapURL + url.QueryEscape(ctx.ContextUser.Location) | |||
// Show OpenID URIs | |||
openIDs, err := user_model.GetUserOpenIDs(ctx, ctx.ContextUser.ID) |
@@ -4,22 +4,10 @@ | |||
package web | |||
import ( | |||
"code.gitea.io/gitea/modules/base" | |||
"code.gitea.io/gitea/modules/context" | |||
) | |||
// tplSwaggerV1Json swagger v1 json template | |||
const tplSwaggerV1Json base.TplName = "swagger/v1_json" | |||
// SwaggerV1Json render swagger v1 json | |||
func SwaggerV1Json(ctx *context.Context) { | |||
t, err := ctx.Render.TemplateLookup(string(tplSwaggerV1Json), nil) | |||
if err != nil { | |||
ctx.ServerError("unable to find template", err) | |||
return | |||
} | |||
ctx.Resp.Header().Set("Content-Type", "application/json") | |||
if err = t.Execute(ctx.Resp, ctx.Data); err != nil { | |||
ctx.ServerError("unable to execute template", err) | |||
} | |||
ctx.JSONTemplate("swagger/v1_json") | |||
} |
@@ -31,9 +31,8 @@ | |||
<li> | |||
{{svg "octicon-location"}} | |||
<span class="gt-f1">{{.ContextUser.Location}}</span> | |||
{{if .UserLocationMapURL}} | |||
{{/* We presume that the UserLocationMapURL is safe, as it is provided by the site administrator. */}} | |||
<a href="{{.UserLocationMapURL | Safe}}{{.ContextUser.Location | QueryEscape}}" rel="nofollow noreferrer" data-tooltip-content="{{ctx.Locale.Tr "user.show_on_map"}}"> | |||
{{if .ContextUserLocationMapURL}} | |||
<a href="{{.ContextUserLocationMapURL}}" rel="nofollow noreferrer" data-tooltip-content="{{ctx.Locale.Tr "user.show_on_map"}}"> | |||
{{svg "octicon-link-external"}} | |||
</a> | |||
{{end}} |
@@ -8,8 +8,8 @@ | |||
"text/html" | |||
], | |||
"schemes": [ | |||
"http", | |||
"https" | |||
"https", | |||
"http" | |||
], | |||
"swagger": "2.0", | |||
"info": { | |||
@@ -19,9 +19,9 @@ | |||
"name": "MIT", | |||
"url": "http://opensource.org/licenses/MIT" | |||
}, | |||
"version": "{{AppVer | JSEscape | Safe}}" | |||
"version": "{{AppVer | JSEscape}}" | |||
}, | |||
"basePath": "{{AppSubUrl | JSEscape | Safe}}/api/v1", | |||
"basePath": "{{AppSubUrl | JSEscape}}/api/v1", | |||
"paths": { | |||
"/activitypub/user-id/{user-id}": { | |||
"get": { |
@@ -1,16 +1,16 @@ | |||
{ | |||
"issuer": "{{AppUrl | JSEscape | Safe}}", | |||
"authorization_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/authorize", | |||
"token_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/access_token", | |||
"jwks_uri": "{{AppUrl | JSEscape | Safe}}login/oauth/keys", | |||
"userinfo_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/userinfo", | |||
"introspection_endpoint": "{{AppUrl | JSEscape | Safe}}login/oauth/introspect", | |||
"issuer": "{{AppUrl | JSEscape}}", | |||
"authorization_endpoint": "{{AppUrl | JSEscape}}login/oauth/authorize", | |||
"token_endpoint": "{{AppUrl | JSEscape}}login/oauth/access_token", | |||
"jwks_uri": "{{AppUrl | JSEscape}}login/oauth/keys", | |||
"userinfo_endpoint": "{{AppUrl | JSEscape}}login/oauth/userinfo", | |||
"introspection_endpoint": "{{AppUrl | JSEscape}}login/oauth/introspect", | |||
"response_types_supported": [ | |||
"code", | |||
"id_token" | |||
], | |||
"id_token_signing_alg_values_supported": [ | |||
"{{.SigningKey.SigningMethod.Alg | JSEscape | Safe}}" | |||
"{{.SigningKey.SigningMethod.Alg | JSEscape}}" | |||
], | |||
"subject_types_supported": [ | |||
"public" |