Fix manifest encoding (#14114)

The previous URL encoding would encode spaces to '+' for the app name
which is incorrect. Use base64 encoding instead which does not have such
issues.
This commit is contained in:
silverwind 2020-12-23 20:09:54 +01:00 committed by GitHub
parent e0c753e770
commit cd5278a44c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 105 additions and 10 deletions

View file

@ -7,8 +7,8 @@ package setting
import ( import (
"encoding/base64" "encoding/base64"
"encoding/json"
"fmt" "fmt"
"html/template"
"io" "io"
"io/ioutil" "io/ioutil"
"math" "math"
@ -104,6 +104,7 @@ var (
GracefulHammerTime time.Duration GracefulHammerTime time.Duration
StartupTimeout time.Duration StartupTimeout time.Duration
StaticURLPrefix string StaticURLPrefix string
AbsoluteAssetURL string
SSH = struct { SSH = struct {
Disabled bool `ini:"DISABLE_SSH"` Disabled bool `ini:"DISABLE_SSH"`
@ -294,7 +295,7 @@ var (
CSRFCookieName = "_csrf" CSRFCookieName = "_csrf"
CSRFCookieHTTPOnly = true CSRFCookieHTTPOnly = true
ManifestData template.URL ManifestData string
// Mirror settings // Mirror settings
Mirror struct { Mirror struct {
@ -600,6 +601,11 @@ func NewContext() {
Domain = urlHostname Domain = urlHostname
} }
AbsoluteAssetURL = MakeAbsoluteAssetURL(AppURL, StaticURLPrefix)
manifestBytes := MakeManifestData(AppName, AppURL, AbsoluteAssetURL)
ManifestData = `application/json;base64,` + base64.StdEncoding.EncodeToString(manifestBytes)
var defaultLocalURL string var defaultLocalURL string
switch Protocol { switch Protocol {
case UnixSocket: case UnixSocket:
@ -645,8 +651,6 @@ func NewContext() {
LandingPageURL = LandingPageHome LandingPageURL = LandingPageHome
} }
ManifestData = makeManifestData()
if len(SSH.Domain) == 0 { if len(SSH.Domain) == 0 {
SSH.Domain = Domain SSH.Domain = Domain
} }
@ -1045,12 +1049,74 @@ func loadOrGenerateInternalToken(sec *ini.Section) string {
return token return token
} }
func makeManifestData() template.URL { // MakeAbsoluteAssetURL returns the absolute asset url prefix without a trailing slash
name := url.QueryEscape(AppName) func MakeAbsoluteAssetURL(appURL string, staticURLPrefix string) string {
prefix := url.QueryEscape(StaticURLPrefix) parsedPrefix, err := url.Parse(strings.TrimSuffix(staticURLPrefix, "/"))
subURL := url.QueryEscape(AppSubURL) + "/" if err != nil {
log.Fatal("Unable to parse STATIC_URL_PREFIX: %v", err)
}
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"}`) 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 bytes
} }
// NewServices initializes the services // NewServices initializes the services

View file

@ -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))
}

View file

@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="x-ua-compatible" content="ie=edge"> <meta http-equiv="x-ua-compatible" content="ie=edge">
<title>{{if .Title}}{{.Title | RenderEmojiPlain}} - {{end}} {{if .Repository.Name}}{{.Repository.Name}} - {{end}}{{AppName}} </title> <title>{{if .Title}}{{.Title | RenderEmojiPlain}} - {{end}} {{if .Repository.Name}}{{.Repository.Name}} - {{end}}{{AppName}} </title>
<link rel="manifest" href="{{.ManifestData}}"/> <link rel="manifest" href="data:{{.ManifestData}}"/>
<meta name="theme-color" content="{{ThemeColorMetaTag}}"> <meta name="theme-color" content="{{ThemeColorMetaTag}}">
<meta name="default-theme" content="{{DefaultTheme}}" /> <meta name="default-theme" content="{{DefaultTheme}}" />
<meta name="author" content="{{if .Repository}}{{.Owner.Name}}{{else}}{{MetaAuthor}}{{end}}" /> <meta name="author" content="{{if .Repository}}{{.Owner.Name}}{{else}}{{MetaAuthor}}{{end}}" />