github.com/tonyhb/nomad@v0.11.8/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  }