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