Initial QSfera import
This commit is contained in:
+7
@@ -0,0 +1,7 @@
|
||||
~$
|
||||
\.swp$
|
||||
\.[568]$
|
||||
\.out$
|
||||
^_
|
||||
\.orig$
|
||||
example/example
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2018, Eliezer Croitoru NgTech LTD
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of go-icap nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
// 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.
|
||||
|
||||
// A bridge between ICAP and HTTP.
|
||||
// It allows answering a REQMOD request with an HTTP response generated locally.
|
||||
|
||||
package icap
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type bridgedRespWriter struct {
|
||||
irw ResponseWriter // the underlying icap.ResponseWriter
|
||||
header http.Header // the headers for the HTTP response
|
||||
wroteHeader bool // Have the headers been written yet?
|
||||
}
|
||||
|
||||
func (w *bridgedRespWriter) Header() http.Header {
|
||||
return w.header
|
||||
}
|
||||
|
||||
func (w *bridgedRespWriter) Write(p []byte) (n int, err error) {
|
||||
if !w.wroteHeader {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
return w.irw.Write(p)
|
||||
}
|
||||
|
||||
func (w *bridgedRespWriter) WriteHeader(code int) {
|
||||
if w.wroteHeader {
|
||||
log.Print("http: multiple response.WriteHeader calls")
|
||||
return
|
||||
}
|
||||
|
||||
w.wroteHeader = true
|
||||
|
||||
// Default output is HTML encoded in UTF-8.
|
||||
if w.header.Get("Content-Type") == "" {
|
||||
w.header.Set("Content-Type", "text/html; charset=utf-8")
|
||||
}
|
||||
|
||||
if _, ok := w.header["Date"]; !ok {
|
||||
w.Header().Set("Date", time.Now().UTC().Format(http.TimeFormat))
|
||||
}
|
||||
|
||||
resp := new(http.Response)
|
||||
resp.StatusCode = code
|
||||
resp.Header = w.header
|
||||
|
||||
w.irw.WriteHeader(200, resp, true)
|
||||
}
|
||||
|
||||
// NewBridgedResponseWriter Create an http.ResponseWriter that encapsulates its response in an ICAP response.
|
||||
func NewBridgedResponseWriter(w ResponseWriter) http.ResponseWriter {
|
||||
rw := new(bridgedRespWriter)
|
||||
rw.header = make(http.Header)
|
||||
rw.irw = w
|
||||
|
||||
return rw
|
||||
}
|
||||
|
||||
// ServeLocally Pass use the local HTTP server to generate a response for an ICAP request.
|
||||
func ServeLocally(w ResponseWriter, req *Request) {
|
||||
brw := NewBridgedResponseWriter(w)
|
||||
http.DefaultServeMux.ServeHTTP(brw, req.Request)
|
||||
}
|
||||
|
||||
// ServeLocallyFromHandler ---
|
||||
func ServeLocallyFromHandler(w ResponseWriter, req *Request, mux http.Handler) {
|
||||
brw := NewBridgedResponseWriter(w)
|
||||
mux.ServeHTTP(brw, req.Request)
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// The wire protocol for HTTP's "chunked" Transfer-Encoding.
|
||||
// This code is derived from the standard library's http/httputil/chunked.go,
|
||||
|
||||
package icap
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const maxLineLength = 4096 // assumed <= bufio.defaultBufSize
|
||||
|
||||
var errLineTooLong = errors.New("header line too long")
|
||||
|
||||
// NewChunkedReader returns a new chunkedReader that translates the data read from r
|
||||
// out of HTTP "chunked" format before returning it.
|
||||
// The chunkedReader returns io.EOF when the final 0-length chunk is read.
|
||||
//
|
||||
// NewChunkedReader is not needed by normal applications. The http package
|
||||
// automatically decodes chunking when reading response bodies.
|
||||
func newChunkedReader(r io.Reader) io.Reader {
|
||||
br, ok := r.(*bufio.Reader)
|
||||
if !ok {
|
||||
br = bufio.NewReader(r)
|
||||
}
|
||||
return &chunkedReader{r: br}
|
||||
}
|
||||
|
||||
type chunkedReader struct {
|
||||
r *bufio.Reader
|
||||
n uint64 // unread bytes in chunk
|
||||
err error
|
||||
buf [2]byte
|
||||
}
|
||||
|
||||
func (cr *chunkedReader) beginChunk() {
|
||||
// chunk-size CRLF
|
||||
var line []byte
|
||||
line, cr.err = readLine(cr.r)
|
||||
if cr.err != nil {
|
||||
return
|
||||
}
|
||||
cr.n, cr.err = parseHexUint(line)
|
||||
if cr.err != nil {
|
||||
return
|
||||
}
|
||||
if cr.n == 0 {
|
||||
cr.err = io.EOF
|
||||
}
|
||||
}
|
||||
|
||||
func (cr *chunkedReader) Read(b []uint8) (n int, err error) {
|
||||
if cr.err != nil {
|
||||
return 0, cr.err
|
||||
}
|
||||
if cr.n == 0 {
|
||||
cr.beginChunk()
|
||||
if cr.err != nil {
|
||||
return 0, cr.err
|
||||
}
|
||||
}
|
||||
if uint64(len(b)) > cr.n {
|
||||
b = b[0:cr.n]
|
||||
}
|
||||
n, cr.err = cr.r.Read(b)
|
||||
cr.n -= uint64(n)
|
||||
if cr.n == 0 && cr.err == nil {
|
||||
// end of chunk (CRLF)
|
||||
if _, cr.err = io.ReadFull(cr.r, cr.buf[:]); cr.err == nil {
|
||||
if cr.buf[0] != '\r' || cr.buf[1] != '\n' {
|
||||
cr.err = errors.New("malformed chunked encoding")
|
||||
}
|
||||
}
|
||||
}
|
||||
return n, cr.err
|
||||
}
|
||||
|
||||
// Read a line of bytes (up to \n) from b.
|
||||
// Give up if the line exceeds maxLineLength.
|
||||
// The returned bytes are a pointer into storage in
|
||||
// the bufio, so they are only valid until the next bufio read.
|
||||
func readLine(b *bufio.Reader) (p []byte, err error) {
|
||||
if p, err = b.ReadSlice('\n'); err != nil {
|
||||
// We always know when EOF is coming.
|
||||
// If the caller asked for a line, there should be a line.
|
||||
if err == io.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
} else if err == bufio.ErrBufferFull {
|
||||
err = errLineTooLong
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if len(p) >= maxLineLength {
|
||||
return nil, errLineTooLong
|
||||
}
|
||||
return trimTrailingWhitespace(p), nil
|
||||
}
|
||||
|
||||
func trimTrailingWhitespace(b []byte) []byte {
|
||||
for len(b) > 0 && isASCIISpace(b[len(b)-1]) {
|
||||
b = b[:len(b)-1]
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func isASCIISpace(b byte) bool {
|
||||
return b == ' ' || b == '\t' || b == '\n' || b == '\r'
|
||||
}
|
||||
|
||||
// NewChunkedWriter returns a new chunkedWriter that translates writes into HTTP
|
||||
// "chunked" format before writing them to w. Closing the returned chunkedWriter
|
||||
// sends the final 0-length chunk that marks the end of the stream.
|
||||
//
|
||||
// NewChunkedWriter is not needed by normal applications. The http
|
||||
// package adds chunking automatically if handlers don't set a
|
||||
// Content-Length header. Using NewChunkedWriter inside a handler
|
||||
// would result in double chunking or chunking with a Content-Length
|
||||
// length, both of which are wrong.
|
||||
func NewChunkedWriter(w io.Writer) io.WriteCloser {
|
||||
return &chunkedWriter{w}
|
||||
}
|
||||
|
||||
// Writing to chunkedWriter translates to writing in HTTP chunked Transfer
|
||||
// Encoding wire format to the underlying Wire chunkedWriter.
|
||||
type chunkedWriter struct {
|
||||
Wire io.Writer
|
||||
}
|
||||
|
||||
// Write the contents of data as one chunk to Wire.
|
||||
// NOTE: Note that the corresponding chunk-writing procedure in Conn.Write has
|
||||
// a bug since it does not check for success of io.WriteString
|
||||
func (cw *chunkedWriter) Write(data []byte) (n int, err error) {
|
||||
|
||||
// Don't send 0-length data. It looks like EOF for chunked encoding.
|
||||
if len(data) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if _, err = fmt.Fprintf(cw.Wire, "%x\r\n", len(data)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if n, err = cw.Wire.Write(data); err != nil {
|
||||
return
|
||||
}
|
||||
if n != len(data) {
|
||||
err = io.ErrShortWrite
|
||||
return
|
||||
}
|
||||
_, err = io.WriteString(cw.Wire, "\r\n")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (cw *chunkedWriter) Close() error {
|
||||
_, err := io.WriteString(cw.Wire, "0\r\n")
|
||||
return err
|
||||
}
|
||||
|
||||
func parseHexUint(v []byte) (n uint64, err error) {
|
||||
for _, b := range v {
|
||||
n <<= 4
|
||||
switch {
|
||||
case '0' <= b && b <= '9':
|
||||
b = b - '0'
|
||||
case 'a' <= b && b <= 'f':
|
||||
b = b - 'a' + 10
|
||||
case 'A' <= b && b <= 'F':
|
||||
b = b - 'A' + 10
|
||||
default:
|
||||
return 0, fmt.Errorf("invalid chunk length: '%s'", v)
|
||||
}
|
||||
n |= uint64(b)
|
||||
}
|
||||
return
|
||||
}
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
// 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.
|
||||
|
||||
package icap
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ServeMux is an ICAP request multiplexer.
|
||||
// It matches the URL of each incoming request against a list of registered
|
||||
// patterns and calls the handler for the pattern that
|
||||
// most closely matches the URL.
|
||||
//
|
||||
// For more details, see the documentation for http.ServeMux
|
||||
type ServeMux struct {
|
||||
m map[string]Handler
|
||||
}
|
||||
|
||||
// NewServeMux allocates and returns a new ServeMux.
|
||||
func NewServeMux() *ServeMux { return &ServeMux{make(map[string]Handler)} }
|
||||
|
||||
// DefaultServeMux is the default ServeMux used by Serve.
|
||||
var DefaultServeMux = NewServeMux()
|
||||
|
||||
// Does path match pattern?
|
||||
func pathMatch(pattern, path string) bool {
|
||||
if len(pattern) == 0 {
|
||||
// should not happen
|
||||
return false
|
||||
}
|
||||
n := len(pattern)
|
||||
if pattern[n-1] != '/' {
|
||||
return pattern == path
|
||||
}
|
||||
return len(path) >= n && path[0:n] == pattern
|
||||
}
|
||||
|
||||
// Return the canonical path for p, eliminating . and .. elements.
|
||||
func cleanPath(p string) string {
|
||||
if p == "" {
|
||||
return "/"
|
||||
}
|
||||
if p[0] != '/' {
|
||||
p = "/" + p
|
||||
}
|
||||
np := path.Clean(p)
|
||||
// path.Clean removes trailing slash except for root;
|
||||
// put the trailing slash back if necessary.
|
||||
if p[len(p)-1] == '/' && np != "/" {
|
||||
np += "/"
|
||||
}
|
||||
return np
|
||||
}
|
||||
|
||||
// Find a handler on a handler map given a path string
|
||||
// Most-specific (longest) pattern wins
|
||||
func (mux *ServeMux) match(path string) Handler {
|
||||
var h Handler
|
||||
var n = 0
|
||||
for k, v := range mux.m {
|
||||
if !pathMatch(k, path) {
|
||||
continue
|
||||
}
|
||||
if h == nil || len(k) > n {
|
||||
n = len(k)
|
||||
h = v
|
||||
}
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// ServeICAP dispatches the request to the handler whose
|
||||
// pattern most closely matches the request URL.
|
||||
func (mux *ServeMux) ServeICAP(w ResponseWriter, r *Request) {
|
||||
// Clean path to canonical form and redirect.
|
||||
if p := cleanPath(r.URL.Path); p != r.URL.Path {
|
||||
w.Header().Set("Location", p)
|
||||
w.WriteHeader(http.StatusMovedPermanently, nil, false)
|
||||
return
|
||||
}
|
||||
// Host-specific pattern takes precedence over generic ones
|
||||
h := mux.match(r.URL.Host + r.URL.Path)
|
||||
if h == nil {
|
||||
h = mux.match(r.URL.Path)
|
||||
}
|
||||
if h == nil {
|
||||
h = NotFoundHandler()
|
||||
}
|
||||
h.ServeICAP(w, r)
|
||||
}
|
||||
|
||||
// Handle registers the handler for the given pattern.
|
||||
func (mux *ServeMux) Handle(pattern string, handler Handler) {
|
||||
if pattern == "" {
|
||||
panic("icap: invalid pattern " + pattern)
|
||||
}
|
||||
|
||||
mux.m[pattern] = handler
|
||||
|
||||
// Helpful behavior:
|
||||
// If pattern is /tree/, insert permanent redirect for /tree.
|
||||
n := len(pattern)
|
||||
if n > 0 && pattern[n-1] == '/' {
|
||||
mux.m[pattern[0:n-1]] = RedirectHandler(pattern, http.StatusMovedPermanently)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleFunc registers the handler function for the given pattern.
|
||||
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
|
||||
mux.Handle(pattern, HandlerFunc(handler))
|
||||
}
|
||||
|
||||
// Handle registers the handler for the given pattern
|
||||
// in the DefaultServeMux.
|
||||
// The documentation for ServeMux explains how patterns are matched.
|
||||
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
|
||||
|
||||
// HandleFunc registers the handler function for the given pattern
|
||||
// in the DefaultServeMux.
|
||||
// The documentation for ServeMux explains how patterns are matched.
|
||||
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
|
||||
DefaultServeMux.HandleFunc(pattern, handler)
|
||||
}
|
||||
|
||||
// NotFound replies to the request with an HTTP 404 not found error.
|
||||
func NotFound(w ResponseWriter, r *Request) {
|
||||
w.WriteHeader(http.StatusNotFound, nil, false)
|
||||
}
|
||||
|
||||
// NotFoundHandler returns a simple request handler
|
||||
// that replies to each request with a ``404 page not found'' reply.
|
||||
func NotFoundHandler() Handler { return HandlerFunc(NotFound) }
|
||||
|
||||
// Redirect to a fixed URL
|
||||
type redirectHandler struct {
|
||||
url string
|
||||
code int
|
||||
}
|
||||
|
||||
func (rh *redirectHandler) ServeICAP(w ResponseWriter, r *Request) {
|
||||
Redirect(w, r, rh.url, rh.code)
|
||||
}
|
||||
|
||||
// RedirectHandler returns a request handler that redirects
|
||||
// each request it receives to the given url using the given
|
||||
// status code.
|
||||
func RedirectHandler(redirectURL string, code int) Handler {
|
||||
return &redirectHandler{redirectURL, code}
|
||||
}
|
||||
|
||||
// Redirect replies to the request with a redirect to url,
|
||||
// which may be a path relative to the request path.
|
||||
func Redirect(w ResponseWriter, r *Request, redirectURL string, code int) {
|
||||
if u, err := url.Parse(redirectURL); err == nil {
|
||||
// If url was relative, make absolute by
|
||||
// combining with request path.
|
||||
// The browser would probably do this for us,
|
||||
// but doing it ourselves is more reliable.
|
||||
oldpath := r.URL.Path
|
||||
if oldpath == "" { // should not happen, but avoid a crash if it does
|
||||
oldpath = "/"
|
||||
}
|
||||
if u.Scheme == "" {
|
||||
// no leading icap://server
|
||||
if redirectURL == "" || redirectURL[0] != '/' {
|
||||
// make relative path absolute
|
||||
olddir, _ := path.Split(oldpath)
|
||||
redirectURL = olddir + redirectURL
|
||||
}
|
||||
|
||||
var query string
|
||||
if i := strings.Index(redirectURL, "?"); i != -1 {
|
||||
redirectURL, query = redirectURL[:i], redirectURL[i:]
|
||||
}
|
||||
|
||||
// clean up but preserve trailing slash
|
||||
trailing := redirectURL[len(redirectURL)-1] == '/'
|
||||
redirectURL = path.Clean(redirectURL)
|
||||
if trailing && redirectURL[len(redirectURL)-1] != '/' {
|
||||
redirectURL += "/"
|
||||
}
|
||||
redirectURL += query
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set("Location", redirectURL)
|
||||
w.WriteHeader(code, nil, false)
|
||||
}
|
||||
+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)
|
||||
}
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
// 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
|
||||
}
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
// 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.
|
||||
|
||||
// Network connections and request dispatch for the ICAP server.
|
||||
|
||||
package icap
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Handler Objects implementing the Handler interface can be registered
|
||||
// to serve ICAP requests.
|
||||
//
|
||||
// ServeICAP should write reply headers and data to the ResponseWriter
|
||||
// and then return.
|
||||
type Handler interface {
|
||||
ServeICAP(ResponseWriter, *Request)
|
||||
}
|
||||
|
||||
// The HandlerFunc type is an adapter to allow the use of
|
||||
// ordinary functions as ICAP handlers. If f is a function
|
||||
// with the appropriate signature, HandlerFunc(f) is a
|
||||
// Handler object that calls f.
|
||||
type HandlerFunc func(ResponseWriter, *Request)
|
||||
|
||||
// ServeICAP calls f(w, r).
|
||||
func (f HandlerFunc) ServeICAP(w ResponseWriter, r *Request) {
|
||||
f(w, r)
|
||||
}
|
||||
|
||||
// A conn represents the server side of an ICAP connection.
|
||||
type conn struct {
|
||||
remoteAddr string // network address of remote side
|
||||
handler Handler // request handler
|
||||
rwc net.Conn // i/o connection
|
||||
buf *bufio.ReadWriter // buffered rwc
|
||||
}
|
||||
|
||||
// Create new connection from rwc.
|
||||
func newConn(rwc net.Conn, handler Handler) (c *conn, err error) {
|
||||
c = new(conn)
|
||||
c.remoteAddr = rwc.RemoteAddr().String()
|
||||
c.handler = handler
|
||||
c.rwc = rwc
|
||||
br := bufio.NewReader(rwc)
|
||||
bw := bufio.NewWriter(rwc)
|
||||
c.buf = bufio.NewReadWriter(br, bw)
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Read next request from connection.
|
||||
func (c *conn) readRequest() (w *respWriter, err error) {
|
||||
var req *Request
|
||||
if req, err = ReadRequest(c.buf); err != nil {
|
||||
//return req, err
|
||||
|
||||
}
|
||||
if req == nil {
|
||||
req = new(Request)
|
||||
} else {
|
||||
req.RemoteAddr = c.remoteAddr
|
||||
}
|
||||
|
||||
w = new(respWriter)
|
||||
w.conn = c
|
||||
w.req = req
|
||||
w.header = make(http.Header)
|
||||
return w, err
|
||||
}
|
||||
|
||||
// Close the connection.
|
||||
func (c *conn) close() {
|
||||
if c.buf != nil {
|
||||
c.buf.Flush()
|
||||
c.buf = nil
|
||||
}
|
||||
if c.rwc != nil {
|
||||
c.rwc.Close()
|
||||
c.rwc = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Serve a new connection.
|
||||
func (c *conn) serve(debugLevel int) {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
c.rwc.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "icap: panic serving %v: %v\n", c.remoteAddr, err)
|
||||
buf.Write(debug.Stack())
|
||||
log.Print(buf.String())
|
||||
}()
|
||||
var w *respWriter
|
||||
w, err := c.readRequest()
|
||||
// In a case of parsing error there should be an option to handle a dummy request to not fail the whole service.
|
||||
|
||||
if err != nil {
|
||||
if debugLevel > 0 {
|
||||
log.Println("error while reading request:", err)
|
||||
log.Println("error while reading request:", w)
|
||||
log.Println("error while reading request:", w.conn)
|
||||
log.Println("error while reading request:", w.req)
|
||||
log.Println("error while reading request:", w.header)
|
||||
}
|
||||
// c.rwc.Close()
|
||||
// return
|
||||
if w == nil {
|
||||
w = new(respWriter)
|
||||
}
|
||||
w.conn = c
|
||||
w.req = new(Request)
|
||||
w.req.Method = "ERRDUMMY"
|
||||
w.req.RawURL = "/"
|
||||
w.req.Proto = "ICAP/1.0"
|
||||
w.req.URL, _ = url.ParseRequestURI("icap://localhost/")
|
||||
w.req.Header = textproto.MIMEHeader{
|
||||
"Connection": {"close"},
|
||||
// "Error:": {err.Error()},
|
||||
}
|
||||
}
|
||||
|
||||
c.handler.ServeICAP(w, w.req)
|
||||
w.finishRequest()
|
||||
|
||||
c.close()
|
||||
}
|
||||
|
||||
// A Server defines parameters for running an ICAP server.
|
||||
type Server struct {
|
||||
Addr string // TCP address to listen on, ":1344" if empty
|
||||
Handler Handler // handler to invoke
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
DebugLevel int
|
||||
}
|
||||
|
||||
// ListenAndServe listens on the TCP network address srv.Addr and then
|
||||
// calls Serve to handle requests on incoming connections. If
|
||||
// srv.Addr is blank, ":1344" is used.
|
||||
func (srv *Server) ListenAndServe() error {
|
||||
addr := srv.Addr
|
||||
if addr == "" {
|
||||
addr = ":1344"
|
||||
}
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.Serve(l)
|
||||
}
|
||||
|
||||
// ListenAndServeTLS ---
|
||||
func (srv *Server) ListenAndServeTLS(cert, key string) error {
|
||||
|
||||
cer, err := tls.LoadX509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addr := srv.Addr
|
||||
if addr == "" {
|
||||
addr = ":1344"
|
||||
}
|
||||
|
||||
config := &tls.Config{Certificates: []tls.Certificate{cer}}
|
||||
l, err := tls.Listen("tcp", addr, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.Serve(l)
|
||||
}
|
||||
|
||||
// Serve accepts incoming connections on the Listener l, creating a
|
||||
// new service thread for each. The service threads read requests and
|
||||
// then call srv.Handler to reply to them.
|
||||
func (srv *Server) Serve(l net.Listener) error {
|
||||
defer l.Close()
|
||||
handler := srv.Handler
|
||||
if handler == nil {
|
||||
handler = DefaultServeMux
|
||||
}
|
||||
|
||||
for {
|
||||
rw, err := l.Accept()
|
||||
if err != nil {
|
||||
if ne, ok := err.(net.Error); ok && ne.Temporary() {
|
||||
log.Printf("icap: Accept error: %v", err)
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
if srv.ReadTimeout != 0 {
|
||||
rw.SetReadDeadline(time.Now().Add(srv.ReadTimeout))
|
||||
}
|
||||
if srv.WriteTimeout != 0 {
|
||||
rw.SetWriteDeadline(time.Now().Add(srv.WriteTimeout))
|
||||
}
|
||||
c, err := newConn(rw, handler)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
go c.serve(srv.DebugLevel)
|
||||
}
|
||||
// The next line is only there to see one specific edge case whichc should never happen.
|
||||
panic("Shuold never be reached")
|
||||
}
|
||||
|
||||
// Serve accepts incoming ICAP connections on the listener l,
|
||||
// creating a new service thread for each. The service threads
|
||||
// read requests and then call handler to reply to them.
|
||||
func Serve(l net.Listener, handler Handler) error {
|
||||
srv := &Server{Handler: handler}
|
||||
return srv.Serve(l)
|
||||
}
|
||||
|
||||
// ListenAndServe listens on the TCP network address addr
|
||||
// and then calls Serve with handler to handle requests
|
||||
// on incoming connections.
|
||||
func ListenAndServe(addr string, handler Handler) error {
|
||||
server := &Server{Addr: addr, Handler: handler}
|
||||
return server.ListenAndServe()
|
||||
}
|
||||
|
||||
// ListenAndServeTLS --
|
||||
func ListenAndServeTLS(addr, cert, key string, handler Handler) error {
|
||||
server := &Server{Addr: addr, Handler: handler}
|
||||
return server.ListenAndServeTLS(cert, key)
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// 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.
|
||||
|
||||
// ICAP status codes.
|
||||
|
||||
package icap
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var statusText = map[int]string{
|
||||
100: "Continue",
|
||||
204: "No Modifications",
|
||||
400: "Bad Request",
|
||||
404: "ICAP Service Not Found",
|
||||
405: "Method Not Allowed",
|
||||
408: "Request Timeout",
|
||||
500: "Server Error",
|
||||
501: "Method Not Implemented",
|
||||
502: "Bad Gateway",
|
||||
503: "Service Overloaded",
|
||||
505: "ICAP Version Not Supported",
|
||||
}
|
||||
|
||||
// StatusText returns a text for the ICAP status code. It returns the empty string if the code is unknown.
|
||||
func StatusText(code int) string {
|
||||
text, ok := statusText[code]
|
||||
if ok {
|
||||
return text
|
||||
}
|
||||
return http.StatusText(code)
|
||||
}
|
||||
Reference in New Issue
Block a user