mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-03 11:38:23 -05:00
254 lines
6.3 KiB
Go
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)
|
|
}
|