github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/pkg/authorization/response.go (about)

     1  package authorization // import "github.com/demonoid81/moby/pkg/authorization"
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"encoding/json"
     7  	"fmt"
     8  	"net"
     9  	"net/http"
    10  
    11  	"github.com/sirupsen/logrus"
    12  )
    13  
    14  // ResponseModifier allows authorization plugins to read and modify the content of the http.response
    15  type ResponseModifier interface {
    16  	http.ResponseWriter
    17  	http.Flusher
    18  
    19  	// RawBody returns the current http content
    20  	RawBody() []byte
    21  
    22  	// RawHeaders returns the current content of the http headers
    23  	RawHeaders() ([]byte, error)
    24  
    25  	// StatusCode returns the current status code
    26  	StatusCode() int
    27  
    28  	// OverrideBody replaces the body of the HTTP reply
    29  	OverrideBody(b []byte)
    30  
    31  	// OverrideHeader replaces the headers of the HTTP reply
    32  	OverrideHeader(b []byte) error
    33  
    34  	// OverrideStatusCode replaces the status code of the HTTP reply
    35  	OverrideStatusCode(statusCode int)
    36  
    37  	// FlushAll flushes all data to the HTTP response
    38  	FlushAll() error
    39  
    40  	// Hijacked indicates the response has been hijacked by the Docker daemon
    41  	Hijacked() bool
    42  }
    43  
    44  // NewResponseModifier creates a wrapper to an http.ResponseWriter to allow inspecting and modifying the content
    45  func NewResponseModifier(rw http.ResponseWriter) ResponseModifier {
    46  	return &responseModifier{rw: rw, header: make(http.Header)}
    47  }
    48  
    49  const maxBufferSize = 64 * 1024
    50  
    51  // responseModifier is used as an adapter to http.ResponseWriter in order to manipulate and explore
    52  // the http request/response from docker daemon
    53  type responseModifier struct {
    54  	// The original response writer
    55  	rw http.ResponseWriter
    56  	// body holds the response body
    57  	body []byte
    58  	// header holds the response header
    59  	header http.Header
    60  	// statusCode holds the response status code
    61  	statusCode int
    62  	// hijacked indicates the request has been hijacked
    63  	hijacked bool
    64  }
    65  
    66  func (rm *responseModifier) Hijacked() bool {
    67  	return rm.hijacked
    68  }
    69  
    70  // WriterHeader stores the http status code
    71  func (rm *responseModifier) WriteHeader(s int) {
    72  
    73  	// Use original request if hijacked
    74  	if rm.hijacked {
    75  		rm.rw.WriteHeader(s)
    76  		return
    77  	}
    78  
    79  	rm.statusCode = s
    80  }
    81  
    82  // Header returns the internal http header
    83  func (rm *responseModifier) Header() http.Header {
    84  
    85  	// Use original header if hijacked
    86  	if rm.hijacked {
    87  		return rm.rw.Header()
    88  	}
    89  
    90  	return rm.header
    91  }
    92  
    93  // StatusCode returns the http status code
    94  func (rm *responseModifier) StatusCode() int {
    95  	return rm.statusCode
    96  }
    97  
    98  // OverrideBody replaces the body of the HTTP response
    99  func (rm *responseModifier) OverrideBody(b []byte) {
   100  	rm.body = b
   101  }
   102  
   103  // OverrideStatusCode replaces the status code of the HTTP response
   104  func (rm *responseModifier) OverrideStatusCode(statusCode int) {
   105  	rm.statusCode = statusCode
   106  }
   107  
   108  // OverrideHeader replaces the headers of the HTTP response
   109  func (rm *responseModifier) OverrideHeader(b []byte) error {
   110  	header := http.Header{}
   111  	if err := json.Unmarshal(b, &header); err != nil {
   112  		return err
   113  	}
   114  	rm.header = header
   115  	return nil
   116  }
   117  
   118  // Write stores the byte array inside content
   119  func (rm *responseModifier) Write(b []byte) (int, error) {
   120  	if rm.hijacked {
   121  		return rm.rw.Write(b)
   122  	}
   123  
   124  	if len(rm.body)+len(b) > maxBufferSize {
   125  		rm.Flush()
   126  	}
   127  	rm.body = append(rm.body, b...)
   128  	return len(b), nil
   129  }
   130  
   131  // Body returns the response body
   132  func (rm *responseModifier) RawBody() []byte {
   133  	return rm.body
   134  }
   135  
   136  func (rm *responseModifier) RawHeaders() ([]byte, error) {
   137  	var b bytes.Buffer
   138  	if err := rm.header.Write(&b); err != nil {
   139  		return nil, err
   140  	}
   141  	return b.Bytes(), nil
   142  }
   143  
   144  // Hijack returns the internal connection of the wrapped http.ResponseWriter
   145  func (rm *responseModifier) Hijack() (net.Conn, *bufio.ReadWriter, error) {
   146  
   147  	rm.hijacked = true
   148  	rm.FlushAll()
   149  
   150  	hijacker, ok := rm.rw.(http.Hijacker)
   151  	if !ok {
   152  		return nil, nil, fmt.Errorf("Internal response writer doesn't support the Hijacker interface")
   153  	}
   154  	return hijacker.Hijack()
   155  }
   156  
   157  // Flush uses the internal flush API of the wrapped http.ResponseWriter
   158  func (rm *responseModifier) Flush() {
   159  	flusher, ok := rm.rw.(http.Flusher)
   160  	if !ok {
   161  		logrus.Error("Internal response writer doesn't support the Flusher interface")
   162  		return
   163  	}
   164  
   165  	rm.FlushAll()
   166  	flusher.Flush()
   167  }
   168  
   169  // FlushAll flushes all data to the HTTP response
   170  func (rm *responseModifier) FlushAll() error {
   171  	// Copy the header
   172  	for k, vv := range rm.header {
   173  		for _, v := range vv {
   174  			rm.rw.Header().Add(k, v)
   175  		}
   176  	}
   177  
   178  	// Copy the status code
   179  	// Also WriteHeader needs to be done after all the headers
   180  	// have been copied (above).
   181  	if rm.statusCode > 0 {
   182  		rm.rw.WriteHeader(rm.statusCode)
   183  	}
   184  
   185  	var err error
   186  	if len(rm.body) > 0 {
   187  		// Write body
   188  		var n int
   189  		n, err = rm.rw.Write(rm.body)
   190  		// TODO(@cpuguy83): there is now a relatively small buffer limit, instead of discarding our buffer here and
   191  		// allocating again later this should just keep using the same buffer and track the buffer position (like a bytes.Buffer with a fixed size)
   192  		rm.body = rm.body[n:]
   193  	}
   194  
   195  	// Clean previous data
   196  	rm.statusCode = 0
   197  	rm.header = http.Header{}
   198  	return err
   199  }