23ab331979
Signed-off-by: Justen Martin <jmart@the-coder.com>
199 lines
4.8 KiB
Go
199 lines
4.8 KiB
Go
package authorization // import "github.com/docker/docker/pkg/authorization"
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// ResponseModifier allows authorization plugins to read and modify the content of the http.response
|
|
type ResponseModifier interface {
|
|
http.ResponseWriter
|
|
http.Flusher
|
|
|
|
// RawBody returns the current http content
|
|
RawBody() []byte
|
|
|
|
// RawHeaders returns the current content of the http headers
|
|
RawHeaders() ([]byte, error)
|
|
|
|
// StatusCode returns the current status code
|
|
StatusCode() int
|
|
|
|
// OverrideBody replaces the body of the HTTP reply
|
|
OverrideBody(b []byte)
|
|
|
|
// OverrideHeader replaces the headers of the HTTP reply
|
|
OverrideHeader(b []byte) error
|
|
|
|
// OverrideStatusCode replaces the status code of the HTTP reply
|
|
OverrideStatusCode(statusCode int)
|
|
|
|
// FlushAll flushes all data to the HTTP response
|
|
FlushAll() error
|
|
|
|
// Hijacked indicates the response has been hijacked by the Docker daemon
|
|
Hijacked() bool
|
|
}
|
|
|
|
// NewResponseModifier creates a wrapper to an http.ResponseWriter to allow inspecting and modifying the content
|
|
func NewResponseModifier(rw http.ResponseWriter) ResponseModifier {
|
|
return &responseModifier{rw: rw, header: make(http.Header)}
|
|
}
|
|
|
|
const maxBufferSize = 64 * 1024
|
|
|
|
// responseModifier is used as an adapter to http.ResponseWriter in order to manipulate and explore
|
|
// the http request/response from docker daemon
|
|
type responseModifier struct {
|
|
// The original response writer
|
|
rw http.ResponseWriter
|
|
// body holds the response body
|
|
body []byte
|
|
// header holds the response header
|
|
header http.Header
|
|
// statusCode holds the response status code
|
|
statusCode int
|
|
// hijacked indicates the request has been hijacked
|
|
hijacked bool
|
|
}
|
|
|
|
func (rm *responseModifier) Hijacked() bool {
|
|
return rm.hijacked
|
|
}
|
|
|
|
// WriterHeader stores the http status code
|
|
func (rm *responseModifier) WriteHeader(s int) {
|
|
|
|
// Use original request if hijacked
|
|
if rm.hijacked {
|
|
rm.rw.WriteHeader(s)
|
|
return
|
|
}
|
|
|
|
rm.statusCode = s
|
|
}
|
|
|
|
// Header returns the internal http header
|
|
func (rm *responseModifier) Header() http.Header {
|
|
|
|
// Use original header if hijacked
|
|
if rm.hijacked {
|
|
return rm.rw.Header()
|
|
}
|
|
|
|
return rm.header
|
|
}
|
|
|
|
// StatusCode returns the http status code
|
|
func (rm *responseModifier) StatusCode() int {
|
|
return rm.statusCode
|
|
}
|
|
|
|
// OverrideBody replaces the body of the HTTP response
|
|
func (rm *responseModifier) OverrideBody(b []byte) {
|
|
rm.body = b
|
|
}
|
|
|
|
// OverrideStatusCode replaces the status code of the HTTP response
|
|
func (rm *responseModifier) OverrideStatusCode(statusCode int) {
|
|
rm.statusCode = statusCode
|
|
}
|
|
|
|
// OverrideHeader replaces the headers of the HTTP response
|
|
func (rm *responseModifier) OverrideHeader(b []byte) error {
|
|
header := http.Header{}
|
|
if err := json.Unmarshal(b, &header); err != nil {
|
|
return err
|
|
}
|
|
rm.header = header
|
|
return nil
|
|
}
|
|
|
|
// Write stores the byte array inside content
|
|
func (rm *responseModifier) Write(b []byte) (int, error) {
|
|
if rm.hijacked {
|
|
return rm.rw.Write(b)
|
|
}
|
|
|
|
if len(rm.body)+len(b) > maxBufferSize {
|
|
rm.Flush()
|
|
}
|
|
rm.body = append(rm.body, b...)
|
|
return len(b), nil
|
|
}
|
|
|
|
// Body returns the response body
|
|
func (rm *responseModifier) RawBody() []byte {
|
|
return rm.body
|
|
}
|
|
|
|
func (rm *responseModifier) RawHeaders() ([]byte, error) {
|
|
var b bytes.Buffer
|
|
if err := rm.header.Write(&b); err != nil {
|
|
return nil, err
|
|
}
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
// Hijack returns the internal connection of the wrapped http.ResponseWriter
|
|
func (rm *responseModifier) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|
|
|
rm.hijacked = true
|
|
rm.FlushAll()
|
|
|
|
hijacker, ok := rm.rw.(http.Hijacker)
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("Internal response writer doesn't support the Hijacker interface")
|
|
}
|
|
return hijacker.Hijack()
|
|
}
|
|
|
|
// Flush uses the internal flush API of the wrapped http.ResponseWriter
|
|
func (rm *responseModifier) Flush() {
|
|
flusher, ok := rm.rw.(http.Flusher)
|
|
if !ok {
|
|
logrus.Error("Internal response writer doesn't support the Flusher interface")
|
|
return
|
|
}
|
|
|
|
rm.FlushAll()
|
|
flusher.Flush()
|
|
}
|
|
|
|
// FlushAll flushes all data to the HTTP response
|
|
func (rm *responseModifier) FlushAll() error {
|
|
// Copy the header
|
|
for k, vv := range rm.header {
|
|
for _, v := range vv {
|
|
rm.rw.Header().Add(k, v)
|
|
}
|
|
}
|
|
|
|
// Copy the status code
|
|
// Also WriteHeader needs to be done after all the headers
|
|
// have been copied (above).
|
|
if rm.statusCode > 0 {
|
|
rm.rw.WriteHeader(rm.statusCode)
|
|
}
|
|
|
|
var err error
|
|
if len(rm.body) > 0 {
|
|
// Write body
|
|
var n int
|
|
n, err = rm.rw.Write(rm.body)
|
|
// TODO(@cpuguy83): there is now a relatively small buffer limit, instead of discarding our buffer here and
|
|
// allocating again later this should just keep using the same buffer and track the buffer position (like a bytes.Buffer with a fixed size)
|
|
rm.body = rm.body[n:]
|
|
}
|
|
|
|
// Clean previous data
|
|
rm.statusCode = 0
|
|
rm.header = http.Header{}
|
|
return err
|
|
}
|