github.com/kyleu/dbaudit@v0.0.2-0.20240321155047-ff2f2c940496/app/controller/cutil/mime.go (about) 1 // Package cutil - Content managed by Project Forge, see [projectforge.md] for details. 2 package cutil 3 4 import ( 5 "fmt" 6 "net/http" 7 "net/url" 8 "strings" 9 10 "github.com/pkg/errors" 11 "gopkg.in/yaml.v2" 12 13 "github.com/kyleu/dbaudit/app" 14 "github.com/kyleu/dbaudit/app/util" 15 ) 16 17 const ( 18 mimeCSV = "text/csv" 19 mimeDebug = "text/plain" 20 mimeJSON = "application/json" 21 mimeXML = "text/xml" 22 mimeYAML = "application/x-yaml" 23 24 HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials" 25 HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers" 26 HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods" 27 HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin" 28 HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers" 29 HeaderCacheControl = "Cache-Control" 30 HeaderContentType = "Content-Type" 31 HeaderReferer = "Referer" 32 ) 33 34 var ( 35 AllowedRequestHeaders = "*" 36 AllowedResponseHeaders = "*" 37 ) 38 39 func WriteCORS(w http.ResponseWriter) { 40 h := w.Header() 41 setIfEmpty := func(k string, v string) { 42 if x := h.Get(k); x == "" { 43 h.Set(k, v) 44 } 45 } 46 setIfEmpty(HeaderAccessControlAllowHeaders, AllowedRequestHeaders) 47 setIfEmpty(HeaderAccessControlAllowMethods, "GET,POST,DELETE,PUT,PATCH,OPTIONS,HEAD") 48 if x := string(h.Get(HeaderReferer)); x == "" { 49 setIfEmpty(HeaderAccessControlAllowOrigin, "*") 50 } else { 51 u, err := url.Parse(x) 52 if err == nil { 53 o := u.Hostname() 54 if u.Port() != "" { 55 o += ":" + u.Port() 56 } 57 sch := u.Scheme 58 if strings.Contains(o, ".network") { 59 sch = "https" 60 } 61 setIfEmpty(HeaderAccessControlAllowOrigin, sch+"://"+o) 62 } else { 63 setIfEmpty(HeaderAccessControlAllowOrigin, "*") 64 } 65 } 66 setIfEmpty(HeaderAccessControlAllowCredentials, util.BoolTrue) 67 setIfEmpty(HeaderAccessControlExposeHeaders, AllowedResponseHeaders) 68 } 69 70 func RespondDebug(w http.ResponseWriter, r *http.Request, as *app.State, filename string, ps *PageState) (string, error) { 71 return RespondJSON(w, filename, RequestCtxToMap(w, r, as, ps)) 72 } 73 74 func RespondCSV(w http.ResponseWriter, filename string, body any) (string, error) { 75 b, err := util.ToCSVBytes(body) 76 if err != nil { 77 return "", err 78 } 79 return RespondMIME(filename, mimeCSV, "csv", b, w) 80 } 81 82 func RespondJSON(w http.ResponseWriter, filename string, body any) (string, error) { 83 b := util.ToJSONBytes(body, true) 84 return RespondMIME(filename, mimeJSON, "json", b, w) 85 } 86 87 type XMLResponse struct { 88 Result any `xml:"result"` 89 } 90 91 func RespondXML(w http.ResponseWriter, filename string, body any) (string, error) { 92 b, err := util.ToXMLBytes(body, true) 93 if err != nil { 94 return "", errors.Wrapf(err, "can't serialize response of type [%T] to XML", body) 95 } 96 return RespondMIME(filename, mimeXML, "xml", b, w) 97 } 98 99 func RespondYAML(w http.ResponseWriter, filename string, body any) (string, error) { 100 b, err := yaml.Marshal(body) 101 if err != nil { 102 return "", err 103 } 104 return RespondMIME(filename, mimeYAML, "yaml", b, w) 105 } 106 107 func RespondMIME(filename string, mime string, ext string, ba []byte, w http.ResponseWriter) (string, error) { 108 h := w.Header() 109 h.Set(HeaderContentType, mime+"; charset=UTF-8") 110 if filename != "" { 111 if !strings.HasSuffix(filename, "."+ext) { 112 filename = filename + "." + ext 113 } 114 h.Set("Content-Disposition", fmt.Sprintf(`attachment; filename=%q`, filename)) 115 } 116 WriteCORS(w) 117 if len(ba) == 0 { 118 return "", errors.New("no bytes available to write") 119 } 120 if _, err := w.Write(ba); err != nil { 121 return "", errors.Wrap(err, "cannot write to response") 122 } 123 124 return "", nil 125 } 126 127 func GetContentType(r *http.Request) string { 128 ret := r.Header.Get(HeaderContentType) 129 if idx := strings.Index(ret, ";"); idx > -1 { 130 ret = ret[0:idx] 131 } 132 t := r.URL.Query().Get("t") 133 switch t { 134 case "debug": 135 return mimeDebug 136 case "csv": 137 return mimeCSV 138 case "json": 139 return mimeJSON 140 case "xml": 141 return mimeXML 142 case "yaml", "yml": 143 return mimeYAML 144 default: 145 return strings.TrimSpace(ret) 146 } 147 } 148 149 func IsContentTypeCSV(c string) bool { 150 return c == mimeCSV 151 } 152 153 func IsContentTypeDebug(c string) bool { 154 return c == mimeDebug 155 } 156 157 func IsContentTypeJSON(c string) bool { 158 return c == mimeJSON || c == "text/json" 159 } 160 161 func IsContentTypeXML(c string) bool { 162 return c == "application/xml" || c == mimeXML 163 } 164 165 func IsContentTypeYAML(c string) bool { 166 return c == mimeYAML || c == "text/yaml" 167 }