github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/helper/noxssrw/noxssrw.go (about) 1 // Package noxssrw (No XSS ResponseWriter) behaves like the Go standard 2 // library's ResponseWriter by detecting the Content-Type of a response if it 3 // has not been explicitly set. However, unlike the standard library's 4 // implementation, this implementation will never return the "text/html" 5 // Content-Type and instead return "text/plain". 6 package noxssrw 7 8 import ( 9 "net/http" 10 "strings" 11 ) 12 13 var ( 14 // DefaultUnsafeTypes are Content-Types that browsers will render as hypertext. 15 // Any Content-Types that allow Javascript or remote resource fetching must be 16 // converted to a Content-Type that prevents evaluation. 17 // 18 // Types are prefix matched to avoid comparing against specific 19 // character sets (eg "text/html; charset=utf-8") which may be user 20 // controlled. 21 DefaultUnsafeTypes = map[string]string{ 22 "text/html": "text/plain", 23 "text/xhtml": "text/plain", 24 "text/xhtml+xml": "text/plain", 25 } 26 27 // DefaultHeaders contain CORS headers meant to prevent the execution 28 // of Javascript in compliant browsers. 29 DefaultHeaders = map[string]string{ 30 "Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'; sandbox", 31 "X-Content-Type-Options": "nosniff", 32 "X-XSS-Protection": "1; mode=block", 33 } 34 ) 35 36 // NoXSSResponseWriter implements http.ResponseWriter but prevents renderable 37 // Content-Types from being automatically detected. Create with 38 // NewResponseWriter. 39 type NoXSSResponseWriter struct { 40 // TypeMap maps types unsafe for untrusted content to their safe 41 // version; may be replaced but not mutated. 42 TypeMap map[string]string 43 44 // DefaultHeaders to set on first write if they are not already 45 // explicitly set. 46 DefaultHeaders map[string]string 47 48 // buffer up to 512 bytes before detecting Content-Type and writing 49 // response. 50 buf []byte 51 52 // subsequentWrite is true after the first Write is called 53 subsequentWrite bool 54 55 // flushed is true if Content-Type has been set and Writes may be 56 // passed through. 57 flushed bool 58 59 // original ResponseWriter being wrapped 60 orig http.ResponseWriter 61 } 62 63 // Header returns the header map that will be sent by 64 // WriteHeader. The Header map also is the mechanism with which 65 // Handlers can set HTTP trailers. 66 // 67 // Changing the header map after a call to WriteHeader (or 68 // Write) has no effect unless the modified headers are 69 // trailers. 70 // 71 // There are two ways to set Trailers. The preferred way is to 72 // predeclare in the headers which trailers you will later 73 // send by setting the "Trailer" header to the names of the 74 // trailer keys which will come later. In this case, those 75 // keys of the Header map are treated as if they were 76 // trailers. See the example. The second way, for trailer 77 // keys not known to the Handler until after the first Write, 78 // is to prefix the Header map keys with the TrailerPrefix 79 // constant value. See TrailerPrefix. 80 // 81 // To suppress automatic response headers (such as "Date"), set 82 // their value to nil. 83 func (w *NoXSSResponseWriter) Header() http.Header { 84 return w.orig.Header() 85 } 86 87 // Write writes the data to the connection as part of an HTTP reply. 88 // 89 // If WriteHeader has not yet been called, Write calls 90 // WriteHeader(http.StatusOK) before writing the data. If the Header 91 // does not contain a Content-Type line, Write adds a Content-Type set 92 // to the result of passing the initial 512 bytes of written data to 93 // DetectContentType. Additionally, if the total size of all written 94 // data is under a few KB and there are no Flush calls, the 95 // Content-Length header is added automatically. 96 // 97 // Depending on the HTTP protocol version and the client, calling 98 // Write or WriteHeader may prevent future reads on the 99 // Request.Body. For HTTP/1.x requests, handlers should read any 100 // needed request body data before writing the response. Once the 101 // headers have been flushed (due to either an explicit Flusher.Flush 102 // call or writing enough data to trigger a flush), the request body 103 // may be unavailable. For HTTP/2 requests, the Go HTTP server permits 104 // handlers to continue to read the request body while concurrently 105 // writing the response. However, such behavior may not be supported 106 // by all HTTP/2 clients. Handlers should read before writing if 107 // possible to maximize compatibility. 108 func (w *NoXSSResponseWriter) Write(p []byte) (int, error) { 109 headers := w.Header() 110 // If first write, set any unset default headers. Do this on first write 111 // to allow overriding the default set of headers. 112 if !w.subsequentWrite { 113 for k, v := range w.DefaultHeaders { 114 if headers.Get(k) == "" { 115 headers.Set(k, v) 116 } 117 } 118 w.subsequentWrite = true 119 } 120 121 // If already flushed, write-through and short-circuit 122 if w.flushed { 123 return w.orig.Write(p) 124 } 125 126 // < 512 bytes available, buffer and wait for closing or a subsequent 127 // request 128 if len(w.buf)+len(p) < 512 { 129 w.buf = append(w.buf, p...) 130 return len(p), nil 131 } 132 133 // >= 512 bytes available, set the Content-Type and flush. 134 all := append(w.buf, p...) 135 contentType := http.DetectContentType(all) 136 137 // Prefix match to exclude the character set which may be user 138 // controlled. 139 for prefix, safe := range w.TypeMap { 140 if strings.HasPrefix(contentType, prefix) { 141 contentType = safe 142 break 143 } 144 } 145 146 // Set the Content-Type iff it was not already explicitly set 147 if headers.Get("Content-Type") == "" { 148 headers.Set("Content-Type", contentType) 149 } 150 151 // Write the buffer 152 n, err := w.orig.Write(w.buf) 153 if err != nil { 154 // Throw away part of buffer written successfully and 155 // inform caller p was not written at all 156 w.buf = w.buf[:n] 157 return 0, err 158 } 159 160 // Headers and buffer were written, this writer has been 161 // flushed and can be a passthrough 162 w.flushed = true 163 w.buf = w.buf[:] 164 165 // Write p 166 return w.orig.Write(p) 167 } 168 169 // Close and flush the writer. Necessary for responses that never reached 512 170 // bytes. 171 func (w *NoXSSResponseWriter) Close() (int, error) { 172 // If the buffer was already flushed this is a noop 173 if w.flushed { 174 return 0, nil 175 } 176 177 // Prefix match to exclude the character set which may be user 178 // controlled. 179 contentType := http.DetectContentType(w.buf) 180 for prefix, safe := range w.TypeMap { 181 if strings.HasPrefix(contentType, prefix) { 182 contentType = safe 183 break 184 } 185 } 186 187 // Set the Content-Type iff it was not already explicitly set 188 if headers := w.Header(); headers.Get("Content-Type") == "" { 189 headers.Set("Content-Type", contentType) 190 } 191 192 // Write the buffer 193 return w.orig.Write(w.buf) 194 } 195 196 // WriteHeader sends an HTTP response header with the provided 197 // status code. 198 // 199 // If WriteHeader is not called explicitly, the first call to Write 200 // will trigger an implicit WriteHeader(http.StatusOK). 201 // Thus explicit calls to WriteHeader are mainly used to 202 // send error codes. 203 // 204 // The provided code must be a valid HTTP 1xx-5xx status code. 205 // Only one header may be written. Go does not currently 206 // support sending user-defined 1xx informational headers, 207 // with the exception of 100-continue response header that the 208 // Server sends automatically when the Request.Body is read. 209 func (w *NoXSSResponseWriter) WriteHeader(statusCode int) { 210 w.orig.WriteHeader(statusCode) 211 } 212 213 // NewResponseWriter creates a new ResponseWriter and Close func which will 214 // prevent Go's http.ResponseWriter default behavior of detecting the 215 // Content-Type. 216 // 217 // The Close func must be called to ensure that responses < 512 bytes are 218 // flushed as up to 512 bytes are buffered without flushing. 219 func NewResponseWriter(orig http.ResponseWriter) (http.ResponseWriter, func() (int, error)) { 220 w := &NoXSSResponseWriter{ 221 TypeMap: DefaultUnsafeTypes, 222 DefaultHeaders: DefaultHeaders, 223 buf: make([]byte, 0, 512), 224 orig: orig, 225 } 226 227 return w, w.Close 228 }