diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 290ec94c4..8a4d7acc4 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -7,8 +7,8 @@ package setting import ( "encoding/base64" + "encoding/json" "fmt" - "html/template" "io" "io/ioutil" "math" @@ -104,6 +104,7 @@ var ( GracefulHammerTime time.Duration StartupTimeout time.Duration StaticURLPrefix string + AbsoluteAssetURL string SSH = struct { Disabled bool `ini:"DISABLE_SSH"` @@ -294,7 +295,7 @@ var ( CSRFCookieName = "_csrf" CSRFCookieHTTPOnly = true - ManifestData template.URL + ManifestData string // Mirror settings Mirror struct { @@ -600,6 +601,11 @@ func NewContext() { Domain = urlHostname } + AbsoluteAssetURL = MakeAbsoluteAssetURL(AppURL, StaticURLPrefix) + + manifestBytes := MakeManifestData(AppName, AppURL, AbsoluteAssetURL) + ManifestData = `application/json;base64,` + base64.StdEncoding.EncodeToString(manifestBytes) + var defaultLocalURL string switch Protocol { case UnixSocket: @@ -645,8 +651,6 @@ func NewContext() { LandingPageURL = LandingPageHome } - ManifestData = makeManifestData() - if len(SSH.Domain) == 0 { SSH.Domain = Domain } @@ -1045,12 +1049,74 @@ func loadOrGenerateInternalToken(sec *ini.Section) string { return token } -func makeManifestData() template.URL { - name := url.QueryEscape(AppName) - prefix := url.QueryEscape(StaticURLPrefix) - subURL := url.QueryEscape(AppSubURL) + "/" +// MakeAbsoluteAssetURL returns the absolute asset url prefix without a trailing slash +func MakeAbsoluteAssetURL(appURL string, staticURLPrefix string) string { + parsedPrefix, err := url.Parse(strings.TrimSuffix(staticURLPrefix, "/")) + if err != nil { + log.Fatal("Unable to parse STATIC_URL_PREFIX: %v", err) + } + + if err == nil && parsedPrefix.Hostname() == "" { + if staticURLPrefix == "" { + return strings.TrimSuffix(appURL, "/") + } + + // StaticURLPrefix is just a path + return strings.TrimSuffix(appURL, "/") + strings.TrimSuffix(staticURLPrefix, "/") + } + + return strings.TrimSuffix(staticURLPrefix, "/") +} + +// MakeManifestData generates web app manifest JSON +func MakeManifestData(appName string, appURL string, absoluteAssetURL string) []byte { + type manifestIcon struct { + Src string `json:"src"` + Type string `json:"type"` + Sizes string `json:"sizes"` + } + + type manifestJSON struct { + Name string `json:"name"` + ShortName string `json:"short_name"` + StartURL string `json:"start_url"` + Icons []manifestIcon `json:"icons"` + } + + bytes, err := json.Marshal(&manifestJSON{ + Name: appName, + ShortName: appName, + StartURL: appURL, + Icons: []manifestIcon{ + { + Src: absoluteAssetURL + "/img/logo-lg.png", + Type: "image/png", + Sizes: "880x880", + }, + { + Src: absoluteAssetURL + "/img/logo-512.png", + Type: "image/png", + Sizes: "512x512", + }, + { + Src: absoluteAssetURL + "/img/logo-192.png", + Type: "image/png", + Sizes: "192x192", + }, + { + Src: absoluteAssetURL + "/img/logo-sm.png", + Type: "image/png", + Sizes: "120x120", + }, + }, + }) + + if err != nil { + log.Error("unable to marshal manifest JSON. Error: %v", err) + return make([]byte, 0) + } - return template.URL(`data:application/json,{"short_name":"` + name + `","name":"` + name + `","icons":[{"src":"` + prefix + `/img/logo-lg.png","type":"image/png","sizes":"880x880"},{"src":"` + prefix + `/img/logo-sm.png","type":"image/png","sizes":"120x120"},{"src":"` + prefix + `/img/logo-512.png","type":"image/png","sizes":"512x512"},{"src":"` + prefix + `/img/logo-192.png","type":"image/png","sizes":"192x192"}],"start_url":"` + subURL + `","scope":"` + subURL + `","background_color":"%23FAFAFA","display":"standalone"}`) + return bytes } // NewServices initializes the services diff --git a/modules/setting/setting_test.go b/modules/setting/setting_test.go new file mode 100644 index 000000000..f12fd8843 --- /dev/null +++ b/modules/setting/setting_test.go @@ -0,0 +1,29 @@ +// Copyright 2020 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. + +package setting + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMakeAbsoluteAssetURL(t *testing.T) { + assert.Equal(t, "https://localhost:2345", MakeAbsoluteAssetURL("https://localhost:1234", "https://localhost:2345")) + assert.Equal(t, "https://localhost:2345", MakeAbsoluteAssetURL("https://localhost:1234/", "https://localhost:2345")) + assert.Equal(t, "https://localhost:2345", MakeAbsoluteAssetURL("https://localhost:1234/", "https://localhost:2345/")) + assert.Equal(t, "https://localhost:1234/foo", MakeAbsoluteAssetURL("https://localhost:1234", "/foo")) + assert.Equal(t, "https://localhost:1234/foo", MakeAbsoluteAssetURL("https://localhost:1234/", "/foo")) + assert.Equal(t, "https://localhost:1234/foo", MakeAbsoluteAssetURL("https://localhost:1234/", "/foo/")) + assert.Equal(t, "https://localhost:1234/foo/bar", MakeAbsoluteAssetURL("https://localhost:1234/foo", "/bar")) + assert.Equal(t, "https://localhost:1234/foo/bar", MakeAbsoluteAssetURL("https://localhost:1234/foo/", "/bar")) + assert.Equal(t, "https://localhost:1234/foo/bar", MakeAbsoluteAssetURL("https://localhost:1234/foo/", "/bar/")) +} + +func TestMakeManifestData(t *testing.T) { + jsonBytes := MakeManifestData(`Example App '\"`, "https://example.com", "https://example.com/foo/bar") + assert.True(t, json.Valid(jsonBytes)) +} diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index c47fd08c1..32660df6b 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -5,7 +5,7 @@ {{if .Title}}{{.Title | RenderEmojiPlain}} - {{end}} {{if .Repository.Name}}{{.Repository.Name}} - {{end}}{{AppName}} - +