diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index e62747c7241..4fa9466d19a 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -317,7 +317,7 @@ func TestRender_email(t *testing.T) { func TestRender_emoji(t *testing.T) { setting.AppURL = markup.TestAppURL - setting.StaticURLPrefix = markup.TestAppURL + setting.StaticURLPrefix = strings.TrimSuffix(markup.TestAppURL, "/") test := func(input, expected string) { expected = strings.ReplaceAll(expected, "&", "&") @@ -500,7 +500,7 @@ func Test_ParseClusterFuzz(t *testing.T) { } func TestPostProcess(t *testing.T) { - setting.StaticURLPrefix = markup.TestAppURL // can't run standalone + setting.StaticURLPrefix = strings.TrimSuffix(markup.TestAppURL, "/") // can't run standalone defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)() test := func(input, expected string) { diff --git a/modules/setting/server.go b/modules/setting/server.go index 1085e052a3e..dc58e43c435 100644 --- a/modules/setting/server.go +++ b/modules/setting/server.go @@ -4,7 +4,6 @@ package setting import ( - "encoding/base64" "net" "net/url" "os" @@ -13,7 +12,6 @@ import ( "strings" "time" - "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" ) @@ -112,72 +110,9 @@ var ( StartupTimeout time.Duration PerWriteTimeout = 30 * time.Second PerWritePerKbTimeout = 10 * time.Second - StaticURLPrefix string - AbsoluteAssetURL string - - ManifestData string + StaticURLPrefix string // no trailing slash, defaults to AppSubURL, the URL can be relative or absolute ) -// MakeManifestData generates web app manifest JSON -func MakeManifestData(appName, appURL, 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 + "/assets/img/logo.png", - Type: "image/png", - Sizes: "512x512", - }, - { - Src: absoluteAssetURL + "/assets/img/logo.svg", - Type: "image/svg+xml", - Sizes: "512x512", - }, - }, - }) - if err != nil { - log.Error("unable to marshal manifest JSON. Error: %v", err) - return make([]byte, 0) - } - - return bytes -} - -// MakeAbsoluteAssetURL returns the absolute asset url prefix without a trailing slash -func MakeAbsoluteAssetURL(appURL *url.URL, 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.String(), "/") - } - - // StaticURLPrefix is just a path - appHostURL := &url.URL{Scheme: appURL.Scheme, Host: appURL.Host} - return appHostURL.String() + "/" + strings.Trim(staticURLPrefix, "/") - } - - return strings.TrimSuffix(staticURLPrefix, "/") -} - func loadServerFrom(rootCfg ConfigProvider) { sec := rootCfg.Section("server") AppName = rootCfg.Section("").Key("APP_NAME").MustString("Gitea: Git with a cup of tea") @@ -313,10 +248,6 @@ func loadServerFrom(rootCfg ConfigProvider) { 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 HTTPUnix: diff --git a/modules/setting/setting_test.go b/modules/setting/setting_test.go deleted file mode 100644 index 13575f52a6e..00000000000 --- a/modules/setting/setting_test.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2020 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package setting - -import ( - "net/url" - "testing" - - "code.gitea.io/gitea/modules/json" - - "github.com/stretchr/testify/assert" -) - -func TestMakeAbsoluteAssetURL(t *testing.T) { - appURL1, _ := url.Parse("https://localhost:1234") - appURL2, _ := url.Parse("https://localhost:1234/") - appURLSub1, _ := url.Parse("https://localhost:1234/foo") - appURLSub2, _ := url.Parse("https://localhost:1234/foo/") - - // static URL is an absolute URL, so should be used - assert.Equal(t, "https://localhost:2345", MakeAbsoluteAssetURL(appURL1, "https://localhost:2345")) - assert.Equal(t, "https://localhost:2345", MakeAbsoluteAssetURL(appURL1, "https://localhost:2345/")) - - assert.Equal(t, "https://localhost:1234/foo", MakeAbsoluteAssetURL(appURL1, "/foo")) - assert.Equal(t, "https://localhost:1234/foo", MakeAbsoluteAssetURL(appURL2, "/foo")) - assert.Equal(t, "https://localhost:1234/foo", MakeAbsoluteAssetURL(appURL1, "/foo/")) - - assert.Equal(t, "https://localhost:1234/foo", MakeAbsoluteAssetURL(appURLSub1, "/foo")) - assert.Equal(t, "https://localhost:1234/foo", MakeAbsoluteAssetURL(appURLSub2, "/foo")) - assert.Equal(t, "https://localhost:1234/foo", MakeAbsoluteAssetURL(appURLSub1, "/foo/")) - - assert.Equal(t, "https://localhost:1234/bar", MakeAbsoluteAssetURL(appURLSub1, "/bar")) - assert.Equal(t, "https://localhost:1234/bar", MakeAbsoluteAssetURL(appURLSub2, "/bar")) - assert.Equal(t, "https://localhost:1234/bar", MakeAbsoluteAssetURL(appURLSub1, "/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/routers/web/misc/misc.go b/routers/web/misc/misc.go index a50d9130ac2..0b939ee4352 100644 --- a/routers/web/misc/misc.go +++ b/routers/web/misc/misc.go @@ -7,9 +7,12 @@ import ( "net/http" "path" "strconv" + "strings" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/httpcache" + "code.gitea.io/gitea/modules/httplib" + "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -17,6 +20,29 @@ import ( "code.gitea.io/gitea/services/context" ) +func SiteManifest(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/manifest+json") + if httpcache.HandleGenericETagPublicCache(req, w, "", &setting.AppStartTime) { + return + } + if req.Method == http.MethodHead { + return + } + + ctx := req.Context() + absoluteAssetURL := strings.TrimSuffix(httplib.MakeAbsoluteURL(ctx, setting.StaticURLPrefix), "/") + manifest := map[string]any{ + "name": setting.AppName, + "short_name": setting.AppName, + "start_url": httplib.GuessCurrentAppURL(ctx), + "icons": []map[string]string{ + {"src": absoluteAssetURL + "/assets/img/logo.png", "type": "image/png", "sizes": "512x512"}, + {"src": absoluteAssetURL + "/assets/img/logo.svg", "type": "image/svg+xml", "sizes": "512x512"}, + }, + } + _ = json.NewEncoder(w).Encode(manifest) +} + func SSHInfo(rw http.ResponseWriter, req *http.Request) { if !git.DefaultFeatures().SupportProcReceive { rw.WriteHeader(http.StatusNotFound) diff --git a/routers/web/web.go b/routers/web/web.go index 330a5a9c244..15f8d4886b4 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -260,6 +260,7 @@ func Routes() *web.Router { routes.BeforeRouting(chi_middleware.GetHead) routes.Head("/", misc.DummyOK) // for health check - doesn't need to be passed through gzip handler + routes.Methods("GET, HEAD", "/assets/site-manifest.json", misc.SiteManifest) routes.Methods("GET, HEAD, OPTIONS", "/assets/*", routing.MarkLogLevelTrace, public.AssetsCors(), public.FileHandlerFunc()) routes.Methods("GET, HEAD", "/avatars/*", avatarStorageHandler(setting.Avatar.Storage, "avatars", storage.Avatars)) routes.Methods("GET, HEAD", "/repo-avatars/*", avatarStorageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars)) diff --git a/services/context/context.go b/services/context/context.go index e4c2c1d6fb3..8d286f67385 100644 --- a/services/context/context.go +++ b/services/context/context.go @@ -205,7 +205,6 @@ func Contexter() func(next http.Handler) http.Handler { ctx.Data["DisableStars"] = setting.Repository.DisableStars ctx.Data["EnableActions"] = setting.Actions.Enabled && !unit.TypeActions.UnitGlobalDisabled() - ctx.Data["ManifestData"] = setting.ManifestData ctx.Data["AllLangs"] = translation.AllLangs() next.ServeHTTP(ctx.Resp, ctx.Req) diff --git a/services/context/context_template.go b/services/context/context_template.go index 2c8fde6870d..b63aaf4c3c3 100644 --- a/services/context/context_template.go +++ b/services/context/context_template.go @@ -148,8 +148,7 @@ func (c TemplateContext) HeadMetaContentSecurityPolicy() template.HTML { // * Maybe this approach should be avoided, don't make the config system too complex, just let users use A return template.HTML(`