diff --git a/server/command.go b/server/command.go index 8fc92fa5e..d092d3c39 100644 --- a/server/command.go +++ b/server/command.go @@ -1256,7 +1256,7 @@ func (p *Plugin) loadFlagUserInstance(mattermostUserID string, args []string) (* } func (p *Plugin) respondCommandTemplate(commandArgs *model.CommandArgs, path string, values interface{}) *model.CommandResponse { - t := p.templates[path] + t := p.textTemplates[path] if t == nil { return p.responsef(commandArgs, "no template found for "+path) } diff --git a/server/command_test.go b/server/command_test.go index 9a65815f9..f2c755677 100644 --- a/server/command_test.go +++ b/server/command_test.go @@ -374,9 +374,10 @@ func TestPlugin_ExecuteCommand_Installation(t *testing.T) { p.SetAPI(api) p.client = pluginapi.NewClient(p.API, p.Driver) _, filename, _, _ := runtime.Caller(0) - templates, err := p.loadTemplates(filepath.Dir(filename) + "/../assets/templates") + htmlTemplates, textTemplates, err := p.loadTemplates(filepath.Dir(filename) + "/../assets/templates") require.NoError(t, err) - p.templates = templates + p.htmlTemplates = htmlTemplates + p.textTemplates = textTemplates store := NewStore(&p) p.instanceStore = p.getMockInstanceStoreKV(tt.numInstances) diff --git a/server/http.go b/server/http.go index daaacbbe3..e77d8a063 100644 --- a/server/http.go +++ b/server/http.go @@ -5,6 +5,9 @@ package main import ( "encoding/json" + htmlTemplate "html/template" + textTemplate "text/template" + "net/http" "net/url" "os" @@ -13,7 +16,6 @@ import ( "runtime/debug" "strconv" "strings" - "text/template" "github.com/gorilla/mux" "github.com/pkg/errors" @@ -68,6 +70,8 @@ const ( websocketEventConnect = "connect" websocketEventDisconnect = "disconnect" websocketEventUpdateDefaults = "update_defaults" + + ContentTypeHTML = "text/html" ) func makeAutocompleteRoute(path string) string { @@ -145,9 +149,10 @@ func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Req p.router.ServeHTTP(w, r) } -func (p *Plugin) loadTemplates(dir string) (map[string]*template.Template, error) { +func (p *Plugin) loadTemplates(dir string) (map[string]*htmlTemplate.Template, map[string]*textTemplate.Template, error) { dir = filepath.Clean(dir) - templates := make(map[string]*template.Template) + htmlTemplates := make(map[string]*htmlTemplate.Template) + textTemplates := make(map[string]*textTemplate.Template) err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil { return err @@ -155,20 +160,35 @@ func (p *Plugin) loadTemplates(dir string) (map[string]*template.Template, error if info.IsDir() { return nil } - template, err := template.ParseFiles(path) - if err != nil { - p.errorf("OnActivate: failed to parse template %s: %v", path, err) - return nil + + var key string + if strings.HasSuffix(info.Name(), ".html") { // Check if the content type of template is HTML + template, err := htmlTemplate.ParseFiles(path) + if err != nil { + p.errorf("OnActivate: failed to parse template %s: %v", path, err) + return nil + } + + key = path[len(dir):] + htmlTemplates[key] = template + } else { + template, err := textTemplate.ParseFiles(path) + if err != nil { + p.errorf("OnActivate: failed to parse the template %s: %v", path, err) + return nil + } + + key = path[len(dir):] + textTemplates[key] = template } - key := path[len(dir):] - templates[key] = template + p.debugf("loaded template %s", key) return nil }) if err != nil { - return nil, errors.WithMessage(err, "OnActivate: failed to load templates") + return nil, nil, errors.WithMessage(err, "OnActivate: failed to load templates") } - return templates, nil + return htmlTemplates, textTemplates, nil } func respondErr(w http.ResponseWriter, code int, err error) (int, error) { @@ -191,32 +211,42 @@ func respondJSON(w http.ResponseWriter, obj interface{}) (int, error) { func (p *Plugin) respondTemplate(w http.ResponseWriter, r *http.Request, contentType string, values interface{}) (int, error) { _, path := splitInstancePath(r.URL.Path) - w.Header().Set("Content-Type", contentType) - t := p.templates[path] - if t == nil { - return respondErr(w, http.StatusInternalServerError, - errors.New("no template found for "+path)) - } - err := t.Execute(w, values) - if err != nil { - return http.StatusInternalServerError, errors.WithMessage(err, "failed to write response") - } - return http.StatusOK, nil + return p.executeTemplate(w, path, contentType, values) } func (p *Plugin) respondSpecialTemplate(w http.ResponseWriter, key string, status int, contentType string, values interface{}) (int, error) { + return p.executeTemplate(w, key, contentType, values) +} + +func (p *Plugin) executeTemplate(w http.ResponseWriter, key string, contentType string, values interface{}) (int, error) { w.Header().Set("Content-Type", contentType) - t := p.templates[key] + if contentType == ContentTypeHTML { + t := p.htmlTemplates[key] + if t == nil { + return respondErr(w, http.StatusInternalServerError, + errors.New("no template found for "+key)) + } + + if err := t.Execute(w, values); err != nil { + return http.StatusInternalServerError, + errors.WithMessage(err, "failed to write response") + } + + return http.StatusOK, nil + } + + t := p.textTemplates[key] if t == nil { return respondErr(w, http.StatusInternalServerError, errors.New("no template found for "+key)) } - err := t.Execute(w, values) - if err != nil { + + if err := t.Execute(w, values); err != nil { return http.StatusInternalServerError, errors.WithMessage(err, "failed to write response") } - return status, nil + + return http.StatusOK, nil } func instancePath(route string, instanceID types.ID) string { diff --git a/server/plugin.go b/server/plugin.go index eb1ed8c33..f68562761 100644 --- a/server/plugin.go +++ b/server/plugin.go @@ -9,13 +9,14 @@ import ( "encoding/base64" "encoding/json" "fmt" + htmlTemplate "html/template" "math" "net/url" "path/filepath" "regexp" "strings" "sync" - "text/template" + textTemplate "text/template" "github.com/gorilla/mux" "github.com/pkg/errors" @@ -125,7 +126,8 @@ type Plugin struct { RSAKey *rsa.PrivateKey `json:",omitempty"` // templates are loaded on startup - templates map[string]*template.Template + htmlTemplates map[string]*htmlTemplate.Template + textTemplates map[string]*textTemplate.Template // channel to distribute work to the webhook processors webhookQueue chan *webhookMessage @@ -272,11 +274,12 @@ func (p *Plugin) OnActivate() error { return errors.WithMessage(err, "OnActivate: failed to migrate from previous version of the Jira plugin") } - templates, err := p.loadTemplates(filepath.Join(bundlePath, "assets", "templates")) + htmlTemplates, textTemplates, err := p.loadTemplates(filepath.Join(bundlePath, "assets", "templates")) if err != nil { return err } - p.templates = templates + p.htmlTemplates = htmlTemplates + p.textTemplates = textTemplates p.setupFlow = p.NewSetupFlow() p.oauth2Flow = p.NewOAuth2Flow() diff --git a/server/user_cloud.go b/server/user_cloud.go index 9ff479686..d69e2ff0b 100644 --- a/server/user_cloud.go +++ b/server/user_cloud.go @@ -41,7 +41,7 @@ func (p *Plugin) httpACUserRedirect(w http.ResponseWriter, r *http.Request, inst submitURL := path.Join(ci.Plugin.GetPluginURLPath(), instancePath(routeACUserConfirm, instanceID)) - return ci.Plugin.respondTemplate(w, r, "text/html", struct { + return ci.Plugin.respondTemplate(w, r, ContentTypeHTML, struct { SubmitURL string ArgJiraJWT string ArgMMToken string @@ -166,7 +166,7 @@ func (p *Plugin) httpACUserInteractive(w http.ResponseWriter, r *http.Request, i // This set of props should work for all relevant routes/templates connectSubmitURL := path.Join(p.GetPluginURLPath(), instancePath(routeACUserConnected, instanceID)) disconnectSubmitURL := path.Join(p.GetPluginURLPath(), instancePath(routeACUserDisconnected, instanceID)) - return ci.Plugin.respondTemplate(w, r, "text/html", struct { + return ci.Plugin.respondTemplate(w, r, ContentTypeHTML, struct { ConnectSubmitURL string DisconnectSubmitURL string ArgJiraJWT string diff --git a/server/user_cloud_oauth.go b/server/user_cloud_oauth.go index 585e79110..50e1a1569 100644 --- a/server/user_cloud_oauth.go +++ b/server/user_cloud_oauth.go @@ -79,7 +79,7 @@ func (p *Plugin) httpOAuth2Complete(w http.ResponseWriter, r *http.Request, inst return respondErr(w, http.StatusInternalServerError, errors.Wrap(err, fmt.Sprintf("Error occurred while connecting user. UserID: %s", mattermostUserID))) } - return p.respondTemplate(w, r, "text/html", struct { + return p.respondTemplate(w, r, ContentTypeHTML, struct { MattermostDisplayName string JiraDisplayName string RevokeURL string diff --git a/server/user_server.go b/server/user_server.go index 0c39bb008..707f9f59b 100644 --- a/server/user_server.go +++ b/server/user_server.go @@ -34,7 +34,7 @@ func (p *Plugin) httpOAuth1aComplete(w http.ResponseWriter, r *http.Request, ins if len(errtext) > 0 { errtext = strings.ToUpper(errtext[:1]) + errtext[1:] } - status, err = p.respondSpecialTemplate(w, "/other/message.html", status, "text/html", struct { + status, err = p.respondSpecialTemplate(w, "/other/message.html", status, ContentTypeHTML, struct { Header string Message string }{ @@ -108,7 +108,7 @@ func (p *Plugin) httpOAuth1aComplete(w http.ResponseWriter, r *http.Request, ins return http.StatusInternalServerError, err } - return p.respondTemplate(w, r, "text/html", struct { + return p.respondTemplate(w, r, ContentTypeHTML, struct { MattermostDisplayName string JiraDisplayName string RevokeURL string @@ -130,7 +130,7 @@ func (p *Plugin) httpOAuth1aDisconnect(w http.ResponseWriter, r *http.Request, i } return p.respondSpecialTemplate(w, "/other/message.html", http.StatusOK, - "text/html", struct { + ContentTypeHTML, struct { Header string Message string }{