Files
opencloud/vendor/github.com/egirna/icap/response.go
2023-04-19 20:24:34 +02:00

230 lines
5.4 KiB
Go

// Copyright 2011 Andy Balholm. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Responding to ICAP requests.
package icap
import (
"bytes"
"errors"
"fmt"
"io"
"log"
"net/http"
"net/http/httputil"
"strconv"
"strings"
"time"
)
// ResponseWriter ---
type ResponseWriter interface {
// Header returns the header map that will be sent by WriteHeader.
// Changing the header after a call to WriteHeader (or Write) has
// no effect.
Header() http.Header
// Write writes the data to the connection as part of an ICAP reply.
// If WriteHeader has not yet been called, Write calls WriteHeader(http.StatusOK, nil)
// before writing the data.
Write([]byte) (int, error)
// Write raw data to the connection.
WriteRaw(string)
// WriteHeader sends an ICAP response header with status code.
// Then it sends an HTTP header if httpMessage is not nil.
// httpMessage may be an *http.Request or an *http.Response.
// hasBody should be true if there will be calls to Write(), generating a message body.
WriteHeader(code int, httpMessage interface{}, hasBody bool)
}
type respWriter struct {
conn *conn // information on the connection
req *Request // the request that is being responded to
header http.Header // the ICAP header to write for the response
wroteHeader bool // true if the headers have already been written
wroteRaw bool // true if raw data was written to the connection
cw io.WriteCloser // the chunked writer used to write the body
}
func (w *respWriter) Header() http.Header {
return w.header
}
func (w *respWriter) Write(p []byte) (n int, err error) {
if !w.wroteHeader {
w.WriteHeader(http.StatusOK, nil, true)
}
if w.cw == nil {
return 0, errors.New("called Write() on an icap.ResponseWriter that should not have a body")
}
return w.cw.Write(p)
}
func (w *respWriter) WriteRaw(p string) {
bw := w.conn.buf.Writer
io.WriteString(bw, p)
w.wroteRaw = true
}
func (w *respWriter) WriteHeader(code int, httpMessage interface{}, hasBody bool) {
if w.wroteHeader {
log.Println("Called WriteHeader twice on the same connection")
return
}
// Make the HTTP header and the Encapsulated: header.
var header []byte
var encap string
var err error
switch msg := httpMessage.(type) {
case *http.Request:
header, err = httpRequestHeader(msg)
if err != nil {
break
}
if hasBody {
encap = fmt.Sprintf("req-hdr=0, req-body=%d", len(header))
} else {
encap = fmt.Sprintf("req-hdr=0, null-body=%d", len(header))
}
case *http.Response:
header, err = httpResponseHeader(msg)
if err != nil {
break
}
if hasBody {
encap = fmt.Sprintf("res-hdr=0, res-body=%d", len(header))
} else {
encap = fmt.Sprintf("res-hdr=0, null-body=%d", len(header))
}
}
if encap == "" {
if hasBody {
method := w.req.Method
if len(method) > 3 {
method = method[0:3]
}
method = strings.ToLower(method)
encap = fmt.Sprintf("%s-body=0", method)
} else {
encap = "null-body=0"
}
}
w.header.Set("Encapsulated", encap)
if _, ok := w.header["Date"]; !ok {
w.Header().Set("Date", time.Now().UTC().Format(http.TimeFormat))
}
w.header.Set("Connection", "close")
bw := w.conn.buf.Writer
status := StatusText(code)
if status == "" {
status = fmt.Sprintf("status code %d", code)
}
fmt.Fprintf(bw, "ICAP/1.0 %d %s\r\n", code, status)
w.header.Write(bw)
io.WriteString(bw, "\r\n")
if header != nil {
bw.Write(header)
}
w.wroteHeader = true
if hasBody {
w.cw = httputil.NewChunkedWriter(w.conn.buf.Writer)
}
}
func (w *respWriter) finishRequest() {
if !w.wroteHeader {
w.WriteHeader(http.StatusOK, nil, false)
}
if w.cw != nil && !w.wroteRaw {
w.cw.Close()
w.cw = nil
io.WriteString(w.conn.buf, "\r\n")
}
w.conn.buf.Flush()
}
// httpRequestHeader returns the headers for an HTTP request
// as a slice of bytes in a form suitable for including in an ICAP message.
func httpRequestHeader(req *http.Request) (hdr []byte, err error) {
buf := new(bytes.Buffer)
if req.URL == nil {
if err != nil {
return nil, errors.New("icap: httpRequestHeader called on Request with no URL")
}
}
host := req.URL.Host
if host == "" {
host = req.Host
}
req.Header.Set("Host", host)
uri := req.URL.String()
fmt.Fprintf(buf, "%s %s %s\r\n", valueOrDefault(req.Method, "GET"), uri, valueOrDefault(req.Proto, "HTTP/1.1"))
req.Header.WriteSubset(buf, map[string]bool{
"Transfer-Encoding": true,
"Content-Length": true,
})
io.WriteString(buf, "\r\n")
return buf.Bytes(), nil
}
// httpResponseHeader returns the headers for an HTTP response
// as a slice of bytes.
func httpResponseHeader(resp *http.Response) (hdr []byte, err error) {
buf := new(bytes.Buffer)
// Status line
text := resp.Status
if text == "" {
text = http.StatusText(resp.StatusCode)
if text == "" {
text = "status code " + strconv.Itoa(resp.StatusCode)
}
}
proto := resp.Proto
if proto == "" {
proto = "HTTP/1.1"
}
fmt.Fprintf(buf, "%s %d %s\r\n", proto, resp.StatusCode, text)
if _, xIcap206Exists := resp.Header["X-Icap-206"]; xIcap206Exists {
resp.Header.Write(buf)
} else {
resp.Header.WriteSubset(buf, map[string]bool{
"Transfer-Encoding": true,
"Content-Length": false,
})
}
io.WriteString(buf, "\r\n")
return buf.Bytes(), nil
}
// Return value if nonempty, def otherwise.
func valueOrDefault(value, def string) string {
if value != "" {
return value
}
return def
}