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 (
 | 
					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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										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 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}}" />
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue