1
0
mirror of https://github.com/golang/go synced 2024-10-04 03:21:22 -06:00

http: sniffing placeholder

R=dsymonds
CC=golang-dev
https://golang.org/cl/4746041
This commit is contained in:
Russ Cox 2011-07-15 01:01:49 -04:00
parent 5d929a9c92
commit e1b2e20217
3 changed files with 72 additions and 4 deletions

View File

@ -18,6 +18,7 @@ GOFILES=\
response.go\ response.go\
reverseproxy.go\ reverseproxy.go\
server.go\ server.go\
sniff.go\
status.go\ status.go\
transfer.go\ transfer.go\
transport.go\ transport.go\

View File

@ -99,6 +99,7 @@ type conn struct {
buf *bufio.ReadWriter // buffered rwc buf *bufio.ReadWriter // buffered rwc
hijacked bool // connection has been hijacked by handler hijacked bool // connection has been hijacked by handler
tlsState *tls.ConnectionState // or nil when not using TLS tlsState *tls.ConnectionState // or nil when not using TLS
body []byte
} }
// A response represents the server side of an HTTP response. // A response represents the server side of an HTTP response.
@ -112,6 +113,7 @@ type response struct {
written int64 // number of bytes written in body written int64 // number of bytes written in body
contentLength int64 // explicitly-declared Content-Length; or -1 contentLength int64 // explicitly-declared Content-Length; or -1
status int // status code passed to WriteHeader status int // status code passed to WriteHeader
needSniff bool // need to sniff to find Content-Type
// close connection after this reply. set on request and // close connection after this reply. set on request and
// updated after response from handler if there's a // updated after response from handler if there's a
@ -147,6 +149,7 @@ func newConn(rwc net.Conn, handler Handler) (c *conn, err os.Error) {
c.remoteAddr = rwc.RemoteAddr().String() c.remoteAddr = rwc.RemoteAddr().String()
c.handler = handler c.handler = handler
c.rwc = rwc c.rwc = rwc
c.body = make([]byte, sniffLen)
br := bufio.NewReader(rwc) br := bufio.NewReader(rwc)
bw := bufio.NewWriter(rwc) bw := bufio.NewWriter(rwc)
c.buf = bufio.NewReadWriter(br, bw) c.buf = bufio.NewReadWriter(br, bw)
@ -209,6 +212,7 @@ func (c *conn) readRequest() (w *response, err os.Error) {
w.req = req w.req = req
w.header = make(Header) w.header = make(Header)
w.contentLength = -1 w.contentLength = -1
c.body = c.body[:0]
return w, nil return w, nil
} }
@ -249,9 +253,9 @@ func (w *response) WriteHeader(code int) {
} }
} }
} else { } else {
// Default output is HTML encoded in UTF-8. // If no content type, apply sniffing algorithm to body.
if w.header.Get("Content-Type") == "" { if w.header.Get("Content-Type") == "" {
w.header.Set("Content-Type", "text/html; charset=utf-8") w.needSniff = true
} }
} }
@ -337,8 +341,35 @@ func (w *response) WriteHeader(code int) {
} }
io.WriteString(w.conn.buf, proto+" "+codestring+" "+text+"\r\n") io.WriteString(w.conn.buf, proto+" "+codestring+" "+text+"\r\n")
w.header.Write(w.conn.buf) w.header.Write(w.conn.buf)
// If we need to sniff the body, leave the header open.
// Otherwise, end it here.
if !w.needSniff {
io.WriteString(w.conn.buf, "\r\n") io.WriteString(w.conn.buf, "\r\n")
} }
}
// sniff uses the first block of written data,
// stored in w.conn.body, to decide the Content-Type
// for the HTTP body.
func (w *response) sniff() {
if !w.needSniff {
return
}
w.needSniff = false
data := w.conn.body
ctype := detectContentType(data)
if ctype != "" {
fmt.Fprintf(w.conn.buf, "Content-Type: %s\r\n", ctype)
}
io.WriteString(w.conn.buf, "\r\n")
if w.chunking && len(data) > 0 {
fmt.Fprintf(w.conn.buf, "%x\r\n", len(data))
w.conn.buf.Write(data)
}
}
// bodyAllowed returns true if a Write is allowed for this response type. // bodyAllowed returns true if a Write is allowed for this response type.
// It's illegal to call this before the header has been flushed. // It's illegal to call this before the header has been flushed.
@ -369,6 +400,18 @@ func (w *response) Write(data []byte) (n int, err os.Error) {
return 0, ErrContentLength return 0, ErrContentLength
} }
var m int
if w.needSniff {
body := w.conn.body
m = copy(body[len(body):], data)
w.conn.body = body[:len(body)+m]
if m == len(data) {
return m, nil
}
w.sniff()
data = data[m:]
}
// TODO(rsc): if chunking happened after the buffering, // TODO(rsc): if chunking happened after the buffering,
// then there would be fewer chunk headers. // then there would be fewer chunk headers.
// On the other hand, it would make hijacking more difficult. // On the other hand, it would make hijacking more difficult.
@ -385,7 +428,7 @@ func (w *response) Write(data []byte) (n int, err os.Error) {
} }
} }
return n, err return m + n, err
} }
// If this is an error reply (4xx or 5xx) // If this is an error reply (4xx or 5xx)
@ -449,6 +492,9 @@ func (w *response) finishRequest() {
if !w.wroteHeader { if !w.wroteHeader {
w.WriteHeader(StatusOK) w.WriteHeader(StatusOK)
} }
if w.needSniff {
w.sniff()
}
errorKludge(w) errorKludge(w)
if w.chunking { if w.chunking {
io.WriteString(w.conn.buf, "0\r\n") io.WriteString(w.conn.buf, "0\r\n")
@ -471,6 +517,7 @@ func (w *response) Flush() {
if !w.wroteHeader { if !w.wroteHeader {
w.WriteHeader(StatusOK) w.WriteHeader(StatusOK)
} }
w.sniff()
w.conn.buf.Flush() w.conn.buf.Flush()
} }
@ -517,6 +564,7 @@ func (c *conn) serve() {
if req.ContentLength == 0 { if req.ContentLength == 0 {
w.Header().Set("Connection", "close") w.Header().Set("Connection", "close")
w.WriteHeader(StatusBadRequest) w.WriteHeader(StatusBadRequest)
w.finishRequest()
break break
} }
req.Header.Del("Expect") req.Header.Del("Expect")
@ -535,6 +583,7 @@ func (c *conn) serve() {
// respond with a 417 (Expectation Failed) status." // respond with a 417 (Expectation Failed) status."
w.Header().Set("Connection", "close") w.Header().Set("Connection", "close")
w.WriteHeader(StatusExpectationFailed) w.WriteHeader(StatusExpectationFailed)
w.finishRequest()
break break
} }

18
src/pkg/http/sniff.go Normal file
View File

@ -0,0 +1,18 @@
// Copyright 2011 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.
package http
// Content-type sniffing algorithm.
// http://tools.ietf.org/html/draft-ietf-websec-mime-sniff-03
// The algorithm prefers to use sniffLen bytes to make its decision.
const sniffLen = 1024
// detectContentType returns the sniffed Content-Type string
// for the given data.
func detectContentType(data []byte) string {
// TODO(dsymonds,rsc): Implement algorithm from draft.
return "text/html; charset=utf-8"
}