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 }