diff options
Diffstat (limited to 'web')
-rw-r--r-- | web/context.go | 4 | ||||
-rw-r--r-- | web/handlers.go | 4 | ||||
-rw-r--r-- | web/helpers_test.go | 15 | ||||
-rw-r--r-- | web/static.go | 18 | ||||
-rw-r--r-- | web/subpath.go | 130 | ||||
-rw-r--r-- | web/subpath_test.go | 103 | ||||
-rw-r--r-- | web/web.go | 11 |
7 files changed, 26 insertions, 259 deletions
diff --git a/web/context.go b/web/context.go index 7e4318233..5eb8c94d5 100644 --- a/web/context.go +++ b/web/context.go @@ -5,6 +5,7 @@ package web import ( "net/http" + "path" "regexp" "strings" @@ -126,7 +127,8 @@ func (c *Context) MfaRequired() { } // Special case to let user get themself - if c.Path == "/api/v4/users/me" { + subpath, _ := utils.GetSubpathFromConfig(c.App.Config()) + if c.Path == path.Join(subpath, "/api/v4/users/me") { return } diff --git a/web/handlers.go b/web/handlers.go index fe77241e3..c12466fee 100644 --- a/web/handlers.go +++ b/web/handlers.go @@ -157,11 +157,11 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { c.Err.IsOAuth = false } - if IsApiCall(r) || len(r.Header.Get("X-Mobile-App")) > 0 { + if IsApiCall(c.App, r) || len(r.Header.Get("X-Mobile-App")) > 0 { w.WriteHeader(c.Err.StatusCode) w.Write([]byte(c.Err.ToJson())) } else { - utils.RenderWebAppError(w, r, c.Err, c.App.AsymmetricSigningKey()) + utils.RenderWebAppError(c.App.Config(), w, r, c.Err, c.App.AsymmetricSigningKey()) } if c.App.Metrics != nil { diff --git a/web/helpers_test.go b/web/helpers_test.go deleted file mode 100644 index 4e6a7ff6a..000000000 --- a/web/helpers_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package web_test - -const baseRootHtml = `<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <meta http-equiv=Content-Security-Policy content="script-src 'self' cdn.segment.com/analytics.js/ 'unsafe-eval'"> <meta http-equiv=X-UA-Compatible content="IE=edge"> <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"> <meta name=robots content="noindex, nofollow"> <meta name=referrer content=no-referrer> <title>Mattermost</title> <meta name=apple-mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-status-bar-style content=default> <meta name=mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-title content=Mattermost> <meta name=application-name content=Mattermost> <meta name=format-detection content="telephone=no"> <link rel=apple-touch-icon sizes=57x57 href=/static/files/78b7e73b41b8731ce2c41c870ecc8886.png> <link rel=apple-touch-icon sizes=60x60 href=/static/files/51d00ffd13afb6d74fd8f6dfdeef768a.png> <link rel=apple-touch-icon sizes=72x72 href=/static/files/23645596f8f78f017bd4d457abb855c4.png> <link rel=apple-touch-icon sizes=76x76 href=/static/files/26e9d72f472663a00b4b206149459fab.png> <link rel=apple-touch-icon sizes=144x144 href=/static/files/7bd91659bf3fc8c68fcd45fc1db9c630.png> <link rel=apple-touch-icon sizes=120x120 href=/static/files/fa69ffe11eb334aaef5aece8d848ca62.png> <link rel=apple-touch-icon sizes=152x152 href=/static/files/f046777feb6ab12fc43b8f9908b1db35.png> <link rel=icon type=image/png sizes=16x16 href=/static/files/02b96247d275680adaaabf01c71c571d.png> <link rel=icon type=image/png sizes=32x32 href=/static/files/1d9020f201a6762421cab8d30624fdd8.png> <link rel=icon type=image/png sizes=96x96 href=/static/files/fe23af39ae98d77dc26ae8586565970f.png> <link rel=icon type=image/png sizes=192x192 href=/static/files/d7ff68a7675f84337cc154c3d4abe713.png> <link rel=manifest href=/static/files/a985ad72552ad069537d6eea81e719c7.json> <link rel=stylesheet class=code_theme> <style>.error-screen{font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding-top:50px;max-width:750px;font-size:14px;color:#333;margin:auto;display:none;line-height:1.5}.error-screen h2{font-size:30px;font-weight:400;line-height:1.2}.error-screen ul{padding-left:15px;line-height:1.7;margin-top:0;margin-bottom:10px}.error-screen hr{color:#ddd;margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.error-screen-visible{display:block}</style> <link href="/static/main.364fd054d7a6d741efc6.css" rel="stylesheet"><script type="text/javascript" src="/static/main.e49599ac425584ffead5.js"></script></head> <body class=font--open_sans> <div id=root> <div class=error-screen> <h2>Cannot connect to Mattermost</h2> <hr/> <p>We're having trouble connecting to Mattermost. If refreshing this page (Ctrl+R or Command+R) does not work, please verify that your computer is connected to the internet.</p> <br/> </div> <div class=loading-screen style=position:relative> <div class=loading__content> <div class="round round-1"></div> <div class="round round-2"></div> <div class="round round-3"></div> </div> </div> </div> <noscript> To use Mattermost, please enable JavaScript. </noscript> </body> </html>` - -const baseCss = `@font-face{font-family:FontAwesome;src:url(/static/files/674f50d287a8c48dc19ba404d20fe713.eot);src:url(/static/files/674f50d287a8c48dc19ba404d20fe713.eot?#iefix&v=4.7.0) format("embedded-opentype"),url(/static/files/af7ae505a9eed503f8b8e6982036873e.woff2) format("woff2"),url(/static/files/fee66e712a8a08eef5805a46892932ad.woff) format("woff"),url(/static/files/b06871f281fee6b241d60582ae9369b9.ttf) format("truetype"),url(/static/files/677433a0892aaed7b7d2628c313c9775.svg#fontawesomeregular) format("svg");font-weight:400;font-style:normal}` - -const subpathRootHtml = `<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <meta http-equiv=Content-Security-Policy content="script-src 'self' cdn.segment.com/analytics.js/ 'unsafe-eval' 'sha256-tPOjw+tkVs9axL78ZwGtYl975dtyPHB6LYKAO2R3gR4='"> <meta http-equiv=X-UA-Compatible content="IE=edge"> <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"> <meta name=robots content="noindex, nofollow"> <meta name=referrer content=no-referrer> <title>Mattermost</title> <meta name=apple-mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-status-bar-style content=default> <meta name=mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-title content=Mattermost> <meta name=application-name content=Mattermost> <meta name=format-detection content="telephone=no"> <link rel=apple-touch-icon sizes=57x57 href=/subpath/static/files/78b7e73b41b8731ce2c41c870ecc8886.png> <link rel=apple-touch-icon sizes=60x60 href=/subpath/static/files/51d00ffd13afb6d74fd8f6dfdeef768a.png> <link rel=apple-touch-icon sizes=72x72 href=/subpath/static/files/23645596f8f78f017bd4d457abb855c4.png> <link rel=apple-touch-icon sizes=76x76 href=/subpath/static/files/26e9d72f472663a00b4b206149459fab.png> <link rel=apple-touch-icon sizes=144x144 href=/subpath/static/files/7bd91659bf3fc8c68fcd45fc1db9c630.png> <link rel=apple-touch-icon sizes=120x120 href=/subpath/static/files/fa69ffe11eb334aaef5aece8d848ca62.png> <link rel=apple-touch-icon sizes=152x152 href=/subpath/static/files/f046777feb6ab12fc43b8f9908b1db35.png> <link rel=icon type=image/png sizes=16x16 href=/subpath/static/files/02b96247d275680adaaabf01c71c571d.png> <link rel=icon type=image/png sizes=32x32 href=/subpath/static/files/1d9020f201a6762421cab8d30624fdd8.png> <link rel=icon type=image/png sizes=96x96 href=/subpath/static/files/fe23af39ae98d77dc26ae8586565970f.png> <link rel=icon type=image/png sizes=192x192 href=/subpath/static/files/d7ff68a7675f84337cc154c3d4abe713.png> <link rel=manifest href=/subpath/static/files/a985ad72552ad069537d6eea81e719c7.json> <link rel=stylesheet class=code_theme> <style>.error-screen{font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding-top:50px;max-width:750px;font-size:14px;color:#333;margin:auto;display:none;line-height:1.5}.error-screen h2{font-size:30px;font-weight:400;line-height:1.2}.error-screen ul{padding-left:15px;line-height:1.7;margin-top:0;margin-bottom:10px}.error-screen hr{color:#ddd;margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.error-screen-visible{display:block}</style><script>window.publicPath='/subpath/static/'</script> <link href="/subpath/static/main.364fd054d7a6d741efc6.css" rel="stylesheet"><script type="text/javascript" src="/subpath/static/main.e49599ac425584ffead5.js"></script></head> <body class=font--open_sans> <div id=root> <div class=error-screen> <h2>Cannot connect to Mattermost</h2> <hr/> <p>We're having trouble connecting to Mattermost. If refreshing this page (Ctrl+R or Command+R) does not work, please verify that your computer is connected to the internet.</p> <br/> </div> <div class=loading-screen style=position:relative> <div class=loading__content> <div class="round round-1"></div> <div class="round round-2"></div> <div class="round round-3"></div> </div> </div> </div> <noscript> To use Mattermost, please enable JavaScript. </noscript> </body> </html>` - -const subpathCss = `@font-face{font-family:FontAwesome;src:url(/subpath/static/files/674f50d287a8c48dc19ba404d20fe713.eot);src:url(/subpath/static/files/674f50d287a8c48dc19ba404d20fe713.eot?#iefix&v=4.7.0) format("embedded-opentype"),url(/subpath/static/files/af7ae505a9eed503f8b8e6982036873e.woff2) format("woff2"),url(/subpath/static/files/fee66e712a8a08eef5805a46892932ad.woff) format("woff"),url(/subpath/static/files/b06871f281fee6b241d60582ae9369b9.ttf) format("truetype"),url(/subpath/static/files/677433a0892aaed7b7d2628c313c9775.svg#fontawesomeregular) format("svg");font-weight:400;font-style:normal}` - -const newSubpathRootHtml = `<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <meta http-equiv=Content-Security-Policy content="script-src 'self' cdn.segment.com/analytics.js/ 'unsafe-eval' 'sha256-mbRaPRRpWz6MNkX9SyXWMJ8XnWV4w/DoqK2M0ryUAvc='"> <meta http-equiv=X-UA-Compatible content="IE=edge"> <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"> <meta name=robots content="noindex, nofollow"> <meta name=referrer content=no-referrer> <title>Mattermost</title> <meta name=apple-mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-status-bar-style content=default> <meta name=mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-title content=Mattermost> <meta name=application-name content=Mattermost> <meta name=format-detection content="telephone=no"> <link rel=apple-touch-icon sizes=57x57 href=/nested/subpath/static/files/78b7e73b41b8731ce2c41c870ecc8886.png> <link rel=apple-touch-icon sizes=60x60 href=/nested/subpath/static/files/51d00ffd13afb6d74fd8f6dfdeef768a.png> <link rel=apple-touch-icon sizes=72x72 href=/nested/subpath/static/files/23645596f8f78f017bd4d457abb855c4.png> <link rel=apple-touch-icon sizes=76x76 href=/nested/subpath/static/files/26e9d72f472663a00b4b206149459fab.png> <link rel=apple-touch-icon sizes=144x144 href=/nested/subpath/static/files/7bd91659bf3fc8c68fcd45fc1db9c630.png> <link rel=apple-touch-icon sizes=120x120 href=/nested/subpath/static/files/fa69ffe11eb334aaef5aece8d848ca62.png> <link rel=apple-touch-icon sizes=152x152 href=/nested/subpath/static/files/f046777feb6ab12fc43b8f9908b1db35.png> <link rel=icon type=image/png sizes=16x16 href=/nested/subpath/static/files/02b96247d275680adaaabf01c71c571d.png> <link rel=icon type=image/png sizes=32x32 href=/nested/subpath/static/files/1d9020f201a6762421cab8d30624fdd8.png> <link rel=icon type=image/png sizes=96x96 href=/nested/subpath/static/files/fe23af39ae98d77dc26ae8586565970f.png> <link rel=icon type=image/png sizes=192x192 href=/nested/subpath/static/files/d7ff68a7675f84337cc154c3d4abe713.png> <link rel=manifest href=/nested/subpath/static/files/a985ad72552ad069537d6eea81e719c7.json> <link rel=stylesheet class=code_theme> <style>.error-screen{font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding-top:50px;max-width:750px;font-size:14px;color:#333;margin:auto;display:none;line-height:1.5}.error-screen h2{font-size:30px;font-weight:400;line-height:1.2}.error-screen ul{padding-left:15px;line-height:1.7;margin-top:0;margin-bottom:10px}.error-screen hr{color:#ddd;margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.error-screen-visible{display:block}</style><script>window.publicPath='/nested/subpath/static/'</script> <link href="/nested/subpath/static/main.364fd054d7a6d741efc6.css" rel="stylesheet"><script type="text/javascript" src="/nested/subpath/static/main.e49599ac425584ffead5.js"></script></head> <body class=font--open_sans> <div id=root> <div class=error-screen> <h2>Cannot connect to Mattermost</h2> <hr/> <p>We're having trouble connecting to Mattermost. If refreshing this page (Ctrl+R or Command+R) does not work, please verify that your computer is connected to the internet.</p> <br/> </div> <div class=loading-screen style=position:relative> <div class=loading__content> <div class="round round-1"></div> <div class="round round-2"></div> <div class="round round-3"></div> </div> </div> </div> <noscript> To use Mattermost, please enable JavaScript. </noscript> </body> </html>` - -const newSubpathCss = `@font-face{font-family:FontAwesome;src:url(/nested/subpath/static/files/674f50d287a8c48dc19ba404d20fe713.eot);src:url(/nested/subpath/static/files/674f50d287a8c48dc19ba404d20fe713.eot?#iefix&v=4.7.0) format("embedded-opentype"),url(/nested/subpath/static/files/af7ae505a9eed503f8b8e6982036873e.woff2) format("woff2"),url(/nested/subpath/static/files/fee66e712a8a08eef5805a46892932ad.woff) format("woff"),url(/nested/subpath/static/files/b06871f281fee6b241d60582ae9369b9.ttf) format("truetype"),url(/nested/subpath/static/files/677433a0892aaed7b7d2628c313c9775.svg#fontawesomeregular) format("svg");font-weight:400;font-style:normal}` - -const resetRootHtml = `<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <meta http-equiv=Content-Security-Policy content="script-src 'self' cdn.segment.com/analytics.js/ 'unsafe-eval' 'sha256-VFw7U/t/OI+I9YMja3c2GDwEQbnlOq/L5+GealgesK8='"> <meta http-equiv=X-UA-Compatible content="IE=edge"> <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"> <meta name=robots content="noindex, nofollow"> <meta name=referrer content=no-referrer> <title>Mattermost</title> <meta name=apple-mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-status-bar-style content=default> <meta name=mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-title content=Mattermost> <meta name=application-name content=Mattermost> <meta name=format-detection content="telephone=no"> <link rel=apple-touch-icon sizes=57x57 href=/static/files/78b7e73b41b8731ce2c41c870ecc8886.png> <link rel=apple-touch-icon sizes=60x60 href=/static/files/51d00ffd13afb6d74fd8f6dfdeef768a.png> <link rel=apple-touch-icon sizes=72x72 href=/static/files/23645596f8f78f017bd4d457abb855c4.png> <link rel=apple-touch-icon sizes=76x76 href=/static/files/26e9d72f472663a00b4b206149459fab.png> <link rel=apple-touch-icon sizes=144x144 href=/static/files/7bd91659bf3fc8c68fcd45fc1db9c630.png> <link rel=apple-touch-icon sizes=120x120 href=/static/files/fa69ffe11eb334aaef5aece8d848ca62.png> <link rel=apple-touch-icon sizes=152x152 href=/static/files/f046777feb6ab12fc43b8f9908b1db35.png> <link rel=icon type=image/png sizes=16x16 href=/static/files/02b96247d275680adaaabf01c71c571d.png> <link rel=icon type=image/png sizes=32x32 href=/static/files/1d9020f201a6762421cab8d30624fdd8.png> <link rel=icon type=image/png sizes=96x96 href=/static/files/fe23af39ae98d77dc26ae8586565970f.png> <link rel=icon type=image/png sizes=192x192 href=/static/files/d7ff68a7675f84337cc154c3d4abe713.png> <link rel=manifest href=/static/files/a985ad72552ad069537d6eea81e719c7.json> <link rel=stylesheet class=code_theme> <style>.error-screen{font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding-top:50px;max-width:750px;font-size:14px;color:#333;margin:auto;display:none;line-height:1.5}.error-screen h2{font-size:30px;font-weight:400;line-height:1.2}.error-screen ul{padding-left:15px;line-height:1.7;margin-top:0;margin-bottom:10px}.error-screen hr{color:#ddd;margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.error-screen-visible{display:block}</style><script>window.publicPath='/static/'</script> <link href="/static/main.364fd054d7a6d741efc6.css" rel="stylesheet"><script type="text/javascript" src="/static/main.e49599ac425584ffead5.js"></script></head> <body class=font--open_sans> <div id=root> <div class=error-screen> <h2>Cannot connect to Mattermost</h2> <hr/> <p>We're having trouble connecting to Mattermost. If refreshing this page (Ctrl+R or Command+R) does not work, please verify that your computer is connected to the internet.</p> <br/> </div> <div class=loading-screen style=position:relative> <div class=loading__content> <div class="round round-1"></div> <div class="round round-2"></div> <div class="round round-3"></div> </div> </div> </div> <noscript> To use Mattermost, please enable JavaScript. </noscript> </body> </html>` diff --git a/web/static.go b/web/static.go index 1f76b2725..08da9e95e 100644 --- a/web/static.go +++ b/web/static.go @@ -6,6 +6,7 @@ package web import ( "fmt" "net/http" + "path" "path/filepath" "strings" @@ -18,13 +19,15 @@ import ( func (w *Web) InitStatic() { if *w.App.Config().ServiceSettings.WebserverMode != "disabled" { - UpdateAssetsSubpathFromConfig(w.App.Config()) + utils.UpdateAssetsSubpathFromConfig(w.App.Config()) staticDir, _ := utils.FindDir(model.CLIENT_DIR) mlog.Debug(fmt.Sprintf("Using client directory at %v", staticDir)) - staticHandler := staticHandler(http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir)))) - pluginHandler := pluginHandler(w.App.Config, http.StripPrefix("/static/plugins/", http.FileServer(http.Dir(*w.App.Config().PluginSettings.ClientDirectory)))) + subpath, _ := utils.GetSubpathFromConfig(w.App.Config()) + + staticHandler := staticHandler(http.StripPrefix(path.Join(subpath, "static"), http.FileServer(http.Dir(staticDir)))) + pluginHandler := pluginHandler(w.App.Config, http.StripPrefix(path.Join(subpath, "plugins"), http.FileServer(http.Dir(*w.App.Config().PluginSettings.ClientDirectory)))) if *w.App.Config().ServiceSettings.WebserverMode == "gzip" { staticHandler = gziphandler.GzipHandler(staticHandler) @@ -34,6 +37,13 @@ func (w *Web) InitStatic() { w.MainRouter.PathPrefix("/static/plugins/").Handler(pluginHandler) w.MainRouter.PathPrefix("/static/").Handler(staticHandler) w.MainRouter.Handle("/{anything:.*}", w.NewStaticHandler(root)).Methods("GET") + + // When a subpath is defined, it's necessary to handle redirects without a + // trailing slash. We don't want to use StrictSlash on the w.MainRouter and affect + // all routes, just /subpath -> /subpath/. + w.MainRouter.HandleFunc("", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, r.URL.String()+"/", http.StatusFound) + })) } } @@ -48,7 +58,7 @@ func root(c *Context, w http.ResponseWriter, r *http.Request) { return } - if IsApiCall(r) { + if IsApiCall(c.App, r) { Handle404(c.App, w, r) return } diff --git a/web/subpath.go b/web/subpath.go deleted file mode 100644 index 1bd7412c9..000000000 --- a/web/subpath.go +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package web - -import ( - "crypto/sha256" - "encoding/base64" - "fmt" - "io/ioutil" - "net/url" - "os" - "path" - "path/filepath" - "regexp" - "strings" - - "github.com/pkg/errors" - - "github.com/mattermost/mattermost-server/mlog" - "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" -) - -// UpdateAssetsSubpath rewrites assets in the /client directory to assume the application is hosted -// at the given subpath instead of at the root. No changes are written unless necessary. -func UpdateAssetsSubpath(subpath string) error { - if subpath == "" { - subpath = "/" - } - - staticDir, found := utils.FindDir(model.CLIENT_DIR) - if !found { - return errors.New("failed to find client dir") - } - - staticDir, err := filepath.EvalSymlinks(staticDir) - if err != nil { - return errors.Wrapf(err, "failed to resolve symlinks to %s", staticDir) - } - - rootHtmlPath := filepath.Join(staticDir, "root.html") - oldRootHtml, err := ioutil.ReadFile(rootHtmlPath) - if err != nil { - return errors.Wrap(err, "failed to open root.html") - } - - pathToReplace := "/static/" - newPath := path.Join(subpath, "static") + "/" - - // Determine if a previous subpath had already been rewritten into the assets. - reWebpackPublicPathScript := regexp.MustCompile("window.publicPath='([^']+)'") - alreadyRewritten := false - if matches := reWebpackPublicPathScript.FindStringSubmatch(string(oldRootHtml)); matches != nil { - pathToReplace = matches[1] - alreadyRewritten = true - } - - if pathToReplace == newPath { - mlog.Debug("No rewrite required for static assets", mlog.String("path", pathToReplace)) - return nil - } - - mlog.Debug("Rewriting static assets", mlog.String("from_path", pathToReplace), mlog.String("to_path", newPath)) - - newRootHtml := string(oldRootHtml) - - // Compute the sha256 hash for the inline script and reference same in the CSP meta tag. - // This allows the inline script defining `window.publicPath` to bypass CSP protections. - script := fmt.Sprintf("window.publicPath='%s'", newPath) - scriptHash := sha256.Sum256([]byte(script)) - - reCSP := regexp.MustCompile(`<meta http-equiv=Content-Security-Policy content="script-src 'self' cdn.segment.com/analytics.js/ 'unsafe-eval'([^"]*)">`) - newRootHtml = reCSP.ReplaceAllLiteralString(newRootHtml, fmt.Sprintf( - `<meta http-equiv=Content-Security-Policy content="script-src 'self' cdn.segment.com/analytics.js/ 'unsafe-eval' 'sha256-%s'">`, - base64.StdEncoding.EncodeToString(scriptHash[:]), - )) - - // Rewrite the root.html references to `/static/*` to include the given subpath. This - // potentially includes a previously injected inline script. - newRootHtml = strings.Replace(newRootHtml, pathToReplace, newPath, -1) - - // Inject the script, if needed, to define `window.publicPath`. - if !alreadyRewritten { - newRootHtml = strings.Replace(newRootHtml, "</style>", fmt.Sprintf("</style><script>%s</script>", script), 1) - } - - // Write out the updated root.html. - if err = ioutil.WriteFile(rootHtmlPath, []byte(newRootHtml), 0); err != nil { - return errors.Wrapf(err, "failed to update root.html with subpath %s", subpath) - } - - // Rewrite the *.css references to `/static/*` (or a previously rewritten subpath). - err = filepath.Walk(staticDir, func(walkPath string, info os.FileInfo, err error) error { - if filepath.Ext(walkPath) == ".css" { - if oldCss, err := ioutil.ReadFile(walkPath); err != nil { - return errors.Wrapf(err, "failed to open %s", walkPath) - } else { - newCss := strings.Replace(string(oldCss), pathToReplace, newPath, -1) - if err = ioutil.WriteFile(walkPath, []byte(newCss), 0); err != nil { - return errors.Wrapf(err, "failed to update %s with subpath %s", walkPath, subpath) - } - } - } - - return nil - }) - if err != nil { - return errors.Wrapf(err, "error walking %s", staticDir) - } - - return nil -} - -// UpdateAssetsSubpathFromConfig uses UpdateAssetsSubpath and any path defined in the SiteURL. -func UpdateAssetsSubpathFromConfig(config *model.Config) error { - // Don't rewrite in development environments, since webpack in developer mode constantly - // updates the assets and must be configured separately. - if model.BuildNumber == "dev" { - mlog.Debug("Skipping update to assets subpath since dev build") - return nil - } - - u, err := url.Parse(*config.ServiceSettings.SiteURL) - if err != nil { - return errors.Wrap(err, "failed to parse SiteURL from config") - } - - return UpdateAssetsSubpath(u.Path) -} diff --git a/web/subpath_test.go b/web/subpath_test.go deleted file mode 100644 index 92b1a5d3c..000000000 --- a/web/subpath_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package web_test - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/web" -) - -func TestUpdateAssetsSubpath(t *testing.T) { - t.Run("no client dir", func(t *testing.T) { - tempDir, err := ioutil.TempDir("", "test_update_assets_subpath") - require.NoError(t, err) - defer os.RemoveAll(tempDir) - os.Chdir(tempDir) - - err = web.UpdateAssetsSubpath("/") - require.Error(t, err) - }) - - t.Run("valid", func(t *testing.T) { - tempDir, err := ioutil.TempDir("", "test_update_assets_subpath") - require.NoError(t, err) - defer os.RemoveAll(tempDir) - os.Chdir(tempDir) - - err = os.Mkdir(model.CLIENT_DIR, 0700) - require.NoError(t, err) - - testCases := []struct { - Description string - RootHTML string - MainCSS string - Subpath string - ExpectedRootHTML string - ExpectedMainCSS string - }{ - { - "no changes required, empty subpath provided", - baseRootHtml, - baseCss, - "", - baseRootHtml, - baseCss, - }, - { - "no changes required", - baseRootHtml, - baseCss, - "/", - baseRootHtml, - baseCss, - }, - { - "subpath", - baseRootHtml, - baseCss, - "/subpath", - subpathRootHtml, - subpathCss, - }, - { - "new subpath from old", - subpathRootHtml, - subpathCss, - "/nested/subpath", - newSubpathRootHtml, - newSubpathCss, - }, - { - "resetting to /", - subpathRootHtml, - subpathCss, - "/", - resetRootHtml, - baseCss, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.Description, func(t *testing.T) { - ioutil.WriteFile(filepath.Join(tempDir, model.CLIENT_DIR, "root.html"), []byte(testCase.RootHTML), 0700) - ioutil.WriteFile(filepath.Join(tempDir, model.CLIENT_DIR, "main.css"), []byte(testCase.MainCSS), 0700) - err := web.UpdateAssetsSubpath(testCase.Subpath) - require.NoError(t, err) - - contents, err := ioutil.ReadFile(filepath.Join(tempDir, model.CLIENT_DIR, "root.html")) - require.NoError(t, err) - require.Equal(t, testCase.ExpectedRootHTML, string(contents)) - - contents, err = ioutil.ReadFile(filepath.Join(tempDir, model.CLIENT_DIR, "main.css")) - require.NoError(t, err) - require.Equal(t, testCase.ExpectedMainCSS, string(contents)) - - }) - } - }) -} diff --git a/web/web.go b/web/web.go index 5c1836818..479f439fb 100644 --- a/web/web.go +++ b/web/web.go @@ -6,6 +6,7 @@ package web import ( "fmt" "net/http" + "path" "strings" "github.com/avct/uasurfer" @@ -60,17 +61,19 @@ func Handle404(a *app.App, w http.ResponseWriter, r *http.Request) { mlog.Debug(fmt.Sprintf("%v: code=404 ip=%v", r.URL.Path, utils.GetIpAddress(r))) - if IsApiCall(r) { + if IsApiCall(a, r) { w.WriteHeader(err.StatusCode) err.DetailedError = "There doesn't appear to be an api call for the url='" + r.URL.Path + "'. Typo? are you missing a team_id or user_id as part of the url?" w.Write([]byte(err.ToJson())) } else { - utils.RenderWebAppError(w, r, err, a.AsymmetricSigningKey()) + utils.RenderWebAppError(a.Config(), w, r, err, a.AsymmetricSigningKey()) } } -func IsApiCall(r *http.Request) bool { - return strings.Index(r.URL.Path, "/api/") == 0 +func IsApiCall(a *app.App, r *http.Request) bool { + subpath, _ := utils.GetSubpathFromConfig(a.Config()) + + return strings.Index(r.URL.Path, path.Join(subpath, "api")+"/") == 0 } func ReturnStatusOK(w http.ResponseWriter) { |