MM-10367: rewrite subpath assets on startup (#8944)
Examine ServiceSettings.SiteURL on startup and rewrite assets accordingly if not in a development environment. Also export `mattermost config subpath` command to manually do same. This accompanies a webapp PR to use the updated `root.html` to define the necessary webpack asset path for dynamically loading assets.
Этот коммит содержится в:
коммит произвёл
Christopher Speller
родитель
d0cda0500e
Коммит
f106417103
@@ -5,12 +5,14 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/model"
|
"github.com/mattermost/mattermost-server/model"
|
||||||
"github.com/mattermost/mattermost-server/utils"
|
"github.com/mattermost/mattermost-server/utils"
|
||||||
"github.com/spf13/cobra"
|
"github.com/mattermost/mattermost-server/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ConfigCmd = &cobra.Command{
|
var ConfigCmd = &cobra.Command{
|
||||||
@@ -25,9 +27,22 @@ var ValidateConfigCmd = &cobra.Command{
|
|||||||
RunE: configValidateCmdF,
|
RunE: configValidateCmdF,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ConfigSubpathCmd = &cobra.Command{
|
||||||
|
Use: "subpath",
|
||||||
|
Short: "Update client asset loading to use the configured subpath",
|
||||||
|
Long: "Update the hard-coded production client asset paths to take into account Mattermost running on a subpath.",
|
||||||
|
Example: ` config subpath
|
||||||
|
config subpath --path /mattermost
|
||||||
|
config subpath --path /`,
|
||||||
|
RunE: configSubpathCmdF,
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
ConfigSubpathCmd.Flags().String("path", "", "Optional subpath; defaults to value in SiteURL")
|
||||||
|
|
||||||
ConfigCmd.AddCommand(
|
ConfigCmd.AddCommand(
|
||||||
ValidateConfigCmd,
|
ValidateConfigCmd,
|
||||||
|
ConfigSubpathCmd,
|
||||||
)
|
)
|
||||||
RootCmd.AddCommand(ConfigCmd)
|
RootCmd.AddCommand(ConfigCmd)
|
||||||
}
|
}
|
||||||
@@ -65,3 +80,22 @@ func configValidateCmdF(command *cobra.Command, args []string) error {
|
|||||||
CommandPrettyPrintln("The document is valid")
|
CommandPrettyPrintln("The document is valid")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func configSubpathCmdF(command *cobra.Command, args []string) error {
|
||||||
|
a, err := InitDBCommandContextCobra(command)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer a.Shutdown()
|
||||||
|
|
||||||
|
path, err := command.Flags().GetString("path")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed reading path")
|
||||||
|
} else if path == "" {
|
||||||
|
return web.UpdateAssetsSubpathFromConfig(a.Config())
|
||||||
|
} else if err := web.UpdateAssetsSubpath(path); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to update assets subpath")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
15
web/helpers_test.go
Обычный файл
15
web/helpers_test.go
Обычный файл
@@ -0,0 +1,15 @@
|
|||||||
|
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>`
|
||||||
@@ -18,6 +18,8 @@ import (
|
|||||||
|
|
||||||
func (w *Web) InitStatic() {
|
func (w *Web) InitStatic() {
|
||||||
if *w.App.Config().ServiceSettings.WebserverMode != "disabled" {
|
if *w.App.Config().ServiceSettings.WebserverMode != "disabled" {
|
||||||
|
UpdateAssetsSubpathFromConfig(w.App.Config())
|
||||||
|
|
||||||
staticDir, _ := utils.FindDir(model.CLIENT_DIR)
|
staticDir, _ := utils.FindDir(model.CLIENT_DIR)
|
||||||
mlog.Debug(fmt.Sprintf("Using client directory at %v", staticDir))
|
mlog.Debug(fmt.Sprintf("Using client directory at %v", staticDir))
|
||||||
|
|
||||||
|
|||||||
130
web/subpath.go
Обычный файл
130
web/subpath.go
Обычный файл
@@ -0,0 +1,130 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
103
web/subpath_test.go
Обычный файл
103
web/subpath_test.go
Обычный файл
@@ -0,0 +1,103 @@
|
|||||||
|
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))
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
Ссылка в новой задаче
Block a user