Initial QSfera import
This commit is contained in:
+253
@@ -0,0 +1,253 @@
|
||||
// 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)
|
||||
}
|
||||
Reference in New Issue
Block a user