Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
~$
\.swp$
\.[568]$
\.out$
^_
\.orig$
example/example
+27
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}