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

254 lines
6.3 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.
// Reading and parsing of ICAP requests.
// Package icap provides an extensible ICAP server.
package icap
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/textproto"
"net/url"
"strconv"
"strings"
)
type badStringError struct {
what string
str string
}
func (e *badStringError) Error() string { return fmt.Sprintf("%s %q", e.what, e.str) }
// A Request represents a parsed ICAP request.
type Request struct {
Method string // REQMOD, RESPMOD, OPTIONS, etc.
RawURL string // The URL given in the request.
URL *url.URL // Parsed URL.
Proto string // The protocol version.
Header textproto.MIMEHeader // The ICAP header
RemoteAddr string // the address of the computer sending the request
Preview []byte // the body data for an ICAP preview
// The HTTP messages.
Request *http.Request
Response *http.Response
}
// ReadRequest reads and parses a request from b.
func ReadRequest(b *bufio.ReadWriter) (req *Request, err error) {
tp := textproto.NewReader(b.Reader)
req = new(Request)
// Read first line.
var s string
s, err = tp.ReadLine()
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return nil, err
}
f := strings.SplitN(s, " ", 3)
if len(f) < 3 {
return nil, &badStringError{"malformed ICAP request", s}
}
req.Method, req.RawURL, req.Proto = f[0], f[1], f[2]
req.URL, err = url.ParseRequestURI(req.RawURL)
if err != nil {
return nil, err
}
req.Header, err = tp.ReadMIMEHeader()
if err != nil {
return nil, err
}
s = req.Header.Get("Encapsulated")
if s == "" {
return req, nil // No HTTP headers or body.
}
eList := strings.Split(s, ", ")
var initialOffset, reqHdrLen, respHdrLen int
var hasBody bool
var prevKey string
var prevValue int
for _, item := range eList {
eq := strings.Index(item, "=")
if eq == -1 {
return nil, &badStringError{"malformed Encapsulated: header", s}
}
key := item[:eq]
value, err := strconv.Atoi(item[eq+1:])
if err != nil {
return nil, &badStringError{"malformed Encapsulated: header", s}
}
// Calculate the length of the previous section.
switch prevKey {
case "":
initialOffset = value
case "req-hdr":
reqHdrLen = value - prevValue
case "res-hdr":
respHdrLen = value - prevValue
case "req-body", "opt-body", "res-body", "null-body":
return nil, fmt.Errorf("%s must be the last section", prevKey)
}
switch key {
case "req-hdr", "res-hdr", "null-body":
case "req-body", "res-body", "opt-body":
hasBody = true
default:
return nil, &badStringError{"invalid key for Encapsulated: header", key}
}
prevValue = value
prevKey = key
}
// Read the HTTP headers.
var rawReqHdr, rawRespHdr []byte
if initialOffset > 0 {
junk := make([]byte, initialOffset)
_, err = io.ReadFull(b, junk)
if err != nil {
return nil, err
}
}
if reqHdrLen > 0 {
rawReqHdr = make([]byte, reqHdrLen)
_, err = io.ReadFull(b, rawReqHdr)
if err != nil {
return nil, err
}
}
if respHdrLen > 0 {
rawRespHdr = make([]byte, respHdrLen)
_, err = io.ReadFull(b, rawRespHdr)
if err != nil {
return nil, err
}
}
var bodyReader io.ReadCloser = emptyReader(0)
if hasBody {
if p := req.Header.Get("Preview"); p != "" {
moreBody := true
req.Preview, err = ioutil.ReadAll(newChunkedReader(b))
if err != nil {
if strings.Contains(err.Error(), "ieof") {
// The data ended with "0; ieof", which the HTTP chunked reader doesn't understand.
moreBody = false
err = nil
} else {
return nil, err
}
}
var r io.Reader = bytes.NewBuffer(req.Preview)
if moreBody {
r = io.MultiReader(r, &continueReader{buf: b})
}
bodyReader = ioutil.NopCloser(r)
} else {
bodyReader = ioutil.NopCloser(newChunkedReader(b))
}
}
// Construct the http.Request.
if rawReqHdr != nil {
invalidURLEscapeFixed := false
req.Request, err = http.ReadRequest(bufio.NewReader(bytes.NewBuffer(rawReqHdr)))
if err != nil && strings.Contains(err.Error(), "invalid URL escape") {
//Fix the request url
// Convert the rawReqHdr to string
// find the url\path start and end(sould be in the status line
// convert the percents into %25
// Then reparse the whole request
rawReqHdrStr := string(rawReqHdr)
result := strings.Split(rawReqHdrStr, "\n")
result[0] = strings.Replace(result[0], "%", "%25", -1)
// The next is a compromise since when adding "\r\n" it causes the request parsing to fail
newReq := strings.Join(result, "\n")
req.Request, err = http.ReadRequest(bufio.NewReader(bytes.NewBuffer([]byte(newReq))))
if err != nil {
return req, fmt.Errorf("error while parsing HTTP request: %v", err)
}
invalidURLEscapeFixed = true
}
if err != nil && !invalidURLEscapeFixed {
return req, fmt.Errorf("error while parsing HTTP request: %v", err)
}
if req.Method == "REQMOD" {
req.Request.Body = bodyReader
} else {
req.Request.Body = emptyReader(0)
}
}
// Construct the http.Response.
if rawRespHdr != nil {
request := req.Request
if request == nil {
request, _ = http.NewRequest("GET", "/", nil)
}
req.Response, err = http.ReadResponse(bufio.NewReader(bytes.NewBuffer(rawRespHdr)), request)
if err != nil {
return req, fmt.Errorf("error while parsing HTTP response: %v", err)
}
if req.Method == "RESPMOD" {
req.Response.Body = bodyReader
} else {
req.Response.Body = emptyReader(0)
}
}
return
}
// An emptyReader is an io.ReadCloser that always returns os.EOF.
type emptyReader byte
func (emptyReader) Read(p []byte) (n int, err error) {
return 0, io.EOF
}
func (emptyReader) Close() error {
return nil
}
// A continueReader sends a "100 Continue" message the first time Read
// is called, creates a ChunkedReader, and reads from that.
type continueReader struct {
buf *bufio.ReadWriter // the underlying connection
cr io.Reader // the ChunkedReader
}
func (c *continueReader) Read(p []byte) (n int, err error) {
if c.cr == nil {
_, err := c.buf.WriteString("ICAP/1.0 100 Continue\r\n\r\n")
if err != nil {
return 0, err
}
err = c.buf.Flush()
if err != nil {
return 0, err
}
c.cr = newChunkedReader(c.buf)
}
return c.cr.Read(p)
}