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:
		
							parent
							
								
									e0c753e770
								
							
						
					
					
						commit
						cd5278a44c
					
				
					 3 changed files with 105 additions and 10 deletions
				
			
		|  | @ -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) | ||||
| 	} | ||||
| 
 | ||||
| 	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
 | ||||
|  |  | |||
							
								
								
									
										29
									
								
								modules/setting/setting_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								modules/setting/setting_test.go
									
									
									
									
									
										Normal 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)) | ||||
| } | ||||
|  | @ -5,7 +5,7 @@ | |||
| 	<meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
| 	<meta http-equiv="x-ua-compatible" content="ie=edge"> | ||||
| 	<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="default-theme" content="{{DefaultTheme}}" /> | ||||
| 	<meta name="author" content="{{if .Repository}}{{.Owner.Name}}{{else}}{{MetaAuthor}}{{end}}" /> | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue