package engine import ( "bytes" "html" "html/template" "net" "net/http" "path/filepath" "strconv" "strings" ) const ( // Highlight of errors in the code preHighlight = "" postHighlight = "" // HTML template for the error page htmlTemplate = ` {{.Title}}
{{.Title}}
Contents of {{.Filename}}:
{{.Code}}
Error message:
{{.ErrorMessage}}
` ) // Given a lowercase string for the language, return an appropriate error page title func errorPageTitle(lang string) string { switch lang { case "": return "Error" case "css": return "CSS Error" case "gcss": return "GCSS Error" case "html": return "HTML Error" case "jsx": return "JSX Error" case "lua": return "Lua Error" case "teal": return "Teal Error" default: return lang + " Error" } } // isLoopback returns true if the request originates from a loopback address func isLoopback(req *http.Request) bool { host, _, err := net.SplitHostPort(req.RemoteAddr) if err != nil { host = req.RemoteAddr } ip := net.ParseIP(host) return ip != nil && ip.IsLoopback() } // PrettyError serves an informative error page to the user func (ac *Config) PrettyError(w http.ResponseWriter, req *http.Request, filename string, filebytes []byte, errormessage, lang string) { // Only show detailed error pages to loopback clients if !isLoopback(req) { http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } // Prevent caching of error pages w.Header().Set("Cache-Control", "no-store") // HTTP status w.WriteHeader(http.StatusOK) // HTTP content type w.Header().Add(contentType, htmlUTF8) var ( code string err error ) // The line that the error refers to, for the case of Lua linenr := -1 if len(filebytes) > 0 { if lang == "lua" { fields := strings.SplitN(errormessage, ":", 3) if len(fields) > 2 { numberfield := fields[1] if strings.Contains(numberfield, "(") { numberfield = strings.Split(numberfield, "(")[0] } linenr, err = strconv.Atoi(numberfield) linenr-- if err != nil { linenr = -1 } } } else if lang == "amber" { if strings.Contains(errormessage, "- Line: ") { fields := strings.SplitN(errormessage, "- Line: ", 2) if strings.Contains(fields[1], ",") { numberfields := strings.SplitN(fields[1], ",", 2) linenr, err = strconv.Atoi(strings.TrimSpace(numberfields[0])) linenr-- if err != nil { linenr = -1 } } } } lines := strings.Split(string(filebytes), "\n") for i, line := range lines { lines[i] = html.EscapeString(line) } if (linenr >= 0) && (linenr < len(lines)) { lines[linenr] = preHighlight + lines[linenr] + postHighlight } code = strings.Join(lines, "\n") } title := errorPageTitle(lang) data := struct { Title string Filename string Code template.HTML ErrorMessage string VersionString string }{ Title: title, Filename: filepath.Base(filename), Code: template.HTML(code), ErrorMessage: strings.TrimSpace(errormessage), VersionString: ac.versionString, } tmpl, err := template.New("errorPage").Parse(htmlTemplate) if err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } if ac.autoRefresh { var htmlbuf bytes.Buffer if err := tmpl.Execute(&htmlbuf, data); err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } w.Write(ac.InsertAutoRefresh(req, htmlbuf.Bytes())) return } if err := tmpl.Execute(w, data); err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } }