github.com/hernad/nomad@v1.6.112/helper/noxssrw/noxssrw.go (about)

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