diff --git a/CHANGELOG b/CHANGELOG index a4cc56be..411062bd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +0.7.4 (2013-07-15) + * Fix adding extra connection header for client request with both + "Proxy-Connection" and "Connection" headers + * Ignore UTF-8 BOM in config file + 0.7.3 (2013-07-10) * Handle 100-continue: do not forward expect header from client, ignore 100 continue response replied by some web servers diff --git a/README.md b/README.md index 0d33d736..f7d5f39e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ COW 是一个利用二级代理自动化穿越防火墙的 HTTP 代理服务器。它能自动检测被墙网站,仅对这些网站使用二级代理。 -当前版本:0.7.3 [CHANGELOG](CHANGELOG) +当前版本:0.7.4 [CHANGELOG](CHANGELOG) [![Build Status](https://travis-ci.org/cyfdecyf/cow.png?branch=master)](https://travis-ci.org/cyfdecyf/cow) **欢迎在 develop branch 进行开发并发送 pull request :)** @@ -112,6 +112,7 @@ COW 默认配置下检测到被墙后,过两分钟再次尝试直连也是为 Bug reporter: GitHub users: glacjay, trawor, Blaskyy, lucifer9, zellux, xream, hieixu, fantasticfears, perrywky, JayXon, graminc, WingGao, polong, dallascao + Twitter users: @shao222 @glacjay 对 0.3 版本的 COW 提出了让它更加自动化的建议,使我重新考虑 COW 的设计目标并且改进成 0.5 版本之后的工作方式。 diff --git a/config.go b/config.go index ddb97455..d13e367b 100644 --- a/config.go +++ b/config.go @@ -14,7 +14,7 @@ import ( ) const ( - version = "0.7.3" + version = "0.7.4" defaultListenAddr = "127.0.0.1:7777" ) @@ -388,6 +388,8 @@ func parseConfig(path string) { } defer f.Close() + IgnoreUTF8BOM(f) + fr := bufio.NewReader(f) parser := reflect.ValueOf(configParser{}) diff --git a/http.go b/http.go index 6f59d8f5..535f2a92 100644 --- a/http.go +++ b/http.go @@ -49,7 +49,8 @@ type Request struct { tryCnt byte } -var zeroRequest = Request{} +// Assume keep-alive request by default. +var zeroRequest = Request{Header: Header{ConnectionKeepAlive: true}} func (r *Request) reset() { b := r.rawByte @@ -284,8 +285,8 @@ func ParseRequestURIBytes(rawurl []byte) (*URL, error) { // headers of interest to a proxy // Define them as constant and use editor's completion to avoid typos. -// Note RFC2616 only says about "Connection", no "Proxy-Connection", but firefox -// send this header. +// Note RFC2616 only says about "Connection", no "Proxy-Connection", but +// Firefox and Safari send this header along with "Connection" header. // See more at http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/web-proxy-connection-header.html const ( headerConnection = "connection" @@ -301,8 +302,9 @@ const ( headerUpgrade = "upgrade" headerExpect = "expect" - fullHeaderConnection = "Connection: keep-alive\r\n" - fullHeaderTransferEncoding = "Transfer-Encoding: chunked\r\n" + fullHeaderConnectionKeepAlive = "Connection: keep-alive\r\n" + fullHeaderConnectionClose = "Connection: close\r\n" + fullHeaderTransferEncoding = "Transfer-Encoding: chunked\r\n" ) // Using Go's method expression @@ -337,10 +339,15 @@ var hopByHopHeader = map[string]bool{ type HeaderParserFunc func(*Header, []byte, *bytes.Buffer) error +// Used by both "Connection" and "Proxy-Connection" header. COW always adds +// connection header at the end of a request/response (in parseRequest and +// parseResponse), no matter whether the original one has this header or not. +// This will change the order of headers, but should be OK as RFC2616 4.2 says +// header order is not significant. (Though general-header first is "good- +// practice".) func (h *Header) parseConnection(s []byte, raw *bytes.Buffer) error { ASCIIToLowerInplace(s) - h.ConnectionKeepAlive = bytes.Contains(s, []byte("keep-alive")) - raw.WriteString(fullHeaderConnection) + h.ConnectionKeepAlive = !bytes.Contains(s, []byte("close")) return nil } @@ -515,9 +522,10 @@ func parseRequest(c *clientConn, r *Request) (err error) { errl.Printf("Parsing request header: %v\n", err) return err } - if !r.ConnectionKeepAlive { - // Always add one connection header for request - r.raw.WriteString(fullHeaderConnection) + if r.ConnectionKeepAlive { + r.raw.WriteString(fullHeaderConnectionKeepAlive) + } else { + r.raw.WriteString(fullHeaderConnectionClose) } // The spec says proxy must add Via header. polipo disables this by // default, and I don't want to let others know the user is using COW, so @@ -615,10 +623,14 @@ func parseResponse(sv *serverConn, r *Request, rp *Response) (err error) { if !rp.ConnectionKeepAlive && !rp.Chunking && rp.ContLen == -1 { rp.raw.WriteString("Transfer-Encoding: chunked\r\n") } - if !rp.ConnectionKeepAlive { - rp.raw.WriteString("Connection: keep-alive\r\n") + // Whether COW should respond with keep-alive depends on client request, + // not server response. + if r.ConnectionKeepAlive { + rp.raw.WriteString(fullHeaderConnectionKeepAlive) + rp.raw.WriteString(fullKeepAliveHeader) + } else { + rp.raw.WriteString(fullHeaderConnectionClose) } - rp.raw.WriteString(keepAliveHeader) rp.raw.WriteString(CRLF) return nil diff --git a/http_test.go b/http_test.go index 16b0b05e..53e77082 100644 --- a/http_test.go +++ b/http_test.go @@ -88,10 +88,10 @@ func TestParseHeader(t *testing.T) { header *Header }{ {"content-length: 64\r\nConnection: keep-alive\r\n\r\n", - "content-length: 64\r\nConnection: keep-alive\r\n", + "content-length: 64\r\n", &Header{ContLen: 64, Chunking: false, ConnectionKeepAlive: true}}, {"Connection: keep-alive\r\nKeep-Alive: timeout=10\r\nTransfer-Encoding: chunked\r\nTE: trailers\r\n\r\n", - "Connection: keep-alive\r\nTransfer-Encoding: chunked\r\n", + "Transfer-Encoding: chunked\r\n", &Header{ContLen: -1, Chunking: true, ConnectionKeepAlive: true, KeepAlive: 10 * time.Second}}, /* diff --git a/install-cow.sh b/install-cow.sh index 320227c3..01a7acb8 100755 --- a/install-cow.sh +++ b/install-cow.sh @@ -1,6 +1,6 @@ #!/bin/bash -version=0.7.3 +version=0.7.4 arch=`uname -m` case $arch in diff --git a/proxy.go b/proxy.go index 9c783784..81a45e9c 100644 --- a/proxy.go +++ b/proxy.go @@ -37,7 +37,7 @@ var httpBuf = leakybuf.NewLeakyBuf(512, httpBufSize) // very conservative and easy to cause problem if we are not careful.) const clientConnTimeout = 5 * time.Second const clientMaxTimeoutCnt = 2 -const keepAliveHeader = "Keep-Alive: timeout=10\r\n" +const fullKeepAliveHeader = "Keep-Alive: timeout=10\r\n" // Remove idle server connection every cleanServerInterval second. const cleanServerInterval = 5 * time.Second diff --git a/util.go b/util.go index 8373f97a..e253fe85 100644 --- a/util.go +++ b/util.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "crypto/md5" "errors" "fmt" @@ -504,3 +505,22 @@ func stringHash(s string) (hash uint64) { } return } + +// IgnoreUTF8BOM consumes UTF-8 encoded BOM character if present in the file. +func IgnoreUTF8BOM(f *os.File) error { + bom := make([]byte, 3) + n, err := f.Read(bom) + if err != nil { + return err + } + if n != 3 { + return nil + } + if bytes.Equal(bom, []byte{0xEF, 0xBB, 0xBF}) { + debug.Println("UTF-8 BOM found") + return nil + } + // No BOM found, seek back + _, err = f.Seek(-3, 1) + return err +}