[
  {
    "path": ".gitignore",
    "content": "*.sublime*\ncow-proxy\nbin\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: go\ngo:\n  - 1.4.2\nenv:\n  - TRAVIS=\"yes\"\ninstall:\n  - go get github.com/shadowsocks/shadowsocks-go/shadowsocks\n  - go get github.com/cyfdecyf/bufio\n  - go get github.com/cyfdecyf/leakybuf\n  - go get github.com/cyfdecyf/color\nscript:\n  - pushd $TRAVIS_BUILD_DIR\n  - go test -v\n  - ./script/test.sh\n  - popd\n"
  },
  {
    "path": "CHANGELOG",
    "content": "0.9.8 (2016-06-19)\n    * Fix OTA support bug in shadowsocks (report by @defia)\n    * Fix WeChat image url problem (by @breath-co2 @haha1903)\n    * Fix connection reset detection (by @fgid)\n\n0.9.7 (2016-05-04)\n    * Support shadowsocks OTA\n\n0.9.6 (2015-06-07)\n    * Reload config by sending SIGUSR1 on Unix system\n    * Load blocked/direct/stat file from same directory as rc file by default\n    * Allow user to specify blocked/direct/stat file path\n    * Detect arm without vfp in install script.\n    * Fix estimate timeout bug\n\n0.9.5 (2015-05-12)\n    * Support new encryption method \"chacha20\" and \"salsa20\"\n    * Avoid biased parent proxy selection for hash load balacing\n    * Fix AirDrop on OS X when using PAC\n    * Fix failed start with corrupted stat file\n    * Support changing the estimate timeout target\n\n0.9.4 (2014-10-08)\n    * Bug fix (#179): close stat file after load\n\n0.9.3 (2014-09-21)\n    * Support new encryption method \"rc4-md5\"\n\n0.9.2 (2014-07-23)\n    * Reduce the possibility of encountering too many open file error\n    * New connection latency based load balancing\n    * Fix auto load plist for OS X\n    * Identify blocked site by HTTP error code\n\n0.9.1 (2013-12-20)\n    * Fix can't save site stat bug\n    * Improve install and startup script\n\n0.9 (2013-12-02)\n    * New feature: two COW servers can be connected using encrypted\n      connection, thus we have an encrypted HTTP proxy chain that can\n      be used to bypass the firewall\n    * Allow client to use HTTP basic authentication\n    * Simplify configuration syntax\n    * Better reuse for HTTP parent connections\n    * Reduce direct/blocked delta\n    * Generate new PAC every minute\n\n0.8 (2013-08-10)\n    * Share server connections between different clients\n    * Add tunnelAllowedPort option to limit ports CONNECT method can connect to\n    * Avoid timeout too soon for frequently visited direct sites\n    * Fix reporting malformed requests in two cases when request has body:\n      - Authenticate requests\n      - Error occurred before request is sent\n    * Support multi-lined headers\n    * Change client connection timeout to 15s\n    * Change as direct delta to 15\n    * Provide ARMv5 binary\n\n0.7.6 (2013-07-28)\n    * Fix bug for close connection response with no body\n    * Fix response not keep alive by default\n    * Always try parent proxy upon DNS/connection error\n    * Do not take special handling on log with debug option\n    * Add proxy status statistics in debug code\n\n0.7.5 (2013-07-25)\n    * Fix crash on IPv6 client authentication\n    * Provide ARMv6 binary\n\n0.7.4 (2013-07-15)\n    * Fix adding extra connection header for client request with both\n      \"Proxy-Connection\" and \"Connection\" headers\n    * Ignore UTF-8 BOM in config file\n\n0.7.3 (2013-07-10)\n    * Handle 100-continue: do not forward expect header from client, ignore 100\n      continue response replied by some web servers\n    * For windows: add cow-hide.exe to run cow.exe as background process,\n      (provided by to xupefei)\n    * Filter sites covered by user specified domains on load\n    * Fix incorrectly changing header value to lower case: user name and\n      password can now contain upper case letters\n\n0.7.2 (2013-07-01)\n    * Close idle server connections earlier: avoid opening too many sockets\n    * Support authenticating multiple users (can limit port for each user)\n\n0.7.1 (2013-06-08)\n    * Fix parent proxy fallback bug\n\n0.7 (2013-06-07)\n    * Always use direct connection only for private IP addresses\n    * Support multiple HTTP/SOCKS5 parent proxies\n    * Support running multiple ssh server\n    * Fix client request read timeout handling\n    * Refactor parent proxy related code\n\n0.6.3 (2013-05-27)\n    * Support more shadowsocks encryption method\n    * Fix several windows network error detection issues (dirty hack)\n\n0.6.2 (2013-05-17)\n    * Support multiple shadowsocks servers\n    * Simple load balancing: backup or hash strategy\n    * PAC fix: do not add domains with blocked host/sub domain\n    * Remove some no longer working command line options\n\n0.6.1 (2013-03-14)\n\n    * Avoid using too much memory to hold http requests\n    * Support http parent proxy basic authentication\n    * For windows: add cow-taskbar.exe to hide cmd window to status area\n    * Fix timeout error detection\n    * Some bug fixes\n\n0.6 (2013-03-03)\n\n    * Allow user to specify proxy address in PAC\n    * Performance optimization\n    * More tolerant with HTTP servers and clients\n    * Some bug fixes\n\n0.5.1 (2013-02-10)\n\n    * Handle blocked site that will return EOF\n    * Small bug fixes\n\n0.5 (2013-02-07)\n\n    * Support parent HTTP proxy (such as goagent)\n    * Work more automatically: because of this, updateBlocked, updateDirect,\n      autoRetry options and chou file are removed\n    * Record direct/blocked visit count to make blocked/direct site handling\n    more reliable\n    * Builtin common blocked/direct site list\n    * Periodically estimate timeout value to avoid considering direct site as\n    blocked with bad network connection\n    * Support specifying host in blocked/direct file\n    * User configurable timeout\n    * Better windows support: connection reset, timeout and DNS error detection\n      tested and works on XP\n    * Support listening multiple addresses\n    * Support IP based and user password authentication\n    * Various bug fixes\n\n0.3.5 (2012-12-23)\n\n    * Performance improvement by better buffer usage\n    * Allow specifying config file on command line\n    * Better windows support: Config and domain list file on windows are put in the same\n      directory as COW's binary. And they all have txt extension for easy editing\n    * Bug fix: convert HTTP/1.0 response to HTTP/1.1\n\n0.3.4 (2012-12-09)\n\n    * Support shadowsocks\n    * Reduce latency (maybe just a little, not measured)\n    * Allow specifying ssh server port in config file\n    * Bug fix: crash when handling flush error\n    * Bug fix: correctly handle web servers which use closed connection to\n      indicate end of response\n\n0.3.3 (2012-12-05)\n\n    * Keep HTTP CONNECT connection open. Avoid problems for Application which\n      uses long connection.\n    * Bug fix: crash when printing domain list inconsistency message.\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2012-2013 Chen Yufei. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n   * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n   * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README-en.md",
    "content": "# COW (Climb Over the Wall) proxy\n\nCOW is a HTTP proxy to simplify bypassing the great firewall. It tries to automatically identify blocked websites and only use parent proxy for those sites.\n\nCurrent version: 0.9.8 [CHANGELOG](CHANGELOG)\n[![Build Status](https://travis-ci.org/cyfdecyf/cow.png?branch=master)](https://travis-ci.org/cyfdecyf/cow)\n\n## Features\n\n- As a HTTP proxy, can be used by mobile devices\n- Supports HTTP, SOCKS5, [shadowsocks](https://github.com/clowwindy/shadowsocks/wiki/Shadowsocks-%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E) and COW itself as parent proxy\n  - Supports simple load balancing between multiple parent proxies\n- Automatically identify blocked websites, only use parent proxy for those sites\n- Generate and serve PAC file for browser to bypass COW for best performance\n  - Contain domains that can be directly accessed (recorded accoring to your visit history)\n\n# Quickstart\n\nInstall:\n\n- **OS X, Linux (x86, ARM):** Run the following command (also for update)\n\n        curl -L git.io/cow | bash\n\n  - All binaries are compiled on OS X, if ARM binary can't work, please download [Go ARM](https://storage.googleapis.com/golang/go1.6.2.linux-amd64.tar.gz) and install from source.\n- **Windows:** download from the [release page](https://github.com/cyfdecyf/cow/releases)\n- If you are familiar with Go, run `go get github.com/cyfdecyf/cow` to install from source.\n\nModify configuration file `~/.cow/rc` (OS X or Linux) or `rc.txt` (Windows). A simple example with the most important options:\n\n    # Line starting with # is comment and will be ignored\n    # Local proxy listen address\n    listen = http://127.0.0.1:7777\n\n    # SOCKS5 parent proxy\n    proxy = socks5://127.0.0.1:1080\n    # HTTP parent proxy\n    proxy = http://127.0.0.1:8080\n    proxy = http://user:password@127.0.0.1:8080\n    # shadowsocks parent proxy\n    proxy = ss://aes-128-cfb:password@1.2.3.4:8388\n    # cow parent proxy\n    proxy = cow://aes-128-cfb:password@1.2.3.4:8388\n\nSee [detailed configuration example](doc/sample-config/rc-en) for other features.\n\nThe PAC file can be accessed at `http://<listen>/pac`, for the above example: `http://127.0.0.1:7777/pac`.\n\nCommand line options can override options in the configuration file For more details, see the output of `cow -h`\n\n## Blocked and directly accessible sites list\n\nIn ideal situation, you don't need to specify which sites are blocked and which are not, but COW hasen't reached that goal. So you may need to manually specify this if COW made the wrong judgement.\n\n- `<dir containing rc file>/blocked` for blocked sites\n- `<dir containing rc file>/direct` for directly accessible sites\n- One line for each domain\n  - `google.com` means `*.google.com`\n  - You can use domains like `google.com.hk`\n\n# Technical details\n\n## Visited site recording\n\nCOW records all visited hosts and visit count in `stat` (which is a json file) under the same directory with config file.\n\n- **For unknown site, first try direct access, use parent proxy upon failure. After 2 minutes, try direct access again**\n  - Builtin [common blocked site](site_blocked.go) in order to reduce time to discover blockage and the use parent proxy\n- Hosts will be put into PAC after a few times of successful direct visit\n- Hosts will use parent proxy if direct access failed for a few times\n  - To avoid mistakes, will try direct access with some probability\n- Host will be deleted if not visited for a few days\n- Hosts under builtin/manually specified blocked and direct domains will not appear in `stat`\n\n## How does COW detect blocked sites\n\nUpon the following error, one domain is considered to be blocked\n\n  - Server connection reset\n  - Connection to server timeout\n  - Read from server timeout\n\nCOW will retry HTTP request upon these errors, But if there's some data sent back to the client, connection with the client will be dropped to signal error..\n\nServer connection reset is usually reliable in detecting blocked sites. But timeout is not. COW tries to estimate timeout value every 30 seconds, in order to avoid considering normal sites as blocked when network condition is bad. Revert to direct access after two minutes upon first blockage is also to avoid mistakes.\n\nIf automatica timeout retry causes problem for you, try to change `readTimeout` and `dialTimeout` in configuration.\n\n# Limitations\n\n- No caching, COW just passes traffic between clients and web servers\n  - For web browsing, browsers have their own cache\n- Blocked site detection is not always reliable\n\n# Acknowledgements\n\nRefer to [README.md](README.md).\n"
  },
  {
    "path": "README.md",
    "content": "# COW (Climb Over the Wall) proxy\n\nCOW 是一个简化穿墙的 HTTP 代理服务器。它能自动检测被墙网站，仅对这些网站使用二级代理。\n\n[English README](README-en.md).\n\n当前版本：0.9.8 [CHANGELOG](CHANGELOG)\n[![Build Status](https://travis-ci.org/cyfdecyf/cow.png?branch=master)](https://travis-ci.org/cyfdecyf/cow)\n\n**欢迎在 develop branch 进行开发并发送 pull request :)**\n\n## 功能\n\nCOW 的设计目标是自动化，理想情况下用户无需关心哪些网站无法访问，可直连网站也不会因为使用二级代理而降低访问速度。\n\n- 作为 HTTP 代理，可提供给移动设备使用；若部署在国内服务器上，可作为 APN 代理\n- 支持 HTTP, SOCKS5, [shadowsocks](https://github.com/clowwindy/shadowsocks/wiki/Shadowsocks-%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E) 和 cow 自身作为二级代理\n  - 可使用多个二级代理，支持简单的负载均衡\n- 自动检测网站是否被墙，仅对被墙网站使用二级代理\n- 自动生成包含直连网站的 PAC，访问这些网站时可绕过 COW\n  - 内置[常见可直连网站](site_direct.go)，如国内社交、视频、银行、电商等网站（可手工添加）\n\n# 快速开始\n\n安装：\n\n- **OS X, Linux (x86, ARM):** 执行以下命令（也可用于更新）\n\n        curl -L git.io/cow | bash\n\n  - 环境变量 `COW_INSTALLDIR` 可以指定安装的路径，若该环境变量不是目录则询问用户\n  - 所有 binary 在 OS X 上编译获得，若 ARM 版本可能无法工作，请下载 [Go ARM](https://storage.googleapis.com/golang/go1.6.2.linux-amd64.tar.gz) 后从源码安装\n- **Windows:** 从 [release 页面](https://github.com/cyfdecyf/cow/releases)下载\n- 熟悉 Go 的用户可用 `go get github.com/cyfdecyf/cow` 从源码安装\n\n编辑 `~/.cow/rc` (Linux) 或 `rc.txt` (Windows)，简单的配置例子如下：\n\n    #开头的行是注释，会被忽略\n    # 本地 HTTP 代理地址\n    # 配置 HTTP 和 HTTPS 代理时请填入该地址\n    # 若配置代理时有对所有协议使用该代理的选项，且你不清楚此选项的含义，请勾选\n    # 或者在自动代理配置中填入 http://127.0.0.1:7777/pac\n    listen = http://127.0.0.1:7777\n\n    # SOCKS5 二级代理\n    proxy = socks5://127.0.0.1:1080\n    # HTTP 二级代理\n    proxy = http://127.0.0.1:8080\n    proxy = http://user:password@127.0.0.1:8080\n    # shadowsocks 二级代理\n    proxy = ss://aes-128-cfb:password@1.2.3.4:8388\n    # cow 二级代理\n    proxy = cow://aes-128-cfb:password@1.2.3.4:8388\n\n使用 cow 协议的二级代理需要在国外服务器上安装 COW，并使用如下配置：\n\n    listen = cow://aes-128-cfb:password@0.0.0.0:8388\n\n完成配置后启动 COW 并配置好代理即可使用。\n\n# 详细使用说明\n\n配置文件在 Unix 系统上为 `~/.cow/rc`，Windows 上为 COW 所在目录的 `rc.txt` 文件。 **[样例配置](doc/sample-config/rc) 包含了所有选项以及详细的说明**，建议下载然后修改。\n\n启动 COW：\n\n- Unix 系统在命令行上执行 `cow &` (若 COW 不在 `PATH` 所在目录，请执行 `./cow &`)\n  - [Linux 启动脚本](doc/init.d/cow)，如何使用请参考注释（Debian 测试通过，其他 Linux 发行版应该也可使用）\n- Windows\n  - 双击 `cow-taskbar.exe`，隐藏到托盘执行\n  - 双击 `cow-hide.exe`，隐藏为后台程序执行\n  - 以上两者都会启动 `cow.exe`\n\nPAC url 为 `http://<listen address>/pac`，也可将浏览器的 HTTP/HTTPS 代理设置为 `listen address` 使所有网站都通过 COW 访问。\n\n**使用 PAC 可获得更好的性能，但若 PAC 中某网站从直连变成被封，浏览器会依然尝试直连。遇到这种情况可以暂时不使用 PAC 而总是走 HTTP 代理，让 COW 学习到新的被封网站。**\n\n命令行选项可以覆盖部分配置文件中的选项、打开 debug/request/reply 日志，执行 `cow -h` 来获取更多信息。\n\n## 手动指定被墙和直连网站\n\n**一般情况下无需手工指定被墙和直连网站，该功能只是是为了处理特殊情况和性能优化。**\n\n配置文件所在目录下的 `blocked` 和 `direct` 可指定被墙和直连网站（`direct` 中的 host 会添加到 PAC）。\nWindows 下文件名为 `blocked.txt` 和 `direct.txt`。\n\n- 每行一个域名或者主机名（COW 会先检查主机名是否在列表中，再检查域名）\n  - 二级域名如 `google.com` 相当于 `*.google.com`\n  - `com.hk`, `edu.cn` 等二级域名下的三级域名，作为二级域名处理。如 `google.com.hk` 相当于 `*.google.com.hk`\n  - 其他三级及以上域名/主机名做精确匹配，例如 `plus.google.com`\n\n# 技术细节\n\n## 访问网站记录\n\nCOW 在配置文件所在目录下的 `stat` json 文件中记录经常访问网站被墙和直连访问的次数。\n\n- **对未知网站，先尝试直接连接，失败后使用二级代理重试请求，2 分钟后再尝试直接**\n  - 内置[常见被墙网站](site_blocked.go)，减少检测被墙所需时间（可手工添加）\n- 直连访问成功一定次数后相应的 host 会添加到 PAC\n- host 被墙一定次数后会直接用二级代理访问\n  - 为避免误判，会以一定概率再次尝试直连访问\n- host 若一段时间没有访问会自动被删除（避免 `stat` 文件无限增长）\n- 内置网站列表和用户指定的网站不会出现在统计文件中\n\n## COW 如何检测被墙网站\n\nCOW 将以下错误认为是墙在作怪：\n\n- 服务器连接被重置 (connection reset)\n- 创建连接超时\n- 服务器读操作超时\n\n无论是普通的 HTTP GET 等请求还是 CONNECT 请求，失败后 COW 都会自动重试请求。（如果已经有内容发送回 client 则不会重试而是直接断开连接。）\n\n用连接被重置来判断被墙通常来说比较可靠，超时则不可靠。COW 每隔半分钟会尝试估算合适的超时间隔，避免在网络连接差的情况下把直连网站由于超时也当成被墙。\nCOW 默认配置下检测到被墙后，过两分钟再次尝试直连也是为了避免误判。\n\n如果超时自动重试给你造成了问题，请参考[样例配置](doc/sample-config/rc)高级选项中的 `readTimeout`, `dialTimeout` 选项。\n\n## 限制\n\n- 不提供 cache\n- 不支持 HTTP pipeline（Chrome, Firefox 默认都没开启 pipeline，支持这个功能容易增加问题而好处并不明显）\n\n# 致谢 (Acknowledgements)\n\n贡献代码：\n\n- @fzerorubigd: various bug fixes and feature implementation\n- @tevino: http parent proxy basic authentication\n- @xupefei: 提供 cow-hide.exe 以在 windows 上在后台执行 cow.exe\n- @sunteya: 改进启动和安装脚本\n\nBug reporter:\n\n- GitHub users: glacjay, trawor, Blaskyy, lucifer9, zellux, xream, hieixu, fantasticfears, perrywky, JayXon, graminc, WingGao, polong, dallascao, luosheng\n- Twitter users: 特别感谢 @shao222 多次帮助测试新版并报告了不少 bug, @xixitalk\n\n@glacjay 对 0.3 版本的 COW 提出了让它更加自动化的建议，使我重新考虑 COW 的设计目标并且改进成 0.5 版本之后的工作方式。\n"
  },
  {
    "path": "auth.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/cyfdecyf/bufio\"\n\t\"net\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"text/template\"\n\t\"time\"\n)\n\nconst (\n\tauthRealm       = \"cow proxy\"\n\tauthRawBodyTmpl = `<!DOCTYPE html>\n<html>\n\t<head> <title>COW Proxy</title> </head>\n\t<body>\n\t\t<h1>407 Proxy authentication required</h1>\n\t\t<hr />\n\t\tGenerated by <i>COW</i>\n\t</body>\n</html>\n`\n)\n\ntype netAddr struct {\n\tip   net.IP\n\tmask net.IPMask\n}\n\ntype authUser struct {\n\t// user name is the key to auth.user, no need to store here\n\tpasswd string\n\tha1    string // used in request digest, initialized ondemand\n\tport   uint16 // 0 means any port\n}\n\nvar auth struct {\n\trequired bool\n\n\tuser map[string]*authUser\n\n\tallowedClient []netAddr\n\n\tauthed *TimeoutSet // cache authenticated users based on ip\n\n\ttemplate *template.Template\n}\n\nfunc (au *authUser) initHA1(user string) {\n\tif au.ha1 == \"\" {\n\t\tau.ha1 = md5sum(user + \":\" + authRealm + \":\" + au.passwd)\n\t}\n}\n\nfunc parseUserPasswd(userPasswd string) (user string, au *authUser, err error) {\n\tarr := strings.Split(userPasswd, \":\")\n\tn := len(arr)\n\tif n == 1 || n > 3 {\n\t\terr = errors.New(\"user password: \" + userPasswd +\n\t\t\t\" syntax wrong, should be username:password[:port]\")\n\t\treturn\n\t}\n\tuser, passwd := arr[0], arr[1]\n\tif user == \"\" || passwd == \"\" {\n\t\terr = errors.New(\"user password \" + userPasswd +\n\t\t\t\" should not contain empty user name or password\")\n\t\treturn \"\", nil, err\n\t}\n\tvar port int\n\tif n == 3 && arr[2] != \"\" {\n\t\tport, err = strconv.Atoi(arr[2])\n\t\tif err != nil || port <= 0 || port > 0xffff {\n\t\t\terr = errors.New(\"user password: \" + userPasswd + \" invalid port\")\n\t\t\treturn \"\", nil, err\n\t\t}\n\t}\n\tau = &authUser{passwd, \"\", uint16(port)}\n\treturn user, au, nil\n}\n\nfunc parseAllowedClient(val string) {\n\tif val == \"\" {\n\t\treturn\n\t}\n\tarr := strings.Split(val, \",\")\n\tauth.allowedClient = make([]netAddr, len(arr))\n\tfor i, v := range arr {\n\t\ts := strings.TrimSpace(v)\n\t\tipAndMask := strings.Split(s, \"/\")\n\t\tif len(ipAndMask) > 2 {\n\t\t\tFatal(\"allowedClient syntax error: client should be the form ip/nbitmask\")\n\t\t}\n\t\tip := net.ParseIP(ipAndMask[0])\n\t\tif ip == nil {\n\t\t\tFatalf(\"allowedClient syntax error %s: ip address not valid\\n\", s)\n\t\t}\n\t\tvar mask net.IPMask\n\t\tif len(ipAndMask) == 2 {\n\t\t\tnbit, err := strconv.Atoi(ipAndMask[1])\n\t\t\tif err != nil {\n\t\t\t\tFatalf(\"allowedClient syntax error %s: %v\\n\", s, err)\n\t\t\t}\n\t\t\tif nbit > 32 {\n\t\t\t\tFatal(\"allowedClient error: mask number should <= 32\")\n\t\t\t}\n\t\t\tmask = NewNbitIPv4Mask(nbit)\n\t\t} else {\n\t\t\tmask = NewNbitIPv4Mask(32)\n\t\t}\n\t\tauth.allowedClient[i] = netAddr{ip.Mask(mask), mask}\n\t}\n}\n\nfunc addUserPasswd(val string) {\n\tif val == \"\" {\n\t\treturn\n\t}\n\tuser, au, err := parseUserPasswd(val)\n\tdebug.Println(\"user:\", user, \"port:\", au.port)\n\tif err != nil {\n\t\tFatal(err)\n\t}\n\tif _, ok := auth.user[user]; ok {\n\t\tFatal(\"duplicate user:\", user)\n\t}\n\tauth.user[user] = au\n}\n\nfunc loadUserPasswdFile(file string) {\n\tif file == \"\" {\n\t\treturn\n\t}\n\tf, err := os.Open(file)\n\tif err != nil {\n\t\tFatal(\"error opening user passwd fle:\", err)\n\t}\n\n\tr := bufio.NewReader(f)\n\ts := bufio.NewScanner(r)\n\tfor s.Scan() {\n\t\taddUserPasswd(s.Text())\n\t}\n\tf.Close()\n}\n\nfunc initAuth() {\n\tif config.UserPasswd != \"\" ||\n\t\tconfig.UserPasswdFile != \"\" ||\n\t\tconfig.AllowedClient != \"\" {\n\t\tauth.required = true\n\t} else {\n\t\treturn\n\t}\n\n\tauth.user = make(map[string]*authUser)\n\n\taddUserPasswd(config.UserPasswd)\n\tloadUserPasswdFile(config.UserPasswdFile)\n\tparseAllowedClient(config.AllowedClient)\n\n\tauth.authed = NewTimeoutSet(time.Duration(config.AuthTimeout) * time.Hour)\n\n\trawTemplate := \"HTTP/1.1 407 Proxy Authentication Required\\r\\n\" +\n\t\t\"Proxy-Authenticate: Digest realm=\\\"\" + authRealm + \"\\\", nonce=\\\"{{.Nonce}}\\\", qop=\\\"auth\\\"\\r\\n\" +\n\t\t\"Content-Type: text/html\\r\\n\" +\n\t\t\"Cache-Control: no-cache\\r\\n\" +\n\t\t\"Content-Length: \" + fmt.Sprintf(\"%d\", len(authRawBodyTmpl)) + \"\\r\\n\\r\\n\" + authRawBodyTmpl\n\tvar err error\n\tif auth.template, err = template.New(\"auth\").Parse(rawTemplate); err != nil {\n\t\tFatal(\"internal error generating auth template:\", err)\n\t}\n}\n\n// Return err = nil if authentication succeed. nonce would be not empty if\n// authentication is needed, and should be passed back on subsequent call.\nfunc Authenticate(conn *clientConn, r *Request) (err error) {\n\tclientIP, _, _ := net.SplitHostPort(conn.RemoteAddr().String())\n\tif auth.authed.has(clientIP) {\n\t\tdebug.Printf(\"%s has already authed\\n\", clientIP)\n\t\treturn\n\t}\n\tif authIP(clientIP) { // IP is allowed\n\t\treturn\n\t}\n\terr = authUserPasswd(conn, r)\n\tif err == nil {\n\t\tauth.authed.add(clientIP)\n\t}\n\treturn\n}\n\n// authIP checks whether the client ip address matches one in allowedClient.\n// It uses a sequential search.\nfunc authIP(clientIP string) bool {\n\tip := net.ParseIP(clientIP)\n\tif ip == nil {\n\t\tpanic(\"authIP should always get IP address\")\n\t}\n\n\tfor _, na := range auth.allowedClient {\n\t\tif ip.Mask(na.mask).Equal(na.ip) {\n\t\t\tdebug.Printf(\"client ip %s allowed\\n\", clientIP)\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc genNonce() string {\n\tbuf := new(bytes.Buffer)\n\tfmt.Fprintf(buf, \"%x\", time.Now().Unix())\n\treturn buf.String()\n}\n\nfunc calcRequestDigest(kv map[string]string, ha1, method string) string {\n\t// Refer to rfc2617 section 3.2.2.1 Request-Digest\n\tarr := []string{\n\t\tha1,\n\t\tkv[\"nonce\"],\n\t\tkv[\"nc\"],\n\t\tkv[\"cnonce\"],\n\t\t\"auth\",\n\t\tmd5sum(method + \":\" + kv[\"uri\"]),\n\t}\n\treturn md5sum(strings.Join(arr, \":\"))\n}\n\nfunc checkProxyAuthorization(conn *clientConn, r *Request) error {\n\tif debug {\n\t\tdebug.Printf(\"cli(%s) authorization: %s\\n\", conn.RemoteAddr(), r.ProxyAuthorization)\n\t}\n\n\tarr := strings.SplitN(r.ProxyAuthorization, \" \", 2)\n\tif len(arr) != 2 {\n\t\treturn errors.New(\"auth: malformed ProxyAuthorization header: \" + r.ProxyAuthorization)\n\t}\n\tauthMethod := strings.ToLower(strings.TrimSpace(arr[0]))\n\tif authMethod == \"digest\" {\n\t\treturn authDigest(conn, r, arr[1])\n\t} else if authMethod == \"basic\" {\n\t\treturn authBasic(conn, arr[1])\n\t}\n\treturn errors.New(\"auth: method \" + arr[0] + \" unsupported, must use digest\")\n}\n\nfunc authPort(conn *clientConn, user string, au *authUser) error {\n\tif au.port == 0 {\n\t\treturn nil\n\t}\n\t_, portStr, _ := net.SplitHostPort(conn.LocalAddr().String())\n\tport, _ := strconv.Atoi(portStr)\n\tif uint16(port) != au.port {\n\t\terrl.Printf(\"cli(%s) auth: user %s port not match\\n\", conn.RemoteAddr(), user)\n\t\treturn errAuthRequired\n\t}\n\treturn nil\n}\n\nfunc authBasic(conn *clientConn, userPasswd string) error {\n\tb64, err := base64.StdEncoding.DecodeString(userPasswd)\n\tif err != nil {\n\t\treturn errors.New(\"auth:\" + err.Error())\n\t}\n\tarr := strings.Split(string(b64), \":\")\n\tif len(arr) != 2 {\n\t\treturn errors.New(\"auth: malformed basic auth user:passwd\")\n\t}\n\tuser := arr[0]\n\tpasswd := arr[1]\n\n\tau, ok := auth.user[user]\n\tif !ok || au.passwd != passwd {\n\t\treturn errAuthRequired\n\t}\n\treturn authPort(conn, user, au)\n}\n\nfunc authDigest(conn *clientConn, r *Request, keyVal string) error {\n\tauthHeader := parseKeyValueList(keyVal)\n\tif len(authHeader) == 0 {\n\t\treturn errors.New(\"auth: empty authorization list\")\n\t}\n\tnonceTime, err := strconv.ParseInt(authHeader[\"nonce\"], 16, 64)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"auth: nonce %v\", err)\n\t}\n\t// If nonce time too early, reject. iOS will create a new connection to do\n\t// authentication.\n\tif time.Now().Sub(time.Unix(nonceTime, 0)) > time.Minute {\n\t\treturn errAuthRequired\n\t}\n\n\tuser := authHeader[\"username\"]\n\tau, ok := auth.user[user]\n\tif !ok {\n\t\terrl.Printf(\"cli(%s) auth: no such user: %s\\n\", conn.RemoteAddr(), authHeader[\"username\"])\n\t\treturn errAuthRequired\n\t}\n\n\tif err = authPort(conn, user, au); err != nil {\n\t\treturn err\n\t}\n\tif authHeader[\"qop\"] != \"auth\" {\n\t\treturn errors.New(\"auth: qop wrong: \" + authHeader[\"qop\"])\n\t}\n\tresponse, ok := authHeader[\"response\"]\n\tif !ok {\n\t\treturn errors.New(\"auth: no request-digest response\")\n\t}\n\n\tau.initHA1(user)\n\tdigest := calcRequestDigest(authHeader, au.ha1, r.Method)\n\tif response != digest {\n\t\terrl.Printf(\"cli(%s) auth: digest not match, maybe password wrong\", conn.RemoteAddr())\n\t\treturn errAuthRequired\n\t}\n\treturn nil\n}\n\nfunc authUserPasswd(conn *clientConn, r *Request) (err error) {\n\tif r.ProxyAuthorization != \"\" {\n\t\t// client has sent authorization header\n\t\terr = checkProxyAuthorization(conn, r)\n\t\tif err == nil {\n\t\t\treturn\n\t\t} else if err != errAuthRequired {\n\t\t\tsendErrorPage(conn, statusBadReq, \"Bad authorization request\", err.Error())\n\t\t\treturn\n\t\t}\n\t\t// auth required to through the following\n\t}\n\n\tnonce := genNonce()\n\tdata := struct {\n\t\tNonce string\n\t}{\n\t\tnonce,\n\t}\n\tbuf := new(bytes.Buffer)\n\tif err := auth.template.Execute(buf, data); err != nil {\n\t\treturn fmt.Errorf(\"error generating auth response: %v\", err)\n\t}\n\tif bool(debug) && verbose {\n\t\tdebug.Printf(\"authorization response:\\n%s\", buf.String())\n\t}\n\tif _, err := conn.Write(buf.Bytes()); err != nil {\n\t\treturn fmt.Errorf(\"send auth response error: %v\", err)\n\t}\n\treturn errAuthRequired\n}\n"
  },
  {
    "path": "auth_test.go",
    "content": "package main\n\nimport (\n\t\"net\"\n\t\"testing\"\n)\n\nfunc TestParseUserPasswd(t *testing.T) {\n\ttestData := []struct {\n\t\tval  string\n\t\tuser string\n\t\tau   *authUser\n\t}{\n\t\t{\"foo:bar\", \"foo\", &authUser{\"bar\", \"\", 0}},\n\t\t{\"foo:bar:-1\", \"\", nil},\n\t\t{\"hello:world:\", \"hello\", &authUser{\"world\", \"\", 0}},\n\t\t{\"hello:world:0\", \"\", nil},\n\t\t{\"hello:world:1024\", \"hello\", &authUser{\"world\", \"\", 1024}},\n\t\t{\"hello:world:65535\", \"hello\", &authUser{\"world\", \"\", 65535}},\n\t}\n\n\tfor _, td := range testData {\n\t\tuser, au, err := parseUserPasswd(td.val)\n\t\tif td.au == nil {\n\t\t\tif err == nil {\n\t\t\t\tt.Error(td.val, \"should return error\")\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif td.user != user {\n\t\t\tt.Error(td.val, \"user should be:\", td.user, \"got:\", user)\n\t\t}\n\t\tif td.au.passwd != au.passwd {\n\t\t\tt.Error(td.val, \"passwd should be:\", td.au.passwd, \"got:\", au.passwd)\n\t\t}\n\t\tif td.au.port != au.port {\n\t\t\tt.Error(td.val, \"port should be:\", td.au.port, \"got:\", au.port)\n\t\t}\n\t}\n}\n\nfunc TestCalcDigest(t *testing.T) {\n\ta1 := md5sum(\"cyf\" + \":\" + authRealm + \":\" + \"wlx\")\n\tauth := map[string]string{\n\t\t\"nonce\":  \"50ed159c3b707061418bbb14\",\n\t\t\"nc\":     \"00000001\",\n\t\t\"cnonce\": \"6c46874228c087eb\",\n\t\t\"uri\":    \"/\",\n\t}\n\tconst targetDigest = \"bad1cb3526e4b257a62cda10f7c25aad\"\n\n\tdigest := calcRequestDigest(auth, a1, \"GET\")\n\tif digest != targetDigest {\n\t\tt.Errorf(\"authentication digest calculation wrong, got: %x, should be: %s\\n\", digest, targetDigest)\n\t}\n}\n\nfunc TestParseAllowedClient(t *testing.T) {\n\tparseAllowedClient(\"\") // this should not cause error\n\n\tparseAllowedClient(\"192.168.1.1/16, 192.169.1.2\")\n\n\tna := &auth.allowedClient[0]\n\tif !na.ip.Equal(net.ParseIP(\"192.168.0.0\")) {\n\t\tt.Error(\"ParseAllowedClient 192.168.1.1/16 ip error, got ip:\", na.ip)\n\t}\n\tmask := []byte(na.mask)\n\tif mask[0] != 0xff || mask[1] != 0xff || mask[2] != 0 || mask[3] != 0 {\n\t\tt.Error(\"ParseAllowedClient 192.168.1.1/16 mask error\")\n\t}\n\n\tna = &auth.allowedClient[1]\n\tif !na.ip.Equal(net.ParseIP(\"192.169.1.2\")) {\n\t\tt.Error(\"ParseAllowedClient 192.169.1.2 ip error\")\n\t}\n\tmask = []byte(na.mask)\n\tif mask[0] != 0xff || mask[1] != 0xff || mask[2] != 0xff || mask[3] != 0xff {\n\t\tt.Error(\"ParseAllowedClient 192.169.1.2 mask error\")\n\t}\n}\n\nfunc TestAuthIP(t *testing.T) {\n\tparseAllowedClient(\"192.168.0.0/16, 192.169.2.1, 10.0.0.0/8, 8.8.8.8\")\n\n\tvar testData = []struct {\n\t\tip      string\n\t\tallowed bool\n\t}{\n\t\t{\"10.1.2.3\", true},\n\t\t{\"192.168.1.2\", true},\n\t\t{\"192.169.2.1\", true},\n\t\t{\"192.169.2.2\", false},\n\t\t{\"8.8.8.8\", true},\n\t\t{\"1.2.3.4\", false},\n\t}\n\n\tfor _, td := range testData {\n\t\tif authIP(td.ip) != td.allowed {\n\t\t\tif td.allowed {\n\t\t\t\tt.Errorf(\"%s should be allowed\\n\", td.ip)\n\t\t\t} else {\n\t\t\t\tt.Errorf(\"%s should NOT be allowed\\n\", td.ip)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "config.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/cyfdecyf/bufio\"\n)\n\nconst (\n\tversion               = \"0.9.8\"\n\tdefaultListenAddr     = \"127.0.0.1:7777\"\n\tdefaultEstimateTarget = \"example.com\"\n)\n\ntype LoadBalanceMode byte\n\nconst (\n\tloadBalanceBackup LoadBalanceMode = iota\n\tloadBalanceHash\n\tloadBalanceLatency\n)\n\n// allow the same tunnel ports as polipo\nvar defaultTunnelAllowedPort = []string{\n\t\"22\", \"80\", \"443\", // ssh, http, https\n\t\"873\",                      // rsync\n\t\"143\", \"220\", \"585\", \"993\", // imap, imap3, imap4-ssl, imaps\n\t\"109\", \"110\", \"473\", \"995\", // pop2, pop3, hybrid-pop, pop3s\n\t\"5222\", \"5269\", // jabber-client, jabber-server\n\t\"2401\", \"3690\", \"9418\", // cvspserver, svn, git\n}\n\ntype Config struct {\n\tRcFile      string          // config file\n\tLogFile     string          // path for log file\n\tAlwaysProxy bool            // whether we should alwyas use parent proxy\n\tLoadBalance LoadBalanceMode // select load balance mode\n\n\tTunnelAllowedPort map[string]bool // allowed ports to create tunnel\n\n\tSshServer []string\n\n\t// authenticate client\n\tUserPasswd     string\n\tUserPasswdFile string // file that contains user:passwd:[port] pairs\n\tAllowedClient  string\n\tAuthTimeout    time.Duration\n\n\t// advanced options\n\tDialTimeout time.Duration\n\tReadTimeout time.Duration\n\n\tCore         int\n\tDetectSSLErr bool\n\n\tHttpErrorCode int\n\n\tdir         string // directory containing config file\n\tStatFile    string // Path for stat file\n\tBlockedFile string // blocked sites specified by user\n\tDirectFile  string // direct sites specified by user\n\n\t// not configurable in config file\n\tPrintVer        bool\n\tEstimateTimeout bool   // Whether to run estimateTimeout().\n\tEstimateTarget  string // Timeout estimate target site.\n\n\t// not config option\n\tsaveReqLine bool // for http and cow parent, should save request line from client\n}\n\nvar config Config\nvar configNeedUpgrade bool // whether should upgrade config file\n\nfunc printVersion() {\n\tfmt.Println(\"cow version\", version)\n}\n\nfunc initConfig(rcFile string) {\n\tconfig.dir = path.Dir(rcFile)\n\tconfig.BlockedFile = path.Join(config.dir, blockedFname)\n\tconfig.DirectFile = path.Join(config.dir, directFname)\n\tconfig.StatFile = path.Join(config.dir, statFname)\n\n\tconfig.DetectSSLErr = false\n\tconfig.AlwaysProxy = false\n\n\tconfig.AuthTimeout = 2 * time.Hour\n\tconfig.DialTimeout = defaultDialTimeout\n\tconfig.ReadTimeout = defaultReadTimeout\n\n\tconfig.TunnelAllowedPort = make(map[string]bool)\n\tfor _, port := range defaultTunnelAllowedPort {\n\t\tconfig.TunnelAllowedPort[port] = true\n\t}\n\n\tconfig.EstimateTarget = defaultEstimateTarget\n}\n\n// Whether command line options specifies listen addr\nvar cmdHasListenAddr bool\n\nfunc parseCmdLineConfig() *Config {\n\tvar c Config\n\tvar listenAddr string\n\n\tflag.StringVar(&c.RcFile, \"rc\", \"\", \"config file, defaults to $HOME/.cow/rc on Unix, ./rc.txt on Windows\")\n\t// Specifying listen default value to StringVar would override config file options\n\tflag.StringVar(&listenAddr, \"listen\", \"\", \"listen address, disables listen in config\")\n\tflag.IntVar(&c.Core, \"core\", 2, \"number of cores to use\")\n\tflag.StringVar(&c.LogFile, \"logFile\", \"\", \"write output to file\")\n\tflag.BoolVar(&c.PrintVer, \"version\", false, \"print version\")\n\tflag.BoolVar(&c.EstimateTimeout, \"estimate\", true, \"enable/disable estimate timeout\")\n\n\tflag.Parse()\n\n\tif c.RcFile == \"\" {\n\t\tc.RcFile = getDefaultRcFile()\n\t} else {\n\t\tc.RcFile = expandTilde(c.RcFile)\n\t}\n\tif err := isFileExists(c.RcFile); err != nil {\n\t\tFatal(\"fail to get config file:\", err)\n\t}\n\tinitConfig(c.RcFile)\n\n\tif listenAddr != \"\" {\n\t\tconfigParser{}.ParseListen(listenAddr)\n\t\tcmdHasListenAddr = true // must come after parse\n\t}\n\treturn &c\n}\n\nfunc parseBool(v, msg string) bool {\n\tswitch v {\n\tcase \"true\":\n\t\treturn true\n\tcase \"false\":\n\t\treturn false\n\tdefault:\n\t\tFatalf(\"%s should be true or false\\n\", msg)\n\t}\n\treturn false\n}\n\nfunc parseInt(val, msg string) (i int) {\n\tvar err error\n\tif i, err = strconv.Atoi(val); err != nil {\n\t\tFatalf(\"%s should be an integer\\n\", msg)\n\t}\n\treturn\n}\n\nfunc parseDuration(val, msg string) (d time.Duration) {\n\tvar err error\n\tif d, err = time.ParseDuration(val); err != nil {\n\t\tFatalf(\"%s %v\\n\", msg, err)\n\t}\n\treturn\n}\n\nfunc checkServerAddr(addr string) error {\n\t_, _, err := net.SplitHostPort(addr)\n\treturn err\n}\n\nfunc isUserPasswdValid(val string) bool {\n\tarr := strings.SplitN(val, \":\", 2)\n\tif len(arr) != 2 || arr[0] == \"\" || arr[1] == \"\" {\n\t\treturn false\n\t}\n\treturn true\n}\n\n// proxyParser provides functions to parse different types of parent proxy\ntype proxyParser struct{}\n\nfunc (p proxyParser) ProxySocks5(val string) {\n\tif err := checkServerAddr(val); err != nil {\n\t\tFatal(\"parent socks server\", err)\n\t}\n\tparentProxy.add(newSocksParent(val))\n}\n\nfunc (pp proxyParser) ProxyHttp(val string) {\n\tvar userPasswd, server string\n\n\tarr := strings.Split(val, \"@\")\n\tif len(arr) == 1 {\n\t\tserver = arr[0]\n\t} else if len(arr) == 2 {\n\t\tuserPasswd = arr[0]\n\t\tserver = arr[1]\n\t} else {\n\t\tFatal(\"http parent proxy contains more than one @:\", val)\n\t}\n\n\tif err := checkServerAddr(server); err != nil {\n\t\tFatal(\"parent http server\", err)\n\t}\n\n\tconfig.saveReqLine = true\n\n\tparent := newHttpParent(server)\n\tparent.initAuth(userPasswd)\n\tparentProxy.add(parent)\n}\n\n// Parse method:passwd@server:port\nfunc parseMethodPasswdServer(val string) (method, passwd, server string, err error) {\n\t// Use the right-most @ symbol to seperate method:passwd and server:port.\n\tidx := strings.LastIndex(val, \"@\")\n\tif idx == -1 {\n\t\terr = errors.New(\"requires both encrypt method and password\")\n\t\treturn\n\t}\n\n\tmethodPasswd := val[:idx]\n\tserver = val[idx+1:]\n\tif err = checkServerAddr(server); err != nil {\n\t\treturn\n\t}\n\n\t// Password can have : inside, but I don't recommend this.\n\tarr := strings.SplitN(methodPasswd, \":\", 2)\n\tif len(arr) != 2 {\n\t\terr = errors.New(\"method and password should be separated by :\")\n\t\treturn\n\t}\n\tmethod = arr[0]\n\tpasswd = arr[1]\n\treturn\n}\n\n// parse shadowsocks proxy\nfunc (pp proxyParser) ProxySs(val string) {\n\tmethod, passwd, server, err := parseMethodPasswdServer(val)\n\tif err != nil {\n\t\tFatal(\"shadowsocks parent\", err)\n\t}\n\tparent := newShadowsocksParent(server)\n\tparent.initCipher(method, passwd)\n\tparentProxy.add(parent)\n}\n\nfunc (pp proxyParser) ProxyCow(val string) {\n\tmethod, passwd, server, err := parseMethodPasswdServer(val)\n\tif err != nil {\n\t\tFatal(\"cow parent\", err)\n\t}\n\n\tif err := checkServerAddr(server); err != nil {\n\t\tFatal(\"parent cow server\", err)\n\t}\n\n\tconfig.saveReqLine = true\n\tparent := newCowParent(server, method, passwd)\n\tparentProxy.add(parent)\n}\n\n// listenParser provides functions to parse different types of listen addresses\ntype listenParser struct{}\n\nfunc (lp listenParser) ListenHttp(val string) {\n\tif cmdHasListenAddr {\n\t\treturn\n\t}\n\tarr := strings.Fields(val)\n\tif len(arr) > 2 {\n\t\tFatal(\"too many fields in listen = http://\", val)\n\t}\n\n\tvar addr, addrInPAC string\n\taddr = arr[0]\n\tif len(arr) == 2 {\n\t\taddrInPAC = arr[1]\n\t}\n\n\tif err := checkServerAddr(addr); err != nil {\n\t\tFatal(\"listen http server\", err)\n\t}\n\taddListenProxy(newHttpProxy(addr, addrInPAC))\n}\n\nfunc (lp listenParser) ListenCow(val string) {\n\tif cmdHasListenAddr {\n\t\treturn\n\t}\n\tmethod, passwd, addr, err := parseMethodPasswdServer(val)\n\tif err != nil {\n\t\tFatal(\"listen cow\", err)\n\t}\n\taddListenProxy(newCowProxy(method, passwd, addr))\n}\n\n// configParser provides functions to parse options in config file.\ntype configParser struct{}\n\nfunc (p configParser) ParseProxy(val string) {\n\tparser := reflect.ValueOf(proxyParser{})\n\tzeroMethod := reflect.Value{}\n\n\tarr := strings.Split(val, \"://\")\n\tif len(arr) != 2 {\n\t\tFatal(\"proxy has no protocol specified:\", val)\n\t}\n\tprotocol := arr[0]\n\n\tmethodName := \"Proxy\" + strings.ToUpper(protocol[0:1]) + protocol[1:]\n\tmethod := parser.MethodByName(methodName)\n\tif method == zeroMethod {\n\t\tFatalf(\"no such protocol \\\"%s\\\"\\n\", arr[0])\n\t}\n\targs := []reflect.Value{reflect.ValueOf(arr[1])}\n\tmethod.Call(args)\n}\n\nfunc (p configParser) ParseListen(val string) {\n\tif cmdHasListenAddr {\n\t\treturn\n\t}\n\n\tparser := reflect.ValueOf(listenParser{})\n\tzeroMethod := reflect.Value{}\n\n\tvar protocol, server string\n\tarr := strings.Split(val, \"://\")\n\tif len(arr) == 1 {\n\t\tprotocol = \"http\"\n\t\tserver = val\n\t\tconfigNeedUpgrade = true\n\t} else {\n\t\tprotocol = arr[0]\n\t\tserver = arr[1]\n\t}\n\n\tmethodName := \"Listen\" + strings.ToUpper(protocol[0:1]) + protocol[1:]\n\tmethod := parser.MethodByName(methodName)\n\tif method == zeroMethod {\n\t\tFatalf(\"no such listen protocol \\\"%s\\\"\\n\", arr[0])\n\t}\n\targs := []reflect.Value{reflect.ValueOf(server)}\n\tmethod.Call(args)\n}\n\nfunc (p configParser) ParseLogFile(val string) {\n\tconfig.LogFile = expandTilde(val)\n}\n\nfunc (p configParser) ParseAddrInPAC(val string) {\n\tconfigNeedUpgrade = true\n\tarr := strings.Split(val, \",\")\n\tfor i, s := range arr {\n\t\tif s == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\ts = strings.TrimSpace(s)\n\t\thost, _, err := net.SplitHostPort(s)\n\t\tif err != nil {\n\t\t\tFatal(\"proxy address in PAC\", err)\n\t\t}\n\t\tif host == \"0.0.0.0\" {\n\t\t\tFatal(\"can't use 0.0.0.0 as proxy address in PAC\")\n\t\t}\n\t\tif hp, ok := listenProxy[i].(*httpProxy); ok {\n\t\t\thp.addrInPAC = s\n\t\t} else {\n\t\t\tFatal(\"can't specify address in PAC for non http proxy\")\n\t\t}\n\t}\n}\n\nfunc (p configParser) ParseTunnelAllowedPort(val string) {\n\tarr := strings.Split(val, \",\")\n\tfor _, s := range arr {\n\t\ts = strings.TrimSpace(s)\n\t\tif _, err := strconv.Atoi(s); err != nil {\n\t\t\tFatal(\"tunnel allowed ports\", err)\n\t\t}\n\t\tconfig.TunnelAllowedPort[s] = true\n\t}\n}\n\nfunc (p configParser) ParseSocksParent(val string) {\n\tvar pp proxyParser\n\tpp.ProxySocks5(val)\n\tconfigNeedUpgrade = true\n}\n\nfunc (p configParser) ParseSshServer(val string) {\n\tarr := strings.Split(val, \":\")\n\tif len(arr) == 2 {\n\t\tval += \":22\"\n\t} else if len(arr) == 3 {\n\t\tif arr[2] == \"\" {\n\t\t\tval += \"22\"\n\t\t}\n\t} else {\n\t\tFatal(\"sshServer should be in the form of: user@server:local_socks_port[:server_ssh_port]\")\n\t}\n\t// add created socks server\n\tp.ParseSocksParent(\"127.0.0.1:\" + arr[1])\n\tconfig.SshServer = append(config.SshServer, val)\n}\n\nvar http struct {\n\tparent    *httpParent\n\tserverCnt int\n\tpasswdCnt int\n}\n\nfunc (p configParser) ParseHttpParent(val string) {\n\tif err := checkServerAddr(val); err != nil {\n\t\tFatal(\"parent http server\", err)\n\t}\n\tconfig.saveReqLine = true\n\thttp.parent = newHttpParent(val)\n\tparentProxy.add(http.parent)\n\thttp.serverCnt++\n\tconfigNeedUpgrade = true\n}\n\nfunc (p configParser) ParseHttpUserPasswd(val string) {\n\tif !isUserPasswdValid(val) {\n\t\tFatal(\"httpUserPassword syntax wrong, should be in the form of user:passwd\")\n\t}\n\tif http.passwdCnt >= http.serverCnt {\n\t\tFatal(\"must specify httpParent before corresponding httpUserPasswd\")\n\t}\n\thttp.parent.initAuth(val)\n\thttp.passwdCnt++\n}\n\nfunc (p configParser) ParseAlwaysProxy(val string) {\n\tconfig.AlwaysProxy = parseBool(val, \"alwaysProxy\")\n}\n\nfunc (p configParser) ParseLoadBalance(val string) {\n\tswitch val {\n\tcase \"backup\":\n\t\tconfig.LoadBalance = loadBalanceBackup\n\tcase \"hash\":\n\t\tconfig.LoadBalance = loadBalanceHash\n\tcase \"latency\":\n\t\tconfig.LoadBalance = loadBalanceLatency\n\tdefault:\n\t\tFatalf(\"invalid loadBalance mode: %s\\n\", val)\n\t}\n}\n\nfunc (p configParser) ParseStatFile(val string) {\n\tconfig.StatFile = expandTilde(val)\n}\n\nfunc (p configParser) ParseBlockedFile(val string) {\n\tconfig.BlockedFile = expandTilde(val)\n\tif err := isFileExists(config.BlockedFile); err != nil {\n\t\tFatal(\"blocked file:\", err)\n\t}\n}\n\nfunc (p configParser) ParseDirectFile(val string) {\n\tconfig.DirectFile = expandTilde(val)\n\tif err := isFileExists(config.DirectFile); err != nil {\n\t\tFatal(\"direct file:\", err)\n\t}\n}\n\nvar shadow struct {\n\tparent *shadowsocksParent\n\tpasswd string\n\tmethod string\n\n\tserverCnt int\n\tpasswdCnt int\n\tmethodCnt int\n}\n\nfunc (p configParser) ParseShadowSocks(val string) {\n\tif shadow.serverCnt-shadow.passwdCnt > 1 {\n\t\tFatal(\"must specify shadowPasswd for every shadowSocks server\")\n\t}\n\t// create new shadowsocks parent if both server and password are given\n\t// previously\n\tif shadow.parent != nil && shadow.serverCnt == shadow.passwdCnt {\n\t\tif shadow.methodCnt < shadow.serverCnt {\n\t\t\tshadow.method = \"\"\n\t\t\tshadow.methodCnt = shadow.serverCnt\n\t\t}\n\t\tshadow.parent.initCipher(shadow.method, shadow.passwd)\n\t}\n\tif val == \"\" { // the final call\n\t\tshadow.parent = nil\n\t\treturn\n\t}\n\tif err := checkServerAddr(val); err != nil {\n\t\tFatal(\"shadowsocks server\", err)\n\t}\n\tshadow.parent = newShadowsocksParent(val)\n\tparentProxy.add(shadow.parent)\n\tshadow.serverCnt++\n\tconfigNeedUpgrade = true\n}\n\nfunc (p configParser) ParseShadowPasswd(val string) {\n\tif shadow.passwdCnt >= shadow.serverCnt {\n\t\tFatal(\"must specify shadowSocks before corresponding shadowPasswd\")\n\t}\n\tif shadow.passwdCnt+1 != shadow.serverCnt {\n\t\tFatal(\"must specify shadowPasswd for every shadowSocks\")\n\t}\n\tshadow.passwd = val\n\tshadow.passwdCnt++\n}\n\nfunc (p configParser) ParseShadowMethod(val string) {\n\tif shadow.methodCnt >= shadow.serverCnt {\n\t\tFatal(\"must specify shadowSocks before corresponding shadowMethod\")\n\t}\n\t// shadowMethod is optional\n\tshadow.method = val\n\tshadow.methodCnt++\n}\n\nfunc checkShadowsocks() {\n\tif shadow.serverCnt != shadow.passwdCnt {\n\t\tFatal(\"number of shadowsocks server and password does not match\")\n\t}\n\t// parse the last shadowSocks option again to initialize the last\n\t// shadowsocks server\n\tparser := configParser{}\n\tparser.ParseShadowSocks(\"\")\n}\n\n// Put actual authentication related config parsing in auth.go, so config.go\n// doesn't need to know the details of authentication implementation.\n\nfunc (p configParser) ParseUserPasswd(val string) {\n\tconfig.UserPasswd = val\n\tif !isUserPasswdValid(config.UserPasswd) {\n\t\tFatal(\"userPassword syntax wrong, should be in the form of user:passwd\")\n\t}\n}\n\nfunc (p configParser) ParseUserPasswdFile(val string) {\n\terr := isFileExists(val)\n\tif err != nil {\n\t\tFatal(\"userPasswdFile:\", err)\n\t}\n\tconfig.UserPasswdFile = val\n}\n\nfunc (p configParser) ParseAllowedClient(val string) {\n\tconfig.AllowedClient = val\n}\n\nfunc (p configParser) ParseAuthTimeout(val string) {\n\tconfig.AuthTimeout = parseDuration(val, \"authTimeout\")\n}\n\nfunc (p configParser) ParseCore(val string) {\n\tconfig.Core = parseInt(val, \"core\")\n}\n\nfunc (p configParser) ParseHttpErrorCode(val string) {\n\tconfig.HttpErrorCode = parseInt(val, \"httpErrorCode\")\n}\n\nfunc (p configParser) ParseReadTimeout(val string) {\n\tconfig.ReadTimeout = parseDuration(val, \"readTimeout\")\n}\n\nfunc (p configParser) ParseDialTimeout(val string) {\n\tconfig.DialTimeout = parseDuration(val, \"dialTimeout\")\n}\n\nfunc (p configParser) ParseDetectSSLErr(val string) {\n\tconfig.DetectSSLErr = parseBool(val, \"detectSSLErr\")\n}\n\nfunc (p configParser) ParseEstimateTarget(val string) {\n\tconfig.EstimateTarget = val\n}\n\n// overrideConfig should contain options from command line to override options\n// in config file.\nfunc parseConfig(rc string, override *Config) {\n\t// fmt.Println(\"rcFile:\", path)\n\tf, err := os.Open(expandTilde(rc))\n\tif err != nil {\n\t\tFatal(\"Error opening config file:\", err)\n\t}\n\n\tIgnoreUTF8BOM(f)\n\n\tscanner := bufio.NewScanner(f)\n\n\tparser := reflect.ValueOf(configParser{})\n\tzeroMethod := reflect.Value{}\n\tvar lines []string // store lines for upgrade\n\n\tvar n int\n\tfor scanner.Scan() {\n\t\tlines = append(lines, scanner.Text())\n\n\t\tn++\n\t\tline := strings.TrimSpace(scanner.Text())\n\t\tif line == \"\" || line[0] == '#' {\n\t\t\tcontinue\n\t\t}\n\n\t\tv := strings.SplitN(line, \"=\", 2)\n\t\tif len(v) != 2 {\n\t\t\tFatal(\"config syntax error on line\", n)\n\t\t}\n\t\tkey, val := strings.TrimSpace(v[0]), strings.TrimSpace(v[1])\n\n\t\tmethodName := \"Parse\" + strings.ToUpper(key[0:1]) + key[1:]\n\t\tmethod := parser.MethodByName(methodName)\n\t\tif method == zeroMethod {\n\t\t\tFatalf(\"no such option \\\"%s\\\"\\n\", key)\n\t\t}\n\t\t// for backward compatibility, allow empty string in shadowMethod and logFile\n\t\tif val == \"\" && key != \"shadowMethod\" && key != \"logFile\" {\n\t\t\tFatalf(\"empty %s, please comment or remove unused option\\n\", key)\n\t\t}\n\t\targs := []reflect.Value{reflect.ValueOf(val)}\n\t\tmethod.Call(args)\n\t}\n\tif scanner.Err() != nil {\n\t\tFatalf(\"Error reading rc file: %v\\n\", scanner.Err())\n\t}\n\tf.Close()\n\n\toverrideConfig(&config, override)\n\tcheckConfig()\n\n\tif configNeedUpgrade {\n\t\tupgradeConfig(rc, lines)\n\t}\n}\n\nfunc upgradeConfig(rc string, lines []string) {\n\tnewrc := rc + \".upgrade\"\n\tf, err := os.Create(newrc)\n\tif err != nil {\n\t\tfmt.Println(\"can't create upgraded config file\")\n\t\treturn\n\t}\n\n\t// Upgrade config.\n\tproxyId := 0\n\tlistenId := 0\n\tw := bufio.NewWriter(f)\n\tfor _, line := range lines {\n\t\tline := strings.TrimSpace(line)\n\t\tif line == \"\" || line[0] == '#' {\n\t\t\tw.WriteString(line + newLine)\n\t\t\tcontinue\n\t\t}\n\n\t\tv := strings.Split(line, \"=\")\n\t\tkey := strings.TrimSpace(v[0])\n\n\t\tswitch key {\n\t\tcase \"listen\":\n\t\t\tlisten := listenProxy[listenId]\n\t\t\tlistenId++\n\t\t\tw.WriteString(listen.genConfig() + newLine)\n\t\t\t// comment out original\n\t\t\tw.WriteString(\"#\" + line + newLine)\n\t\tcase \"httpParent\", \"shadowSocks\", \"socksParent\":\n\t\t\tbackPool, ok := parentProxy.(*backupParentPool)\n\t\t\tif !ok {\n\t\t\t\tpanic(\"initial parent pool should be backup pool\")\n\t\t\t}\n\t\t\tparent := backPool.parent[proxyId]\n\t\t\tproxyId++\n\t\t\tw.WriteString(parent.genConfig() + newLine)\n\t\t\t// comment out original\n\t\t\tw.WriteString(\"#\" + line + newLine)\n\t\tcase \"httpUserPasswd\", \"shadowPasswd\", \"shadowMethod\", \"addrInPAC\":\n\t\t\t// just comment out\n\t\t\tw.WriteString(\"#\" + line + newLine)\n\t\tcase \"proxy\":\n\t\t\tproxyId++\n\t\t\tw.WriteString(line + newLine)\n\t\tdefault:\n\t\t\tw.WriteString(line + newLine)\n\t\t}\n\t}\n\tw.Flush()\n\tf.Close() // Must close file before renaming, otherwise will fail on windows.\n\n\t// Rename new and old config file.\n\tif err := os.Rename(rc, rc+\"0.8\"); err != nil {\n\t\tfmt.Println(\"can't backup config file for upgrade:\", err)\n\t\treturn\n\t}\n\tif err := os.Rename(newrc, rc); err != nil {\n\t\tfmt.Println(\"can't rename upgraded rc to original name:\", err)\n\t\treturn\n\t}\n}\n\nfunc overrideConfig(oldconfig, override *Config) {\n\tnewVal := reflect.ValueOf(override).Elem()\n\toldVal := reflect.ValueOf(oldconfig).Elem()\n\n\t// typeOfT := newVal.Type()\n\tfor i := 0; i < newVal.NumField(); i++ {\n\t\tnewField := newVal.Field(i)\n\t\toldField := oldVal.Field(i)\n\t\t// log.Printf(\"%d: %s %s = %v\\n\", i,\n\t\t// typeOfT.Field(i).Name, newField.Type(), newField.Interface())\n\t\tswitch newField.Kind() {\n\t\tcase reflect.String:\n\t\t\ts := newField.String()\n\t\t\tif s != \"\" {\n\t\t\t\toldField.SetString(s)\n\t\t\t}\n\t\tcase reflect.Int:\n\t\t\ti := newField.Int()\n\t\t\tif i != 0 {\n\t\t\t\toldField.SetInt(i)\n\t\t\t}\n\t\t}\n\t}\n\n\toldconfig.EstimateTimeout = override.EstimateTimeout\n}\n\n// Must call checkConfig before using config.\nfunc checkConfig() {\n\tcheckShadowsocks()\n\t// listenAddr must be handled first, as addrInPAC dependends on this.\n\tif listenProxy == nil {\n\t\tlistenProxy = []Proxy{newHttpProxy(defaultListenAddr, \"\")}\n\t}\n}\n"
  },
  {
    "path": "config_test.go",
    "content": "package main\n\nimport (\n\t\"testing\"\n)\n\nfunc TestParseListen(t *testing.T) {\n\tparser := configParser{}\n\tparser.ParseListen(\"http://127.0.0.1:8888\")\n\n\thp, ok := listenProxy[0].(*httpProxy)\n\tif !ok {\n\t\tt.Error(\"listen http proxy type wrong\")\n\t}\n\tif hp.addr != \"127.0.0.1:8888\" {\n\t\tt.Error(\"listen http server address parse error\")\n\t}\n\n\tparser.ParseListen(\"http://127.0.0.1:8888 1.2.3.4:5678\")\n\thp, ok = listenProxy[1].(*httpProxy)\n\tif hp.addrInPAC != \"1.2.3.4:5678\" {\n\t\tt.Error(\"listen http addrInPAC parse error\")\n\t}\n}\n\nfunc TestTunnelAllowedPort(t *testing.T) {\n\tinitConfig(\"\")\n\tparser := configParser{}\n\tparser.ParseTunnelAllowedPort(\"1, 2, 3, 4, 5\")\n\tparser.ParseTunnelAllowedPort(\"6\")\n\tparser.ParseTunnelAllowedPort(\"7\")\n\tparser.ParseTunnelAllowedPort(\"8\")\n\n\ttestData := []struct {\n\t\tport    string\n\t\tallowed bool\n\t}{\n\t\t{\"80\", true}, // default allowd ports\n\t\t{\"443\", true},\n\t\t{\"1\", true},\n\t\t{\"3\", true},\n\t\t{\"5\", true},\n\t\t{\"7\", true},\n\t\t{\"8080\", false},\n\t\t{\"8388\", false},\n\t}\n\n\tfor _, td := range testData {\n\t\tallowed := config.TunnelAllowedPort[td.port]\n\t\tif allowed != td.allowed {\n\t\t\tt.Errorf(\"port %s allowed %v, got %v\\n\", td.port, td.allowed, allowed)\n\t\t}\n\t}\n}\n\nfunc TestParseProxy(t *testing.T) {\n\tpool, ok := parentProxy.(*backupParentPool)\n\tif !ok {\n\t\tt.Fatal(\"parentPool by default should be backup pool\")\n\t}\n\tcnt := -1\n\n\tvar parser configParser\n\tparser.ParseProxy(\"http://127.0.0.1:8080\")\n\tcnt++\n\n\thp, ok := pool.parent[cnt].ParentProxy.(*httpParent)\n\tif !ok {\n\t\tt.Fatal(\"1st http proxy parsed not as httpParent\")\n\t}\n\tif hp.server != \"127.0.0.1:8080\" {\n\t\tt.Error(\"1st http proxy server address wrong, got:\", hp.server)\n\t}\n\n\tparser.ParseProxy(\"http://user:passwd@127.0.0.2:9090\")\n\tcnt++\n\thp, ok = pool.parent[cnt].ParentProxy.(*httpParent)\n\tif !ok {\n\t\tt.Fatal(\"2nd http proxy parsed not as httpParent\")\n\t}\n\tif hp.server != \"127.0.0.2:9090\" {\n\t\tt.Error(\"2nd http proxy server address wrong, got:\", hp.server)\n\t}\n\tif hp.authHeader == nil {\n\t\tt.Error(\"2nd http proxy server user password not parsed\")\n\t}\n\n\tparser.ParseProxy(\"socks5://127.0.0.1:1080\")\n\tcnt++\n\tsp, ok := pool.parent[cnt].ParentProxy.(*socksParent)\n\tif !ok {\n\t\tt.Fatal(\"socks proxy parsed not as socksParent\")\n\t}\n\tif sp.server != \"127.0.0.1:1080\" {\n\t\tt.Error(\"socks server address wrong, got:\", sp.server)\n\t}\n\n\tparser.ParseProxy(\"ss://aes-256-cfb:foobar!@127.0.0.1:1080\")\n\tcnt++\n\t_, ok = pool.parent[cnt].ParentProxy.(*shadowsocksParent)\n\tif !ok {\n\t\tt.Fatal(\"shadowsocks proxy parsed not as shadowsocksParent\")\n\t}\n}\n"
  },
  {
    "path": "config_unix.go",
    "content": "// +build darwin freebsd linux netbsd openbsd\n\npackage main\n\nimport (\n\t\"path\"\n)\n\nconst (\n\trcFname      = \"rc\"\n\tblockedFname = \"blocked\"\n\tdirectFname  = \"direct\"\n\tstatFname    = \"stat\"\n\n\tnewLine = \"\\n\"\n)\n\nfunc getDefaultRcFile() string {\n\treturn path.Join(path.Join(getUserHomeDir(), \".cow\", rcFname))\n}\n"
  },
  {
    "path": "config_windows.go",
    "content": "package main\n\nimport (\n\t\"os\"\n\t\"path\"\n)\n\nconst (\n\trcFname      = \"rc.txt\"\n\tblockedFname = \"blocked.txt\"\n\tdirectFname  = \"direct.txt\"\n\tstatFname    = \"stat.txt\"\n\n\tnewLine = \"\\r\\n\"\n)\n\nfunc getDefaultRcFile() string {\n\t// On windows, put the configuration file in the same directory of cow executable\n\t// This is not a reliable way to detect binary directory, but it works for double click and run\n\treturn path.Join(path.Dir(os.Args[0]), rcFname)\n}\n"
  },
  {
    "path": "conn_pool.go",
    "content": "// Share server connections between different clients.\n\npackage main\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\n// Maximum number of connections to a server.\nconst maxServerConnCnt = 5\n\n// Store each server's connections in separate channels, getting\n// connections for different servers can be done in parallel.\ntype ConnPool struct {\n\tidleConn map[string]chan *serverConn\n\tmuxConn  chan *serverConn // connections support multiplexing\n\tsync.RWMutex\n}\n\nvar connPool = &ConnPool{\n\tidleConn: map[string]chan *serverConn{},\n\tmuxConn:  make(chan *serverConn, maxServerConnCnt*2),\n}\n\nconst muxConnHostPort = \"@muxConn\"\n\nfunc init() {\n\t// make sure hostPort here won't match any actual hostPort\n\tgo closeStaleServerConn(connPool.muxConn, muxConnHostPort)\n}\n\nfunc getConnFromChan(ch chan *serverConn) (sv *serverConn) {\n\tfor {\n\t\tselect {\n\t\tcase sv = <-ch:\n\t\t\tif sv.mayBeClosed() {\n\t\t\t\tsv.Close()\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn sv\n\t\tdefault:\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\nfunc putConnToChan(sv *serverConn, ch chan *serverConn, chname string) {\n\tselect {\n\tcase ch <- sv:\n\t\tdebug.Printf(\"connPool channel %s: put conn\\n\", chname)\n\t\treturn\n\tdefault:\n\t\t// Simply close the connection if can't put into channel immediately.\n\t\t// A better solution would remove old connections from the channel and\n\t\t// add the new one. But's it's more complicated and this should happen\n\t\t// rarely.\n\t\tdebug.Printf(\"connPool channel %s: full\", chname)\n\t\tsv.Close()\n\t}\n}\n\nfunc (cp *ConnPool) Get(hostPort string, asDirect bool) (sv *serverConn) {\n\t// Get from site specific connection first.\n\t// Direct connection are all site specific, so must use site specific\n\t// first to avoid using parent proxy for direct sites.\n\tcp.RLock()\n\tch := cp.idleConn[hostPort]\n\tcp.RUnlock()\n\n\tif ch != nil {\n\t\tsv = getConnFromChan(ch)\n\t}\n\tif sv != nil {\n\t\tdebug.Printf(\"connPool %s: get conn\\n\", hostPort)\n\t\treturn sv\n\t}\n\n\t// All mulplexing connections are for blocked sites,\n\t// so for direct sites we should stop here.\n\tif asDirect && !config.AlwaysProxy {\n\t\treturn nil\n\t}\n\n\tsv = getConnFromChan(cp.muxConn)\n\tif bool(debug) && sv != nil {\n\t\tdebug.Println(\"connPool mux: get conn\", hostPort)\n\t}\n\treturn sv\n}\n\nfunc (cp *ConnPool) Put(sv *serverConn) {\n\t// Multiplexing connections.\n\tswitch sv.Conn.(type) {\n\tcase httpConn, cowConn:\n\t\tputConnToChan(sv, cp.muxConn, \"muxConn\")\n\t\treturn\n\t}\n\n\t// Site specific connections.\n\tcp.RLock()\n\tch := cp.idleConn[sv.hostPort]\n\tcp.RUnlock()\n\n\tif ch == nil {\n\t\tdebug.Printf(\"connPool %s: new channel\\n\", sv.hostPort)\n\t\tch = make(chan *serverConn, maxServerConnCnt)\n\t\tch <- sv\n\t\tcp.Lock()\n\t\tcp.idleConn[sv.hostPort] = ch\n\t\tcp.Unlock()\n\t\t// start a new goroutine to close stale server connections\n\t\tgo closeStaleServerConn(ch, sv.hostPort)\n\t} else {\n\t\tputConnToChan(sv, ch, sv.hostPort)\n\t}\n}\n\ntype chanInPool struct {\n\thostPort string\n\tch       chan *serverConn\n}\n\nfunc (cp *ConnPool) CloseAll() {\n\tdebug.Println(\"connPool: close all server connections\")\n\n\t// Because closeServerConn may acquire connPool.Lock, we first collect all\n\t// channel, and close server connection for each one.\n\tvar connCh []chanInPool\n\tcp.RLock()\n\tfor hostPort, ch := range cp.idleConn {\n\t\tconnCh = append(connCh, chanInPool{hostPort, ch})\n\t}\n\tcp.RUnlock()\n\n\tfor _, hc := range connCh {\n\t\tcloseServerConn(hc.ch, hc.hostPort, true)\n\t}\n\n\tcloseServerConn(cp.muxConn, muxConnHostPort, true)\n}\n\nfunc closeServerConn(ch chan *serverConn, hostPort string, force bool) (done bool) {\n\t// If force is true, close all idle connection even if it maybe open.\n\tlcnt := len(ch)\n\tif lcnt == 0 {\n\t\t// Execute the loop at least once.\n\t\tlcnt = 1\n\t}\n\tfor i := 0; i < lcnt; i++ {\n\t\tselect {\n\t\tcase sv := <-ch:\n\t\t\tif force || sv.mayBeClosed() {\n\t\t\t\tdebug.Printf(\"connPool channel %s: close one conn\\n\", hostPort)\n\t\t\t\tsv.Close()\n\t\t\t} else {\n\t\t\t\t// Put it back and wait.\n\t\t\t\tdebug.Printf(\"connPool channel %s: put back conn\\n\", hostPort)\n\t\t\t\tch <- sv\n\t\t\t}\n\t\tdefault:\n\t\t\tif hostPort != muxConnHostPort {\n\t\t\t\t// No more connection in this channel, remove the channel from\n\t\t\t\t// the map.\n\t\t\t\tdebug.Printf(\"connPool channel %s: remove\\n\", hostPort)\n\t\t\t\tconnPool.Lock()\n\t\t\t\tdelete(connPool.idleConn, hostPort)\n\t\t\t\tconnPool.Unlock()\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc closeStaleServerConn(ch chan *serverConn, hostPort string) {\n\t// Tricky here. When removing a channel from the map, there maybe\n\t// goroutines doing Put and Get using that channel.\n\n\t// For Get, there's no problem because it will return immediately.\n\t// For Put, it's possible that a new connection is added to the\n\t// channel, but the channel is no longer in the map.\n\t// So after removed the channel from the map, we wait for several seconds\n\t// and then close all connections left in it.\n\n\t// It's possible that Put add the connection after the final wait, but\n\t// that should not happen in practice, and the worst result is just lost\n\t// some memory and open fd.\n\tfor {\n\t\ttime.Sleep(5 * time.Second)\n\t\tif done := closeServerConn(ch, hostPort, false); done {\n\t\t\tbreak\n\t\t}\n\t}\n\t// Final wait and then close all left connections. In practice, there\n\t// should be no other goroutines holding reference to the channel.\n\ttime.Sleep(2 * time.Second)\n\tfor {\n\t\tselect {\n\t\tcase sv := <-ch:\n\t\t\tdebug.Printf(\"connPool channel %s: close conn after removed\\n\", hostPort)\n\t\t\tsv.Close()\n\t\tdefault:\n\t\t\tdebug.Printf(\"connPool channel %s: cleanup done\\n\", hostPort)\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "conn_pool_test.go",
    "content": "package main\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestGetFromEmptyPool(t *testing.T) {\n\t// should not block\n\tsv := connPool.Get(\"foo\", true)\n\tif sv != nil {\n\t\tt.Error(\"get non nil server conn from empty conn pool\")\n\t}\n}\n\nfunc TestConnPool(t *testing.T) {\n\tcloseOn := time.Now().Add(10 * time.Second)\n\tconns := []*serverConn{\n\t\t{hostPort: \"example.com:80\", willCloseOn: closeOn},\n\t\t{hostPort: \"example.com:80\", willCloseOn: closeOn},\n\t\t{hostPort: \"example.com:80\", willCloseOn: closeOn},\n\t\t{hostPort: \"example.com:443\", willCloseOn: closeOn},\n\t\t{hostPort: \"google.com:443\", willCloseOn: closeOn},\n\t\t{hostPort: \"google.com:443\", willCloseOn: closeOn},\n\t\t{hostPort: \"www.google.com:80\", willCloseOn: closeOn},\n\t}\n\tfor _, sv := range conns {\n\t\tconnPool.Put(sv)\n\t}\n\n\ttestData := []struct {\n\t\thostPort string\n\t\tfound    bool\n\t}{\n\t\t{\"example.com\", false},\n\t\t{\"example.com:80\", true},\n\t\t{\"example.com:80\", true},\n\t\t{\"example.com:80\", true},\n\t\t{\"example.com:80\", false}, // has 3 such conn\n\t\t{\"www.google.com:80\", true},\n\t}\n\n\tfor _, td := range testData {\n\t\tsv := connPool.Get(td.hostPort, true)\n\t\tif td.found {\n\t\t\tif sv == nil {\n\t\t\t\tt.Error(\"should find conn for\", td.hostPort)\n\t\t\t} else if sv.hostPort != td.hostPort {\n\t\t\t\tt.Errorf(\"hostPort should be: %s, got: %s\\n\", td.hostPort, sv.hostPort)\n\t\t\t}\n\t\t} else if sv != nil {\n\t\t\tt.Errorf(\"should NOT find conn for %s, got conn for: %s\\n\",\n\t\t\t\ttd.hostPort, sv.hostPort)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "doc/implementation.md",
    "content": "# Design #\n\n## Requst and response handling ##\n\n**Update** using the following design, it is actually difficult to correctly support HTTP pipelining. I've come up with a new design inspired by Naruil which should be much cleaner and easier to support HTTP pipelining. But as all major browsers, except Opera, does not enable HTTP pipelining by default, I don't think it's worth the effort to support HTTP pipelining now. I'll try to support it with the new design if the performance benefits of HTTP pipelining becomes significant in the future.\n\nThe final design is evolved from different previous implementations. The other subsections following this one describe how its evolved.\n\nCOW uses separate goroutines to read client requests and server responses.\n\n- For each client, COW will create one *request goroutine* to\n  - accept client request (read from client connection)\n  - create connection if no one not exist\n  - send request to the server (write to server connection)\n- For each server connection, there will be an associated *response goroutine*\n  - reading response from the web server (read from server connection)\n  - send response back to the client (write to client connection)\n\nOne client must have one request goroutine, and may have multiple response goroutine. Response goroutine is created when the server connection is created.\n\nThis makes it possible for COW to support HTTP pipeline. (Not very sure about this.) COW does not pack multiple requests and send in batch, but it can send request before previous request response is received. If the client (browser) and the web server supports HTTP pipeline, then COW will not in effect make them go back to wating response for each request.\n\nBut this design does make COW more complicated. I must be careful to avoid concurrency problems between the request and response goroutine.\n\nHere's things that worth noting:\n\n- The web server connection for each host is stored in a map\n  - The request goroutine creates the connection and put it into this map\n  - When serving requests, this map will be be used to find already created server connections\n  - We should avoid writing the map in the response goroutine. So when response goroutine finishes, it should just mark the corresonding connection as closed instead of directly removing it from the map\n\n- Request and response goroutine may need to notify each other to stop\n  - When client connection is closed, all response goroutine should stop\n  - Client connection close can be detected in both request and response goroutine (as both will try to either read or write the connection), to make things simple, I just do notification in the request goroutine\n\n## Notification between goroutines\n\n- Notification sender should not block\n  - I use a size 1 channel for this as the notification will be sent only once\n- Receiver use polling to handle notification\n  - For blocked calls, should set time out to actively poll notification\n\n## Why parse client http request ##\n\nOf course we need to parse http request to know the address of the web server.\n\nBesides, HTTP requests sent to proxy servers are a little different from those sent directly to the web servers. So proxy server need to reconstruct http request\n\n- Normal HTTP 1.1 `GET` request has request URI like '/index.html', but when sending to proxy, it would be something like 'host.com/index.html'\n- The `CONNECT` request requires special handling by the proxy (send a 200 back to the client)\n\n## Parse http response or not? ##\n\nThe initial implementation serves client request one by one. For each request:\n\n1. Parse client HTTP request\n2. Connect to the server and send the request, send the response back to the client\n\nWe need to know whether a response is finished so we can start to serve another request. (This is the oppisite to HTTP pipelining.) That's why we need to parse content-length header and chunked encoding.\n\nParsing responses allow the proxy to put server connections back to a pool, thus allows different clients to reuse server connections.\n\nAfter supporting `CONNECT`, I realized that I can use a separate goroutine to read HTTP response from the server and pass it directly back to the client. This approach doesn't need to parse response to know when the response ends and then starts to process another request.\n\n**Update: not parsing HTTP response do have some problems.** Refer to section \"But response parsing is necessary\".\n\nThis approach has several implications needs to be considered:\n\n- The proxy doesn't know whether the web server closes the connection by setting the header \"Connection: close\"\n  - This should not be a big problem because web server should use persistent connection normally\n- And this header is passed directly to the client which would close it's connection to the proxy (even though the proxy didn't close this connection)\n  - Even if the closed connection header is passed to the client, the client can simply create a new connection to the proxy and the proxy will detect the closed client connection\n- The server connection can only serve a single client connection. Because we don't know the boundary of responses, the proxy is unable to identify different responses and sends to different clients\n  - This means that multiple clients connecting to the same server has to create different server connections\n  - We have to create multiple connection to the same server to reduce latency any way, but makes it impossible to reuse server connection for different clients\n\n### Why choose not parse ###\n\nI choosed not parsing the response because:\n\n- Associating client with dedicated server connection is simpler in implementation\n  - As client could create multiple proxy connections to concurrently issue requests to reduce latency, the proxy can allow only a single connection to different web servers and thus connection pool is not needed\n- Not parsing the response reduces overhead\n  - Need additional goroutine to handle response, so hard to say this definitely has better performance\n  - If we are going to support HTTP pipelining, we may still need to handle response in separate goroutine\n\n### But response parsing is necessary ###\n\nI've got a bug in handling HTTP response 302 when not parsing the response.\n\nWhen trying to visit \"youku.com\", it gives a \"302\" response with \"Connection: close\". The browser doesn't close the connection and still tries to get more content from the server after seeing the response.\n\nI tried polipo and see it will send back \"302\" response along with a \"Content-Length: 0\" to indicate the client that the response has finished.\n\nTo add this kind of response editing capability for my proxy, I have to parse HTTP response.\n\nSo the current solution is to parse the response in the a separate goroutine, which doesn't require lots of code change against the not parsing approach.\n\n# About supporting auto refresh #\n\nWhen blocked sites are detected because of error like connection resets and read time out, we can choose to redo the HTTP request by using parent proxy or just return error page and let the browser refresh.\n\nI tried to support auto refresh. But as I want support HTTP pipelining, the client request and server response read are in separate goroutine. The response reading goroutine need to send redo request to the client request goroutine and maintain a correct request handling order. The resulting code is very complex and difficult to maintain. Besides, the extra code to support auto refresh may incur performance overhead.\n\nAs blocked sites will be recorded, the refresh is only needed for the first access to a blocked site. Auto refresh is just a minor case optimization.\n\nSo I choose not to support auto refresh as the benefit is small.\n\n# Error printing policy #\n\nThe goal is **make it easy to find the exact error location**.\n\n- Error should be printed as early as possible\n- If an error happens in a function which will be invoked at multiple places, print the error at the call site\n"
  },
  {
    "path": "doc/init.d/cow",
    "content": "#!/bin/bash\n### BEGIN INIT INFO\n# Provides:          cow\n# Required-Start:    $network\n# Required-Stop:     $network\n# Default-Start:     2 3 4 5\n# Default-Stop:      0 1 6\n# Short-Description: COW: Climb Over the Wall http proxy\n# Description:       Automatically detect blocked site and use parent proxy.\n### END INIT INFO\n\n# Put this script under /etc/init.d/, then run \"update-rc.d cow defaults\".\n\n# Note: this script requires sudo in order to run COW as the specified\n# user. Please change the following variables in order to use this script.\n# COW will search for rc/direct/block/stat file under user's $HOME/.cow/ directory.\nBIN=/usr/local/bin/cow\nUSER=usr\nGROUP=grp\nPID_DIR=/var/run\nPID_FILE=$PID_DIR/cow.pid\nLOG_FILE=/var/log/cow\n\nRET_VAL=0\n\ncheck_running() {\n  if [[ -r $PID_FILE ]]; then\n    read PID <$PID_FILE\n    if [[ -d \"/proc/$PID\" ]]; then\n      return 0\n    else\n      rm -f $PID_FILE\n      return 1\n    fi\n  else\n    return 2\n  fi\n}\n\ndo_status() {\n  check_running\n  case $? in\n    0)\n      echo \"cow running with PID $PID\"\n      ;;\n    1)\n      echo \"cow not running, remove PID file $PID_FILE\"\n      ;;\n    2)\n      echo \"Could not find PID file $PID_FILE, cow does not appear to be running\"\n      ;;\n  esac\n  return 0\n}\n\ndo_start() {\n  if [[ ! -d $PID_DIR ]]; then\n    echo \"creating PID dir\"\n    mkdir $PID_DIR || echo \"failed creating PID directory $PID_DIR\"; exit 1\n    chown $USER:$GROUP $PID_DIR || echo \"failed creating PID directory $PID_DIR\"; exit 1\n    chmod 0770 $PID_DIR\n  fi\n  if check_running; then\n    echo \"cow already running with PID $PID\"\n    return 0\n  fi\n  echo \"starting cow\"\n  # sudo will set the group to the primary group of $USER\n  sudo -u $USER -H -- $BIN >$LOG_FILE 2>&1 &\n  PID=$!\n  echo $PID > $PID_FILE\n  sleep 0.3\n  if ! check_running; then\n    echo \"start failed\"\n    return 1\n  fi\n  echo \"cow running with PID $PID\"\n  return 0\n}\n\ndo_stop() {\n  if check_running; then\n    echo \"stopping cow with PID $PID\"\n    kill $PID\n    rm -f $PID_FILE\n  else\n    echo \"Could not find PID file $PID_FILE\"\n  fi\n}\n\ndo_restart() {\n  do_stop\n  do_start\n}\n\ncase \"$1\" in\n  start|stop|restart|status)\n    do_$1\n    ;;\n  *)\n    echo \"Usage: cow {start|stop|restart|status}\"\n    RET_VAL=1\n    ;;\nesac\n\nexit $RET_VAL\n"
  },
  {
    "path": "doc/logrotate.d/cow",
    "content": "/var/log/cow {\n  rotate 4\n  weekly\n  compress\n  missingok\n  postrotate\n    /etc/init.d/cow restart\n  endscript\n}\n"
  },
  {
    "path": "doc/osx/info.chenyufei.cow.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n  <dict>\n    <key>Label</key>\n    <string>info.chenyufei.cow</string>\n    <key>ProgramArguments</key>\n    <array>\n\t<string>COWBINARY</string>\n    </array>\n    <key>KeepAlive</key>\n    <true/>\n    <key>RunAtLoad</key>\n    <true/>\n  </dict>\n</plist>\n"
  },
  {
    "path": "doc/sample-config/rc",
    "content": "# 配置文件中 # 开头的行为注释\n#\n# 代理服务器监听地址，重复多次来指定多个监听地址，语法：\n#\n#   listen = protocol://[optional@]server_address:server_port\n#\n# 支持的 protocol 如下：\n#\n# HTTP (提供 http 代理):\n#   listen = http://127.0.0.1:7777\n#\n#   上面的例子中，cow 生成的 PAC url 为 http://127.0.0.1:7777/pac\n#   配置浏览器或系统 HTTP 和 HTTPS 代理时请填入该地址\n#   若配置代理时有对所有协议使用该代理的选项，且你不清楚此选项的含义，请勾选\n#\n# cow (需两个 cow 服务器配合使用):\n#   listen = cow://encrypt_method:password@1.2.3.4:5678\n#\n#   若 1.2.3.4:5678 在国外，位于国内的 cow 配置其为二级代理后，两个 cow 之间可以\n#   通过加密连接传输 http 代理流量。目前的加密采用与 shadowsocks 相同的方式。\n#\n# 其他说明：\n# - 若 server_address 为 0.0.0.0，监听本机所有 IP 地址\n# - 可以用如下语法指定 PAC 中返回的代理服务器地址（当使用端口映射将 http 代理提供给外网时使用）\n#   listen = http://127.0.0.1:7777 1.2.3.4:5678\n#\nlisten = http://127.0.0.1:7777\n\n# 日志文件路径，如不指定则输出到 stdout\n#logFile =\n\n# COW 默认仅对被墙网站使用二级代理\n# 下面选项设置为 true 后，所有网站都通过二级代理访问\n#alwaysProxy = false\n\n# 指定多个二级代理时使用的负载均衡策略，可选策略如下\n#\n#   backup:  默认策略，优先使用第一个指定的二级代理，其他仅作备份使用\n#   hash:    根据请求的 host name，优先使用 hash 到的某一个二级代理\n#   latency: 优先选择连接延迟最低的二级代理\n#\n# 一个二级代理连接失败后会依次尝试其他二级代理\n# 失败的二级代理会以一定的概率再次尝试使用，因此恢复后会重新启用\n#loadBalance = backup\n\n#############################\n# 指定二级代理\n#############################\n\n# 二级代理统一使用下列语法指定：\n#\n#   proxy = protocol://[authinfo@]server:port\n#\n# 重复使用 proxy 多次指定多个二级代理，backup 策略将按照二级代理出现的顺序来使用\n#\n# 目前支持的二级代理及配置举例：\n#\n# SOCKS5:\n#   proxy = socks5://127.0.0.1:1080\n#\n# HTTP:\n#   proxy = http://127.0.0.1:8080\n#   proxy = http://user:password@127.0.0.1:8080\n#\n#   用户认证信息为可选项\n#\n# shadowsocks:\n#   proxy = ss://encrypt_method:password@1.2.3.4:8388\n#   proxy = ss://encrypt_method-auth:password@1.2.3.4:8388\n#\n#   encrypt_method 添加 -auth 启用 One Time Auth\n#   authinfo 中指定加密方法和密码，所有支持的加密方法如下：\n#     aes-128-cfb, aes-192-cfb, aes-256-cfb,\n#     bf-cfb, cast5-cfb, des-cfb, rc4-md5,\n#     chacha20, salsa20, rc4, table\n#   推荐使用 aes-128-cfb\n#\n# cow:\n#   proxy = cow://method:passwd@1.2.3.4:4321\n#\n#   authinfo 与 shadowsocks 相同\n\n\n#############################\n# 执行 ssh 命令创建 SOCKS5 代理\n#############################\n\n# 下面的选项可以让 COW 执行 ssh 命令创建本地 SOCKS5 代理，并在 ssh 断开后重连\n# COW 会自动使用通过 ssh 命令创建的代理，无需再通过 proxy 选项指定\n# 可重复指定多个\n#\n# 注意这一功能需要系统上已有 ssh 命令，且必须使用 ssh public key authentication\n#\n# 若指定该选项，COW 将执行以下命令：\n#     ssh -n -N -D <local_socks_port> -p <server_ssh_port> <user@server>\n# server_ssh_port 端口不指定则默认为 22\n# 如果要指定其他 ssh 选项，请修改 ~/.ssh/config\n#sshServer = user@server:local_socks_port[:server_ssh_port]\n\n#############################\n# 认证\n#############################\n\n# 指定允许的 IP 或者网段。网段仅支持 IPv4，可以指定 IPv6 地址，用逗号分隔多个项\n# 使用此选项时别忘了添加 127.0.0.1，否则本机访问也需要认证\n#allowedClient = 127.0.0.1, 192.168.1.0/24, 10.0.0.0/8\n\n# 要求客户端通过用户名密码认证\n# COW 总是先验证 IP 是否在 allowedClient 中，若不在其中再通过用户名密码认证\n#userPasswd = username:password\n\n# 如需指定多个用户名密码，可在下面选项指定的文件中列出，文件中每行内容如下\n#   username:password[:port]\n# port 为可选项，若指定，则该用户只能从指定端口连接 COW\n# 注意：如有重复用户，COW 会报错退出\n#userPasswdFile = /path/to/file\n\n# 认证失效时间\n# 语法：2h3m4s 表示 2 小时 3 分钟 4 秒\n#authTimeout = 2h\n\n#############################\n# 高级选项\n#############################\n\n# 将指定的 HTTP error code 认为是被干扰，使用二级代理重试，默认为空\n#httpErrorCode =\n\n# 最多允许使用多少个 CPU 核\n#core = 2\n\n# 检测超时时间使用的网站，最好使用能快速访问的站点\n#estimateTarget = example.com\n\n# 允许建立隧道连接的端口，多个端口用逗号分隔，可重复多次\n# 默认总是允许下列服务的端口: ssh, http, https, rsync, imap, pop, jabber, cvs, git, svn\n# 如需允许其他端口，请用该选项添加\n# 限制隧道连接的端口可以防止将运行 COW 的服务器上只监听本机 ip 的服务暴露给外部\n#tunnelAllowedPort = 80, 443\n\n# GFW 会使 DNS 解析超时，也可能返回错误的地址，能连接但是读不到任何内容\n# 下面两个值改小一点可以加速检测网站是否被墙，但网络情况差时可能误判\n\n# 创建连接超时（语法跟 authTimeout 相同）\n#dialTimeout = 5s\n# 从服务器读超时\n#readTimeout = 5s\n\n# 基于 client 是否很快关闭连接来检测 SSL 错误，只对 Chrome 有效\n# （Chrome 遇到 SSL 错误会直接关闭连接，而不是让用户选择是否继续）\n# 可能将可直连网站误判为被墙网站，当 GFW 进行 SSL 中间人攻击时可以考虑使用\n#detectSSLErr = false\n\n# 修改 stat/blocked/direct 文件路径，如不指定，默认在配置文件所在目录下\n# 执行 cow 的用户需要有对 stat 文件所在目录的写权限才能更新 stat 文件\n#statFile = <dir to rc file>/stat\n#blockedFile = <dir to rc file>/blocked\n#directFile = <dir to rc file>/direct\n"
  },
  {
    "path": "doc/sample-config/rc-en",
    "content": "# Lines starting with \"#\" are comments.\n#\n# Listen address of the proxy server, repeat to specify multiple ones.\n# Syntax:\n#\n#   listen = protocol://[optional@]server_address:server_port\n#\n# Supported protocols:\n#\n# HTTP (provides http proxy):\n#   listen = http://127.0.0.1:7777\n#\n#   The generated PAC url in the above example is http://127.0.0.1:7777/pac\n#\n# cow (need two cow servers to use this protocol):\n#   listen = cow://encrypt_method:password@1.2.3.4:5678\n#\n# \tSuppose 1.2.3.4:5678 is outside your country and the network is not\n#   disturbed, then COW running in your own country should configure it\n#   as parent proxy. The two COW servers will use encrypted connection to\n# \tpass data. The encryption method used is the same as shadowsocks.\n#\n# Note:\n# - If server_address is 0.0.0.0, listen all IP addresses on the system.\n# - The following syntax can specify the proxy address in the generated PAC.\n#\t(Use this if you are using port forwarding to provide COW to external network.)\n#\n#       listen = http://127.0.0.1:7777 1.2.3.4:5678\n#\nlisten = http://127.0.0.1:7777\n\n# Log file path, defaults to stdout\n#logFile =\n\n# By default, COW only uses parent proxy if the site is blocked.\n# If the following option is true, COW will use parent proxy for all sites.\n#alwaysProxy = false\n\n# With multiple parent proxies, COW can employ one of the load balancing\n# strategies:\n#\n#   backup:  default policy, use the first prarent proxy in config,\n#            the others are just backup\n#   hash:    hash to a specific parent proxy according to host name\n#   latency: use the parent proxy with lowest connection latency\n#\n# When one parent proxy fails to connect, COW will try other parent proxies\n# in order.\n# Failed parent proxy will be tried with some probability, so they will be\n# used again after recovery.\n#loadBalance = backup\n\n#############################\n# Specify parent proxy\n#############################\n\n# Parent proxies are specified with a generic syntax (following RFC 3986):\n#\n#   proxy = protocol://[authinfo@]server:port\n#\n# Repeat to specify multiple parent proxies. Backup load balancing will use\n# them in order if one fails to connect.\n#\n# Supported parent proxies and config example:\n#\n# SOCKS5:\n#   proxy = socks5://127.0.0.1:1080\n#\n# HTTP:\n#   proxy = http://127.0.0.1:8080\n#   proxy = http://user:password@127.0.0.1:8080\n#\n# \tauthinfo is optional\n#\n# shadowsocks:\n#   proxy = ss://encrypt_method:password@1.2.3.4:8388\n#   proxy = ss://encrypt_method-auth:password@1.2.3.4:8388\n#\n#   Append -auth to encrypt_method to enable One Time Auth.\n#   authinfo specifies encryption method and password.\n#   Here are the supported encryption methods:\n#\n#     aes-128-cfb, aes-192-cfb, aes-256-cfb,\n#     bf-cfb, cast5-cfb, des-cfb, rc4-md5,\n#     chacha20, salsa20, rc4, table\n#\n#   aes-128-cfb is recommended.\n#\n# cow:\n#   proxy = cow://method:passwd@1.2.3.4:4321\n#\n#   authinfo is the same as shadowsocks parent proxy\n\n\n#############################\n# Run ssh command to create SOCKS5 parent proxy\n#############################\n\n# Note: shadowsocks is better, use it if you can.\n\n# The following option lets COW execute ssh command to create local\n# SOCKS5 proxy and automatically re-execute if ssh connection is closed.\n# The created SOCKS5 proxy will be used as a parent proxy.\n# The option can be repeated to create multiple SOCKS5 proxies.\n#\n# Note: requires ssh command and must use ssh public key authentication.\n#\n# COW will execute the following command if the option is given:\n#\n#     ssh -n -N -D <local_socks_port> -p <server_ssh_port> <user@server>\n#\n# server_ssh_port defaults to 22\n# Please modify ~/.ssh/config to specify other ssh options\n#sshServer = user@server:local_socks_port[:server_ssh_port]\n\n#############################\n# Authentication\n#############################\n\n# Specify allowed IP address (IPv4 and IPv6) or sub-network (only IPv4).\n# Don't forget to specify 127.0.0.1 with this option.\n#allowedClient = 127.0.0.1, 192.168.1.0/24, 10.0.0.0/8\n\n# Require username and password authentication. COW always check IP in\n# allowedClient first, then ask for username authentication.\n#userPasswd = username:password\n\n# To specify multiple username and password, list all those in a file with\n# content like this:\n#\n#   username:password[:port]\n#\n# port is optional, user can only connect from the specific port if specified.\n# COW will report error and exit if there's duplicated user.\n#userPasswdFile = /path/to/file\n\n# Time interval to keep authentication information.\n# Syntax: 2h3m4s means 2 hours 3 minutes 4 seconds\n#authTimeout = 2h\n\n#############################\n# Advanced options\n#############################\n\n# Take a specific HTTP error code as blocked and use parent proxy to retry.\n#httpErrorCode =\n\n# Maximum CPU core to use.\n#core = 2\n\n# cow uses this site to estimate timeout, better to use a fast website.\n#estimateTarget = example.com\n\n# Ports allowed to create tunnel (HTTP CONNECT method), comma separated list\n# or repeat to append more ports.\n# Ports for the following service are allowed by default:\n#\n#     ssh, http, https, rsync, imap, pop, jabber, cvs, git, svn\n#\n# Limiting ports for tunneling prevents exposing internal services to outside.\n#tunnelAllowedPort = 80, 443\n\n# GFW may timeout DNS query, or return wrong server address which can connect\n# but blocks on read forever.\n# Decrease the following timeout values can speed up detecting blocked sites,\n# but may mistake normal sites as blocked.\n\n# DNS and connection timeout (same syntax with authTimeout).\n#dialTimeout = 5s\n# Read from server timeout.\n#readTimeout = 5s\n\n# Detect SSL error based on client close connection speed, only effective for\n# Chrome.\n# This detection is no reliable, may mistaken normal sites as blocked.\n# Only consider this option when GFW is making middle man attack.\n#detectSSLErr = false\n\n# Change the stat/blocked/direct file position, defaults to files under directory\n# containing rc file.\n# The cow user must write access to directory containing the stat file in order\n# to update stat.\n#statFile = <dir to rc file>/stat\n#blockedFile = <dir to rc file>/blocked\n#directFile = <dir to rc file>/direct\n"
  },
  {
    "path": "error.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"os\"\n\t\"text/template\"\n\t\"time\"\n)\n\n// Do not end with \"\\r\\n\" so we can add more header later\nvar headRawTmpl = \"HTTP/1.1 {{.CodeReason}}\\r\\n\" +\n\t\"Connection: keep-alive\\r\\n\" +\n\t\"Cache-Control: no-cache\\r\\n\" +\n\t\"Pragma: no-cache\\r\\n\" +\n\t\"Content-Type: text/html\\r\\n\" +\n\t\"Content-Length: {{.Length}}\\r\\n\"\n\nvar errPageTmpl, headTmpl *template.Template\n\nfunc init() {\n\thostName, err := os.Hostname()\n\tif err != nil {\n\t\thostName = \"unknown host\"\n\t}\n\n\terrPageRawTmpl := `<!DOCTYPE html>\n<html>\n\t<head> <title>COW Proxy</title> </head>\n\t<body>\n\t\t<h1>{{.H1}}</h1>\n\t\t{{.Msg}}\n\t\t<hr />\n\t\tGenerated by <i>COW ` + version + `</i> <br />\n\t\tHost <i>` + hostName + `</i> <br />\n\t\t{{.T}}\n\t</body>\n</html>\n`\n\tif headTmpl, err = template.New(\"errorHead\").Parse(headRawTmpl); err != nil {\n\t\tFatal(\"Internal error on generating error head template\")\n\t}\n\tif errPageTmpl, err = template.New(\"errorPage\").Parse(errPageRawTmpl); err != nil {\n\t\tFatalf(\"Internal error on generating error page template\")\n\t}\n}\n\nfunc genErrorPage(h1, msg string) (string, error) {\n\tvar err error\n\tdata := struct {\n\t\tH1  string\n\t\tMsg string\n\t\tT   string\n\t}{\n\t\th1,\n\t\tmsg,\n\t\ttime.Now().Format(time.ANSIC),\n\t}\n\n\tbuf := new(bytes.Buffer)\n\terr = errPageTmpl.Execute(buf, data)\n\treturn buf.String(), err\n}\n\nfunc sendPageGeneric(w io.Writer, codeReason, h1, msg string) {\n\tpage, err := genErrorPage(h1, msg)\n\tif err != nil {\n\t\terrl.Println(\"Error generating error page:\", err)\n\t\treturn\n\t}\n\n\tdata := struct {\n\t\tCodeReason string\n\t\tLength     int\n\t}{\n\t\tcodeReason,\n\t\tlen(page),\n\t}\n\tbuf := new(bytes.Buffer)\n\tif err := headTmpl.Execute(buf, data); err != nil {\n\t\terrl.Println(\"Error generating error page header:\", err)\n\t\treturn\n\t}\n\n\tbuf.WriteString(\"\\r\\n\")\n\tbuf.WriteString(page)\n\tw.Write(buf.Bytes())\n}\n\nfunc sendErrorPage(w io.Writer, codeReason, h1, msg string) {\n\tsendPageGeneric(w, codeReason, \"[Error] \"+h1, msg)\n}\n"
  },
  {
    "path": "estimate_timeout.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n)\n\n// For once blocked site, use min dial/read timeout to make switching to\n// parent proxy faster.\nconst minDialTimeout = 3 * time.Second\nconst minReadTimeout = 4 * time.Second\nconst defaultDialTimeout = 5 * time.Second\nconst defaultReadTimeout = 5 * time.Second\nconst maxTimeout = 15 * time.Second\n\nvar dialTimeout = defaultDialTimeout\nvar readTimeout = defaultReadTimeout\n\n// estimateTimeout tries to fetch a url and adjust timeout value according to\n// how much time is spent on connect and fetch. This avoids incorrectly\n// considering non-blocked sites as blocked when network connection is bad.\nfunc estimateTimeout(host string, payload []byte) {\n\t//debug.Println(\"estimating timeout\")\n\tbuf := connectBuf.Get()\n\tdefer connectBuf.Put(buf)\n\tvar est time.Duration\n\tstart := time.Now()\n\tc, err := net.Dial(\"tcp\", host+\":80\")\n\tif err != nil {\n\t\terrl.Printf(\"estimateTimeout: can't connect to %s: %v, network has problem?\\n\",\n\t\t\thost, err)\n\t\tgoto onErr\n\t}\n\tdefer c.Close()\n\n\test = time.Now().Sub(start) * 5\n\t// debug.Println(\"estimated dialTimeout:\", est)\n\tif est > maxTimeout {\n\t\test = maxTimeout\n\t}\n\tif est > config.DialTimeout {\n\t\tdialTimeout = est\n\t\tdebug.Println(\"new dial timeout:\", dialTimeout)\n\t} else if dialTimeout != config.DialTimeout {\n\t\tdialTimeout = config.DialTimeout\n\t\tdebug.Println(\"new dial timeout:\", dialTimeout)\n\t}\n\n\tstart = time.Now()\n\t// include time spent on sending request, reading all content to make it a\n\t// little longer\n\n\tif _, err = c.Write(payload); err != nil {\n\t\terrl.Println(\"estimateTimeout: error sending request:\", err)\n\t\tgoto onErr\n\t}\n\tfor err == nil {\n\t\t_, err = c.Read(buf)\n\t}\n\tif err != io.EOF {\n\t\terrl.Printf(\"estimateTimeout: error getting %s: %v, network has problem?\\n\",\n\t\t\thost, err)\n\t\tgoto onErr\n\t}\n\test = time.Now().Sub(start) * 10\n\t// debug.Println(\"estimated read timeout:\", est)\n\tif est > maxTimeout {\n\t\test = maxTimeout\n\t}\n\tif est > time.Duration(config.ReadTimeout) {\n\t\treadTimeout = est\n\t\tdebug.Println(\"new read timeout:\", readTimeout)\n\t} else if readTimeout != config.ReadTimeout {\n\t\treadTimeout = config.ReadTimeout\n\t\tdebug.Println(\"new read timeout:\", readTimeout)\n\t}\n\treturn\nonErr:\n\tdialTimeout += 2 * time.Second\n\treadTimeout += 2 * time.Second\n}\n\nfunc runEstimateTimeout() {\n\tconst estimateReq = \"GET / HTTP/1.1\\r\\n\" +\n\t\t\"Host: %s\\r\\n\" +\n\t\t\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:11.0) Gecko/20100101 Firefox/11.0\\r\\n\" +\n\t\t\"Accept: */*\\r\\n\" +\n\t\t\"Accept-Language: en-us,en;q=0.5\\r\\n\" +\n\t\t\"Accept-Encoding: gzip, deflate\\r\\n\" +\n\t\t\"Connection: close\\r\\n\\r\\n\"\n\n\treadTimeout = config.ReadTimeout\n\tdialTimeout = config.DialTimeout\n\n\tpayload := []byte(fmt.Sprintf(estimateReq, config.EstimateTarget))\n\n\tfor {\n\t\testimateTimeout(config.EstimateTarget, payload)\n\t\ttime.Sleep(time.Minute)\n\t}\n}\n\n// Guess network status based on doing HTTP request to estimateSite\nfunc networkBad() bool {\n\treturn (readTimeout != config.ReadTimeout) ||\n\t\t(dialTimeout != config.DialTimeout)\n}\n"
  },
  {
    "path": "http.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/cyfdecyf/bufio\"\n\t\"net\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst CRLF = \"\\r\\n\"\n\nconst (\n\tstatusCodeContinue = 100\n)\n\nconst (\n\tstatusBadReq         = \"400 Bad Request\"\n\tstatusForbidden      = \"403 Forbidden\"\n\tstatusExpectFailed   = \"417 Expectation Failed\"\n\tstatusRequestTimeout = \"408 Request Timeout\"\n)\n\nvar CustomHttpErr = errors.New(\"CustomHttpErr\")\n\ntype Header struct {\n\tContLen             int64\n\tKeepAlive           time.Duration\n\tProxyAuthorization  string\n\tChunking            bool\n\tTrailer             bool\n\tConnectionKeepAlive bool\n\tExpectContinue      bool\n\tHost                string\n}\n\ntype rqState byte\n\nconst (\n\trsCreated  rqState = iota\n\trsSent             // request has been sent to server\n\trsRecvBody         // response header received, receiving response body\n\trsDone\n)\n\ntype Request struct {\n\tMethod  string\n\tURL     *URL\n\traw     *bytes.Buffer // stores the raw content of request header\n\trawByte []byte        // underlying buffer for raw\n\n\t// request line from client starts at 0, cow generates request line that\n\t// can be sent directly to web server\n\treqLnStart int // start of generated request line in raw\n\theadStart  int // start of header in raw\n\tbodyStart  int // start of body in raw\n\n\tHeader\n\tisConnect bool\n\tpartial   bool // whether contains only partial request data\n\tstate     rqState\n\ttryCnt    byte\n}\n\n// Assume keep-alive request by default.\nvar zeroRequest = Request{Header: Header{ConnectionKeepAlive: true}}\n\nfunc (r *Request) reset() {\n\tb := r.rawByte\n\traw := r.raw\n\t*r = zeroRequest // reset to zero value\n\n\tif raw != nil {\n\t\traw.Reset()\n\t\tr.rawByte = b\n\t\tr.raw = raw\n\t} else {\n\t\tr.rawByte = httpBuf.Get()\n\t\tr.raw = bytes.NewBuffer(r.rawByte[:0]) // must use 0 length slice\n\t}\n}\n\nfunc (r *Request) String() (s string) {\n\treturn fmt.Sprintf(\"%s %s%s\", r.Method, r.URL.HostPort, r.URL.Path)\n}\n\nfunc (r *Request) Verbose() []byte {\n\tvar rqbyte []byte\n\tif r.isConnect {\n\t\trqbyte = r.rawBeforeBody()\n\t} else {\n\t\t// This includes client request line if has http parent proxy\n\t\trqbyte = r.raw.Bytes()\n\t}\n\treturn rqbyte\n}\n\n// Message body in request is signaled by the inclusion of a Content-Length\n// or Transfer-Encoding header.\n// Refer to http://stackoverflow.com/a/299696/306935\nfunc (r *Request) hasBody() bool {\n\treturn r.Chunking || r.ContLen > 0\n}\n\nfunc (r *Request) isRetry() bool {\n\treturn r.tryCnt > 1\n}\n\nfunc (r *Request) tryOnce() {\n\tr.tryCnt++\n}\n\nfunc (r *Request) tooManyRetry() bool {\n\treturn r.tryCnt > 3\n}\n\nfunc (r *Request) responseNotSent() bool {\n\treturn r.state <= rsSent\n}\n\nfunc (r *Request) hasSent() bool {\n\treturn r.state >= rsSent\n}\n\nfunc (r *Request) releaseBuf() {\n\tif r.raw != nil {\n\t\thttpBuf.Put(r.rawByte)\n\t\tr.rawByte = nil\n\t\tr.raw = nil\n\t}\n}\n\n// rawRequest returns the raw request that can be sent directly to HTTP/1.1 server.\nfunc (r *Request) rawRequest() []byte {\n\treturn r.raw.Bytes()[r.reqLnStart:]\n}\n\nfunc (r *Request) rawBeforeBody() []byte {\n\treturn r.raw.Bytes()[:r.bodyStart]\n}\n\nfunc (r *Request) rawHeaderBody() []byte {\n\treturn r.raw.Bytes()[r.headStart:]\n}\n\nfunc (r *Request) rawBody() []byte {\n\treturn r.raw.Bytes()[r.bodyStart:]\n}\n\nfunc (r *Request) proxyRequestLine() []byte {\n\treturn r.raw.Bytes()[0:r.reqLnStart]\n}\n\nfunc (r *Request) genRequestLine() {\n\t// Generate normal HTTP request line\n\tr.raw.WriteString(r.Method + \" \")\n\tif len(r.URL.Path) == 0 {\n\t\tr.raw.WriteString(\"/\")\n\t} else {\n\t\tr.raw.WriteString(r.URL.Path)\n\t}\n\tr.raw.WriteString(\" HTTP/1.1\\r\\n\")\n}\n\ntype Response struct {\n\tStatus int\n\tReason []byte\n\n\tHeader\n\n\traw     *bytes.Buffer\n\trawByte []byte\n}\n\nvar zeroResponse = Response{Header: Header{ConnectionKeepAlive: true}}\n\nfunc (rp *Response) reset() {\n\tb := rp.rawByte\n\traw := rp.raw\n\t*rp = zeroResponse\n\n\tif raw != nil {\n\t\traw.Reset()\n\t\trp.rawByte = b\n\t\trp.raw = raw\n\t} else {\n\t\trp.rawByte = httpBuf.Get()\n\t\trp.raw = bytes.NewBuffer(rp.rawByte[:0])\n\t}\n}\n\nfunc (rp *Response) releaseBuf() {\n\tif rp.raw != nil {\n\t\thttpBuf.Put(rp.rawByte)\n\t\trp.rawByte = nil\n\t\trp.raw = nil\n\t}\n}\n\nfunc (rp *Response) rawResponse() []byte {\n\treturn rp.raw.Bytes()\n}\n\nfunc (rp *Response) genStatusLine() {\n\trp.raw.Write([]byte(\"HTTP/1.1 \"))\n\trp.raw.WriteString(strconv.Itoa(rp.Status))\n\tif len(rp.Reason) != 0 {\n\t\trp.raw.WriteByte(' ')\n\t\trp.raw.Write(rp.Reason)\n\t}\n\trp.raw.Write([]byte(CRLF))\n\treturn\n}\n\nfunc (rp *Response) String() string {\n\treturn fmt.Sprintf(\"%d %s\", rp.Status, rp.Reason)\n}\n\nfunc (rp *Response) Verbose() []byte {\n\treturn rp.raw.Bytes()\n}\n\ntype URL struct {\n\tHostPort string // must contain port\n\tHost     string // no port\n\tPort     string\n\tDomain   string\n\tPath     string\n}\n\nfunc (url *URL) String() string {\n\treturn url.HostPort + url.Path\n}\n\n// Set all fields according to hostPort except Path.\nfunc (url *URL) ParseHostPort(hostPort string) {\n\tif hostPort == \"\" {\n\t\treturn\n\t}\n\thost, port, err := net.SplitHostPort(hostPort)\n\tif err != nil {\n\t\t// Add default 80 and split again. If there's still error this time,\n\t\t// it's not because lack of port number.\n\t\thost = hostPort\n\t\tport = \"80\"\n\t\thostPort = net.JoinHostPort(hostPort, port)\n\t}\n\n\turl.Host = host\n\turl.Port = port\n\turl.HostPort = hostPort\n\turl.Domain = host2Domain(host)\n}\n\n// net.ParseRequestURI will unescape encoded path, but the proxy doesn't need\n// that. Assumes the input rawurl is valid. Even if rawurl is not valid, net.Dial\n// will check the correctness of the host.\n\nfunc ParseRequestURI(rawurl string) (*URL, error) {\n\treturn ParseRequestURIBytes([]byte(rawurl))\n}\n\nfunc ParseRequestURIBytes(rawurl []byte) (*URL, error) {\n\tif rawurl[0] == '/' {\n\t\treturn &URL{Path: string(rawurl)}, nil\n\t}\n\n\tvar rest, scheme []byte\n\tid := bytes.Index(rawurl, []byte(\"://\"))\n\tif id == -1 {\n\t\trest = rawurl\n\t\tscheme = []byte(\"http\") // default to http\n\t} else {\n\t\tscheme = rawurl[:id]\n\t\tASCIIToLowerInplace(scheme) // it's ok to lower case scheme\n\t\tif !bytes.Equal(scheme, []byte(\"http\")) && !bytes.Equal(scheme, []byte(\"https\")) {\n\t\t\terrl.Printf(\"%s protocol not supported\\n\", scheme)\n\t\t\treturn nil, errors.New(\"protocol not supported\")\n\t\t}\n\t\trest = rawurl[id+3:]\n\t}\n\n\tvar hostport, host, port, path string\n\tid = bytes.IndexByte(rest, '/')\n\tif id == -1 {\n\t\thostport = string(rest)\n\t} else {\n\t\thostport = string(rest[:id])\n\t\tpath = string(rest[id:])\n\t}\n\n\t// Must add port in host so it can be used as key to find the correct\n\t// server connection.\n\t// e.g. google.com:80 and google.com:443 should use different connections.\n\thost, port, err := net.SplitHostPort(hostport)\n\tif err != nil { // missing port\n\t\thost = hostport\n\t\tif len(scheme) == 4 {\n\t\t\thostport = net.JoinHostPort(host, \"80\")\n\t\t\tport = \"80\"\n\t\t} else {\n\t\t\thostport = net.JoinHostPort(host, \"443\")\n\t\t\tport = \"443\"\n\t\t}\n\t}\n        // Fixed wechat image url bug, url like http://[::ffff:183.192.196.102]/mmsns/lVxxxxxx\n        host = strings.TrimSuffix(strings.TrimPrefix(host, \"[::ffff:\"), \"]\")\n        hostport = net.JoinHostPort(host, port)\n\treturn &URL{hostport, host, port, host2Domain(host), path}, nil\n}\n\n// headers of interest to a proxy\n// Define them as constant and use editor's completion to avoid typos.\n// Note RFC2616 only says about \"Connection\", no \"Proxy-Connection\", but\n// Firefox and Safari send this header along with \"Connection\" header.\n// See more at http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/web-proxy-connection-header.html\nconst (\n\theaderConnection         = \"connection\"\n\theaderContentLength      = \"content-length\"\n\theaderExpect             = \"expect\"\n\theaderHost               = \"host\"\n\theaderKeepAlive          = \"keep-alive\"\n\theaderProxyAuthenticate  = \"proxy-authenticate\"\n\theaderProxyAuthorization = \"proxy-authorization\"\n\theaderProxyConnection    = \"proxy-connection\"\n\theaderReferer            = \"referer\"\n\theaderTE                 = \"te\"\n\theaderTrailer            = \"trailer\"\n\theaderTransferEncoding   = \"transfer-encoding\"\n\theaderUpgrade            = \"upgrade\"\n\n\tfullHeaderConnectionKeepAlive = \"Connection: keep-alive\\r\\n\"\n\tfullHeaderConnectionClose     = \"Connection: close\\r\\n\"\n\tfullHeaderTransferEncoding    = \"Transfer-Encoding: chunked\\r\\n\"\n)\n\n// Using Go's method expression\nvar headerParser = map[string]HeaderParserFunc{\n\theaderConnection:         (*Header).parseConnection,\n\theaderContentLength:      (*Header).parseContentLength,\n\theaderExpect:             (*Header).parseExpect,\n\theaderHost:               (*Header).parseHost,\n\theaderKeepAlive:          (*Header).parseKeepAlive,\n\theaderProxyAuthorization: (*Header).parseProxyAuthorization,\n\theaderProxyConnection:    (*Header).parseConnection,\n\theaderTransferEncoding:   (*Header).parseTransferEncoding,\n\theaderTrailer:            (*Header).parseTrailer,\n}\n\nvar hopByHopHeader = map[string]bool{\n\theaderConnection:         true,\n\theaderKeepAlive:          true,\n\theaderProxyAuthorization: true,\n\theaderProxyConnection:    true,\n\theaderTE:                 true,\n\theaderTrailer:            true,\n\theaderTransferEncoding:   true,\n\theaderUpgrade:            true,\n}\n\n// Note: Value bytes passed to header parse function are in the buffer\n// associated with bufio and will be modified. It will also be stored in the\n// raw request buffer, so becareful when modifying the value bytes. (Change\n// case only when the spec says it is case insensitive.)\n//\n// If Header needs to hold raw value, make a copy. For example,\n// parseProxyAuthorization does this.\n\ntype HeaderParserFunc func(*Header, []byte) error\n\n// Used by both \"Connection\" and \"Proxy-Connection\" header. COW always adds\n// connection header at the end of a request/response (in parseRequest and\n// parseResponse), no matter whether the original one has this header or not.\n// This will change the order of headers, but should be OK as RFC2616 4.2 says\n// header order is not significant. (Though general-header first is \"good-\n// practice\".)\nfunc (h *Header) parseConnection(s []byte) error {\n\tASCIIToLowerInplace(s)\n\th.ConnectionKeepAlive = !bytes.Contains(s, []byte(\"close\"))\n\treturn nil\n}\n\nfunc (h *Header) parseContentLength(s []byte) (err error) {\n\th.ContLen, err = ParseIntFromBytes(s, 10)\n\treturn err\n}\n\nfunc (h *Header) parseHost(s []byte) (err error) {\n\tif h.Host == \"\" {\n\t\th.Host = string(s)\n\t}\n\treturn\n}\n\nfunc (h *Header) parseKeepAlive(s []byte) (err error) {\n\tASCIIToLowerInplace(s)\n\tid := bytes.Index(s, []byte(\"timeout=\"))\n\tif id != -1 {\n\t\tid += len(\"timeout=\")\n\t\tend := id\n\t\tfor ; end < len(s) && IsDigit(s[end]); end++ {\n\t\t}\n\t\tdelta, err := ParseIntFromBytes(s[id:end], 10)\n\t\tif err != nil {\n\t\t\treturn err // possible empty bytes\n\t\t}\n\t\th.KeepAlive = time.Second * time.Duration(delta)\n\t}\n\treturn nil\n}\n\nfunc (h *Header) parseProxyAuthorization(s []byte) error {\n\th.ProxyAuthorization = string(s)\n\treturn nil\n}\n\nfunc (h *Header) parseTransferEncoding(s []byte) error {\n\tASCIIToLowerInplace(s)\n\t// For transfer-encoding: identify, it's the same as specifying neither\n\t// content-length nor transfer-encoding.\n\th.Chunking = bytes.Contains(s, []byte(\"chunked\"))\n\tif !h.Chunking && !bytes.Contains(s, []byte(\"identity\")) {\n\t\treturn fmt.Errorf(\"invalid transfer encoding: %s\", s)\n\t}\n\treturn nil\n}\n\n// RFC 2616 3.6.1 states when trailers are allowed:\n//\n// a) request includes TE header\n// b) server is the original server\n//\n// Even though COW removes TE header, the original server can still respond\n// with Trailer header.\n// As Trailer is general header, it's possible to appear in request. But is\n// there any client does this?\nfunc (h *Header) parseTrailer(s []byte) error {\n\t// use errl to test if this header is common to see\n\terrl.Printf(\"got Trailer header: %s\\n\", s)\n\tif len(s) != 0 {\n\t\th.Trailer = true\n\t}\n\treturn nil\n}\n\n// For now, COW does not fully support 100-continue. It will return \"417\n// expectation failed\" if a request contains expect header. This is one of the\n// strategies supported by polipo, which is easiest to implement in cow.\n// TODO If we see lots of expect 100-continue usage, provide full support.\n\nfunc (h *Header) parseExpect(s []byte) error {\n\tASCIIToLowerInplace(s)\n\terrl.Printf(\"Expect header: %s\\n\", s) // put here to see if expect header is widely used\n\th.ExpectContinue = true\n\t/*\n\t\tif bytes.Contains(s, []byte(\"100-continue\")) {\n\t\t\th.ExpectContinue = true\n\t\t}\n\t*/\n\treturn nil\n}\n\nfunc splitHeader(s []byte) (name, val []byte, err error) {\n\ti := bytes.IndexByte(s, ':')\n\tif i < 0 {\n\t\treturn nil, nil, fmt.Errorf(\"malformed header: %#v\", string(s))\n\t}\n\t// Do not lower case field value, as it maybe case sensitive\n\treturn ASCIIToLower(s[:i]), TrimSpace(s[i+1:]), nil\n}\n\n// Learned from net.textproto. One difference is that this one keeps the\n// ending '\\n' in the returned line. Buf if there's only CRLF in the line,\n// return nil for the line.\nfunc readContinuedLineSlice(r *bufio.Reader) ([]byte, error) {\n\t// feedly.com request headers contains things like:\n\t// \"$Authorization.feedly: $FeedlyAuth\\r\\n\", so we must test for only\n\t// continuation spaces.\n\tisspace := func(b byte) bool {\n\t\treturn b == ' ' || b == '\\t'\n\t}\n\n\t// Read the first line.\n\tline, err := r.ReadSlice('\\n')\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// There are servers that use \\n for line ending, so trim first before check ending.\n\t// For example, the 404 page for http://plan9.bell-labs.com/magic/man2html/1/2l\n\ttrimmed := TrimSpace(line)\n\tif len(trimmed) == 0 {\n\t\tif len(line) > 2 {\n\t\t\treturn nil, fmt.Errorf(\"malformed end of headers, len: %d, %#v\", len(line), string(line))\n\t\t}\n\t\treturn nil, nil\n\t}\n\n\tif isspace(line[0]) {\n\t\treturn nil, fmt.Errorf(\"malformed header, start with space: %#v\", string(line))\n\t}\n\n\t// Optimistically assume that we have started to buffer the next line\n\t// and it starts with an ASCII letter (the next header key), so we can\n\t// avoid copying that buffered data around in memory and skipping over\n\t// non-existent whitespace.\n\tif r.Buffered() > 0 {\n\t\tpeek, err := r.Peek(1)\n\t\tif err == nil && !isspace(peek[0]) {\n\t\t\treturn line, nil\n\t\t}\n\t}\n\n\tvar buf []byte\n\tbuf = append(buf, trimmed...)\n\n\t// Read continuation lines.\n\tfor skipSpace(r) > 0 {\n\t\tline, err := r.ReadSlice('\\n')\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\tbuf = append(buf, ' ')\n\t\tbuf = append(buf, TrimTrailingSpace(line)...)\n\t}\n\tbuf = append(buf, '\\r', '\\n')\n\treturn buf, nil\n}\n\nfunc skipSpace(r *bufio.Reader) int {\n\tn := 0\n\tfor {\n\t\tc, err := r.ReadByte()\n\t\tif err != nil {\n\t\t\t// Bufio will keep err until next read.\n\t\t\tbreak\n\t\t}\n\t\tif c != ' ' && c != '\\t' {\n\t\t\tr.UnreadByte()\n\t\t\tbreak\n\t\t}\n\t\tn++\n\t}\n\treturn n\n}\n\n// Only add headers that are of interest for a proxy into request/response's header map.\nfunc (h *Header) parseHeader(reader *bufio.Reader, raw *bytes.Buffer, url *URL) (err error) {\n\th.ContLen = -1\n\tfor {\n\t\tvar line, name, val []byte\n\t\tif line, err = readContinuedLineSlice(reader); err != nil || len(line) == 0 {\n\t\t\treturn\n\t\t}\n\t\tif name, val, err = splitHeader(line); err != nil {\n\t\t\terrl.Printf(\"split header %v\\nline: %s\\nraw header:\\n%s\\n\", err, line, raw.Bytes())\n\t\t\treturn\n\t\t}\n\t\t// Wait Go to solve/provide the string<->[]byte optimization\n\t\tkn := string(name)\n\t\tif parseFunc, ok := headerParser[kn]; ok {\n\t\t\tif len(val) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err = parseFunc(h, val); err != nil {\n\t\t\t\terrl.Printf(\"parse header %v\\nline: %s\\nraw header:\\n%s\\n\", err, line, raw.Bytes())\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tif hopByHopHeader[kn] {\n\t\t\tcontinue\n\t\t}\n\t\traw.Write(line)\n\t\t// debug.Printf(\"len %d %s\", len(s), s)\n\t}\n}\n\n// Parse the request line and header, does not touch body\nfunc parseRequest(c *clientConn, r *Request) (err error) {\n\tvar s []byte\n\treader := c.bufRd\n\tc.setReadTimeout(\"parseRequest\")\n\t// parse request line\n\tif s, err = reader.ReadSlice('\\n'); err != nil {\n\t\tif isErrTimeout(err) {\n\t\t\treturn errClientTimeout\n\t\t}\n\t\treturn err\n\t}\n\tc.unsetReadTimeout(\"parseRequest\")\n\t// debug.Printf(\"Request line %s\", s)\n\n\tr.reset()\n\tif config.saveReqLine {\n\t\tr.raw.Write(s)\n\t\tr.reqLnStart = len(s)\n\t}\n\n\tvar f [][]byte\n\t// Tolerate with multiple spaces and '\\t' is achieved by FieldsN.\n\tif f = FieldsN(s, 3); len(f) != 3 {\n\t\treturn fmt.Errorf(\"malformed request line: %#v\", string(s))\n\t}\n\tASCIIToUpperInplace(f[0])\n\tr.Method = string(f[0])\n\n\t// Parse URI into host and path\n\tr.URL, err = ParseRequestURIBytes(f[1])\n\tif err != nil {\n\t\treturn\n\t}\n\tr.Header.Host = r.URL.HostPort // If Header.Host is set, parseHost will just return.\n\tif r.Method == \"CONNECT\" {\n\t\tr.isConnect = true\n\t\tif bool(dbgRq) && verbose && !config.saveReqLine {\n\t\t\tr.raw.Write(s)\n\t\t}\n\t} else {\n\t\tr.genRequestLine()\n\t}\n\tr.headStart = r.raw.Len()\n\n\t// Read request header.\n\tif err = r.parseHeader(reader, r.raw, r.URL); err != nil {\n\t\terrl.Printf(\"parse request header: %v %s\\n%s\", err, r, r.Verbose())\n\t\treturn err\n\t}\n\tif r.Chunking {\n\t\tr.raw.WriteString(fullHeaderTransferEncoding)\n\t}\n\tif r.ConnectionKeepAlive {\n\t\tr.raw.WriteString(fullHeaderConnectionKeepAlive)\n\t} else {\n\t\tr.raw.WriteString(fullHeaderConnectionClose)\n\t}\n\t// The spec says proxy must add Via header. polipo disables this by\n\t// default, and I don't want to let others know the user is using COW, so\n\t// don't add it.\n\tr.raw.WriteString(CRLF)\n\tr.bodyStart = r.raw.Len()\n\treturn\n}\n\n// If an http response may have message body\nfunc (rp *Response) hasBody(method string) bool {\n\tif method == \"HEAD\" || rp.Status == 304 || rp.Status == 204 ||\n\t\trp.Status < 200 {\n\t\treturn false\n\t}\n\treturn true\n}\n\n// Parse response status and headers.\nfunc parseResponse(sv *serverConn, r *Request, rp *Response) (err error) {\n\tvar s []byte\n\treader := sv.bufRd\n\tif sv.maybeFake() {\n\t\tsv.setReadTimeout(\"parseResponse\")\n\t}\n\tif s, err = reader.ReadSlice('\\n'); err != nil {\n\t\t// err maybe timeout caused by explicity setting deadline, EOF, or\n\t\t// reset caused by GFW.\n\t\tdebug.Printf(\"read response status line %v %v\\n\", err, r)\n\t\t// Server connection with error will not be used any more, so no need\n\t\t// to unset timeout.\n\t\t// For read error, return directly in order to identify whether this\n\t\t// is caused by GFW.\n\t\treturn err\n\t}\n\tif sv.maybeFake() {\n\t\tsv.unsetReadTimeout(\"parseResponse\")\n\t}\n\t// debug.Printf(\"Response line %s\", s)\n\n\t// response status line parsing\n\tvar f [][]byte\n\tif f = FieldsN(s, 3); len(f) < 2 { // status line are separated by SP\n\t\treturn fmt.Errorf(\"malformed response status line: %#v %v\", string(s), r)\n\t}\n\tstatus, err := ParseIntFromBytes(f[1], 10)\n\n\trp.reset()\n\trp.Status = int(status)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"response status not valid: %s len=%d %v\", f[1], len(f[1]), err)\n\t}\n\tif len(f) == 3 {\n\t\trp.Reason = f[2]\n\t}\n\n\tproto := f[0]\n\tif !bytes.Equal(proto[0:7], []byte(\"HTTP/1.\")) {\n\t\treturn fmt.Errorf(\"invalid response status line: %s request %v\", string(f[0]), r)\n\t}\n\tif proto[7] == '1' {\n\t\trp.raw.Write(s)\n\t} else if proto[7] == '0' {\n\t\t// Should return HTTP version as 1.1 to client since closed connection\n\t\t// will be converted to chunked encoding\n\t\trp.genStatusLine()\n\t} else {\n\t\treturn fmt.Errorf(\"response protocol not supported: %s\", f[0])\n\t}\n\n\tif err = rp.parseHeader(reader, rp.raw, r.URL); err != nil {\n\t\terrl.Printf(\"parse response header: %v %s\\n%s\", err, r, rp.Verbose())\n\t\treturn err\n\t}\n\n\t//Check for http error code from config file\n\tif config.HttpErrorCode > 0 && rp.Status == config.HttpErrorCode {\n\t\tdebug.Println(\"Requested http code is raised\")\n\t\treturn CustomHttpErr\n\t}\n\n\tif rp.Status == statusCodeContinue && !r.ExpectContinue {\n\t\t// not expecting 100-continue, just ignore it and read final response\n\t\terrl.Println(\"Ignore server 100 response for\", r)\n\t\treturn parseResponse(sv, r, rp)\n\t}\n\n\tif rp.Chunking {\n\t\trp.raw.WriteString(fullHeaderTransferEncoding)\n\t} else if rp.ContLen == -1 {\n\t\t// No chunk, no content length, assume close to signal end.\n\t\trp.ConnectionKeepAlive = false\n\t\tif rp.hasBody(r.Method) {\n\t\t\t// Connection close, no content length specification.\n\t\t\t// Use chunked encoding to pass content back to client.\n\t\t\tdebug.Println(\"add chunked encoding to close connection response\", r, rp)\n\t\t\trp.raw.WriteString(fullHeaderTransferEncoding)\n\t\t} else {\n\t\t\tdebug.Println(\"add content-length 0 to close connection response\", r, rp)\n\t\t\trp.raw.WriteString(\"Content-Length: 0\\r\\n\")\n\t\t}\n\t}\n\t// Whether COW should respond with keep-alive depends on client request,\n\t// not server response.\n\tif r.ConnectionKeepAlive {\n\t\trp.raw.WriteString(fullHeaderConnectionKeepAlive)\n\t\trp.raw.WriteString(fullKeepAliveHeader)\n\t} else {\n\t\trp.raw.WriteString(fullHeaderConnectionClose)\n\t}\n\trp.raw.WriteString(CRLF)\n\n\treturn nil\n}\n\nfunc unquote(s string) string {\n\treturn strings.Trim(s, \"\\\"\")\n}\n\nfunc parseKeyValueList(str string) map[string]string {\n\tlist := strings.Split(str, \",\")\n\tif len(list) == 1 && list[0] == \"\" {\n\t\treturn nil\n\t}\n\tres := make(map[string]string)\n\tfor _, ele := range list {\n\t\tkv := strings.SplitN(strings.TrimSpace(ele), \"=\", 2)\n\t\tif len(kv) != 2 {\n\t\t\terrl.Println(\"no equal sign in key value list element:\", ele)\n\t\t\treturn nil\n\t\t}\n\t\tkey, val := kv[0], unquote(kv[1])\n\t\tres[key] = val\n\t}\n\treturn res\n}\n"
  },
  {
    "path": "http_test.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"github.com/cyfdecyf/bufio\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestParseRequestURI(t *testing.T) {\n\tvar testData = []struct {\n\t\trawurl string\n\t\turl    *URL\n\t}{\n\t\t// I'm really tired of typing google.com ...\n\t\t{\"http://www.g.com\", &URL{\"www.g.com:80\", \"www.g.com\", \"80\", \"g.com\", \"\"}},\n\t\t{\"http://plus.g.com/\", &URL{\"plus.g.com:80\", \"plus.g.com\", \"80\", \"g.com\", \"/\"}},\n\t\t{\"https://g.com:80\", &URL{\"g.com:80\", \"g.com\", \"80\", \"g.com\", \"\"}},\n\t\t{\"http://mail.g.com:80/\", &URL{\"mail.g.com:80\", \"mail.g.com\", \"80\", \"g.com\", \"/\"}},\n\t\t{\"http://g.com:80/ncr\", &URL{\"g.com:80\", \"g.com\", \"80\", \"g.com\", \"/ncr\"}},\n\t\t{\"https://g.com/ncr/tree\", &URL{\"g.com:443\", \"g.com\", \"443\", \"g.com\", \"/ncr/tree\"}},\n\t\t{\"www.g.com.hk:80/\", &URL{\"www.g.com.hk:80\", \"www.g.com.hk\", \"80\", \"g.com.hk\", \"/\"}},\n\t\t{\"g.com.jp:80\", &URL{\"g.com.jp:80\", \"g.com.jp\", \"80\", \"g.com.jp\", \"\"}},\n\t\t{\"g.com\", &URL{\"g.com:80\", \"g.com\", \"80\", \"g.com\", \"\"}},\n\t\t{\"g.com:8000/ncr\", &URL{\"g.com:8000\", \"g.com\", \"8000\", \"g.com\", \"/ncr\"}},\n\t\t{\"g.com/ncr/tree\", &URL{\"g.com:80\", \"g.com\", \"80\", \"g.com\", \"/ncr/tree\"}},\n\t\t{\"simplehost\", &URL{\"simplehost:80\", \"simplehost\", \"80\", \"\", \"\"}},\n\t\t{\"simplehost:8080\", &URL{\"simplehost:8080\", \"simplehost\", \"8080\", \"\", \"\"}},\n\t\t{\"192.168.1.1:8080/\", &URL{\"192.168.1.1:8080\", \"192.168.1.1\", \"8080\", \"\", \"/\"}},\n\t\t{\"/helloworld\", &URL{\"\", \"\", \"\", \"\", \"/helloworld\"}},\n\t}\n\tfor _, td := range testData {\n\t\turl, err := ParseRequestURI(td.rawurl)\n\t\tif url == nil {\n\t\t\tif err == nil {\n\t\t\t\tt.Error(\"nil URL must report error\")\n\t\t\t}\n\t\t\tif td.url != nil {\n\t\t\t\tt.Error(td.rawurl, \"should not report error\")\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\tt.Error(td.rawurl, \"non nil URL should not report error\")\n\t\t}\n\t\tif url.HostPort != td.url.HostPort {\n\t\t\tt.Error(td.rawurl, \"parsed hostPort wrong:\", td.url.HostPort, \"got\", url.HostPort)\n\t\t}\n\t\tif url.Host != td.url.Host {\n\t\t\tt.Error(td.rawurl, \"parsed host wrong:\", td.url.Host, \"got\", url.Host)\n\t\t}\n\t\tif url.Port != td.url.Port {\n\t\t\tt.Error(td.rawurl, \"parsed port wrong:\", td.url.Port, \"got\", url.Port)\n\t\t}\n\t\tif url.Domain != td.url.Domain {\n\t\t\tt.Error(td.rawurl, \"parsed domain wrong:\", td.url.Domain, \"got\", url.Domain)\n\t\t}\n\t\tif url.Path != td.url.Path {\n\t\t\tt.Error(td.rawurl, \"parsed path wrong:\", td.url.Path, \"got\", url.Path)\n\t\t}\n\t}\n}\n\nfunc TestParseHeader(t *testing.T) {\n\tvar testData = []struct {\n\t\traw    string\n\t\tnewraw string\n\t\theader *Header\n\t}{\n\t\t{\"content-length: 64\\r\\nConnection: keep-alive\\r\\n\\r\\n\",\n\t\t\t\"content-length: 64\\r\\n\",\n\t\t\t&Header{ContLen: 64, Chunking: false, ConnectionKeepAlive: true}},\n\t\t{\"Connection: keep-alive\\r\\nKeep-Alive: timeout=10\\r\\nTransfer-Encoding: chunked\\r\\nTE: trailers\\r\\n\\r\\n\",\n\t\t\t\"\",\n\t\t\t&Header{ContLen: -1, Chunking: true, ConnectionKeepAlive: true,\n\t\t\t\tKeepAlive: 10 * time.Second}},\n\t\t{\"Connection:\\r\\n keep-alive\\r\\nKeep-Alive: max=5,\\r\\n timeout=10\\r\\n\\r\\n\",\n\t\t\t\"\",\n\t\t\t&Header{ContLen: -1, Chunking: false, ConnectionKeepAlive: true,\n\t\t\t\tKeepAlive: 10 * time.Second}},\n\t\t{\"Connection: \\r\\n close\\r\\nLong: line\\r\\n continued\\r\\n\\tagain\\r\\n\\r\\n\",\n\t\t\t\"Long: line continued again\\r\\n\",\n\t\t\t&Header{ContLen: -1, Chunking: false, ConnectionKeepAlive: false}},\n\t}\n\tfor _, td := range testData {\n\t\tvar h Header\n\t\tvar newraw bytes.Buffer\n\t\th.parseHeader(bufio.NewReader(strings.NewReader(td.raw)), &newraw, nil)\n\t\tif h.ContLen != td.header.ContLen {\n\t\t\tt.Errorf(\"%q parsed content length wrong, should be %d, get %d\\n\",\n\t\t\t\ttd.raw, td.header.ContLen, h.ContLen)\n\t\t}\n\t\tif h.Chunking != td.header.Chunking {\n\t\t\tt.Errorf(\"%q parsed chunking wrong, should be %t, get %t\\n\",\n\t\t\t\ttd.raw, td.header.Chunking, h.Chunking)\n\t\t}\n\t\tif h.ConnectionKeepAlive != td.header.ConnectionKeepAlive {\n\t\t\tt.Errorf(\"%q parsed connection wrong, should be %v, get %v\\n\",\n\t\t\t\ttd.raw, td.header.ConnectionKeepAlive, h.ConnectionKeepAlive)\n\t\t}\n\t\tif h.KeepAlive != td.header.KeepAlive {\n\t\t\tt.Errorf(\"%q parsed keep alive wrong, should be %v, get %v\\n\",\n\t\t\t\ttd.raw, td.header.KeepAlive, h.KeepAlive)\n\t\t}\n\t\tif newraw.String() != td.newraw {\n\t\t\tt.Errorf(\"%q parsed raw wrong\\nshould be: %q\\ngot: %q\\n\",\n\t\t\t\ttd.raw, td.newraw, newraw.Bytes())\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "install-cow.sh",
    "content": "#!/bin/bash\n\nversion=0.9.8\n\narch=`uname -m`\ncase $arch in\n    \"x86_64\")\n        arch=\"64\"\n        ;;\n    \"i386\" | \"i586\" | \"i486\" | \"i686\")\n        arch=\"32\"\n        ;;\n    \"armv5tel\" | \"armv6l\" | \"armv7l\")\n        features=`cat /proc/cpuinfo | grep Features`\n        if [[ ! \"$features\" =~ \"vfp\" ]]; then\n            #arm without vfp must use GOARM=5 binary\n            #see https://github.com/golang/go/wiki/GoArm\n            arch=\"-armv5tel\"\n        else\n            arch=\"-$arch\"\n        fi\n        ;;\n    *)\n        echo \"$arch currently has no precompiled binary\"\n        ;;\nesac\n\nos=`uname -s`\ncase $os in\n    \"Darwin\")\n        os=\"mac\"\n        ;;\n    \"Linux\")\n        os=\"linux\"\n        ;;\n    *)\n        echo \"$os currently has no precompiled binary\"\n        exit 1\nesac\n\nexit_on_fail() {\n    if [ $? != 0 ]; then\n        echo $1\n        exit 1\n    fi\n}\n\nwhile true; do\n    # Get install directory from environment variable.\n    if [[ -n $COW_INSTALLDIR && -d $COW_INSTALLDIR ]]; then\n        install_dir=$COW_INSTALLDIR\n        break\n    fi\n\n    # Get installation directory from user\n    echo -n \"Install cow binary to which directory (absolute path, defaults to current dir): \"\n    read install_dir </dev/tty\n    if [ -z $install_dir ]; then\n        echo \"No installation directory given, assuming current directory\"\n        install_dir=`pwd`\n        break\n    fi\n    if [ ! -d $install_dir ]; then\n        echo \"Directory $install_dir does not exists\"\n    else\n        break\n    fi\ndone\n\n# Ask OS X user whehter to start COW upon login\nstart_on_login=\"n\"\nif [ $os == \"mac\" ]; then\n    while true; do\n        echo -n \"Start COW upon login? (If yes, download a plist file to ~/Library/LaunchAgents) [Y/n] \"\n        read start_on_login </dev/tty\n        case $start_on_login in\n        \"Y\" | \"y\" | \"\")\n            start_on_login=\"y\"\n            break\n            ;;\n        \"N\" | \"n\")\n            start_on_login=\"n\"\n            break\n            ;;\n        esac\n    done\nfi\n\n# Download COW binary\nbin=cow-$os$arch-$version\ntmpdir=`mktemp -d /tmp/cow.XXXXXX`\ntmpbin=$tmpdir/cow\nbinary_url=\"https://github.com/cyfdecyf/cow/releases/download/$version/$bin.gz\"\necho \"Downloading cow binary $binary_url to $tmpbin.gz\"\ncurl -L \"$binary_url\" -o $tmpbin.gz || \\\n    exit_on_fail \"Downloading cow binary failed\"\ngunzip $tmpbin.gz || exit_on_fail \"gunzip $tmpbin.gz failed\"\nchmod +x $tmpbin ||\n    exit_on_fail \"Can't chmod for $tmpbin\"\n\n# Download sample config file if no configuration directory present\ndoc_base=\"https://raw.github.com/cyfdecyf/cow/$version/doc\"\nconfig_dir=\"$HOME/.cow\"\nis_update=true\nif [ ! -e $config_dir ]; then\n    is_update=false\n    sample_config_base=\"${doc_base}/sample-config\"\n    echo \"Downloading sample config file to $config_dir\"\n    mkdir -p $config_dir || exit_on_fail \"Can't create $config_dir directory\"\n    for f in rc; do\n        echo \"Downloading $sample_config_base/$f to $config_dir/$f\"\n        curl -L \"$sample_config_base/$f\" -o $config_dir/$f || \\\n            exit_on_fail \"Downloading sample config file $f failed\"\n    done\nfi\n\n# Download startup plist file\nif [ $start_on_login == \"y\" ]; then\n    la_dir=\"$HOME/Library/LaunchAgents\"\n    plist=\"info.chenyufei.cow.plist\"\n    plist_url=\"$doc_base/osx/$plist\"\n    mkdir -p $la_dir && exit_on_fail \"Can't create directory $la_dir\"\n    echo \"Downloading $plist_url to $la_dir/$plist\"\n    curl -L \"$plist_url\" | \\\n        sed -e \"s,COWBINARY,$install_dir/cow,\" > $la_dir/$plist || \\\n        exit_on_fail \"Download startup plist file to $la_dir failed\"\nfi\n\n# Move binary to install directory\necho \"Move $tmpbin to $install_dir (will run sudo if no write permission to install directory)\"\nif [ -w $install_dir ]; then\n    mv $tmpbin $install_dir\nelse\n    sudo mv $tmpbin $install_dir\nfi\nexit_on_fail \"Failed to move $tmpbin to $install_dir\"\nrmdir $tmpdir\n\n# Done\necho\nif $is_update; then\n    echo \"Update finished.\"\nelse\n    echo \"Installation finished.\"\n    echo \"Please edit $config_dir/rc according to your own settings.\"\n    echo 'After that, execute \"cow &\" to start cow and run in background.'\nfi\n\n"
  },
  {
    "path": "log.go",
    "content": "package main\n\n// This logging trick is learnt from a post by Rob Pike\n// https://groups.google.com/d/msg/golang-nuts/gU7oQGoCkmg/j3nNxuS2O_sJ\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/cyfdecyf/color\"\n)\n\ntype infoLogging bool\ntype debugLogging bool\ntype errorLogging bool\ntype requestLogging bool\ntype responseLogging bool\n\nvar (\n\tinfo   infoLogging\n\tdebug  debugLogging\n\terrl   errorLogging\n\tdbgRq  requestLogging\n\tdbgRep responseLogging\n\n\tlogFile io.Writer\n\n\t// make sure logger can be called before initLog\n\terrorLog    = log.New(os.Stdout, \"[ERROR] \", log.LstdFlags)\n\tdebugLog    = log.New(os.Stdout, \"[DEBUG] \", log.LstdFlags)\n\trequestLog  = log.New(os.Stdout, \"[>>>>>] \", log.LstdFlags)\n\tresponseLog = log.New(os.Stdout, \"[<<<<<] \", log.LstdFlags)\n\n\tverbose  bool\n\tcolorize bool\n)\n\nfunc init() {\n\tflag.BoolVar((*bool)(&info), \"info\", true, \"info log\")\n\tflag.BoolVar((*bool)(&debug), \"debug\", false, \"debug log, with this option, log goes to stdout with color\")\n\tflag.BoolVar((*bool)(&errl), \"err\", true, \"error log\")\n\tflag.BoolVar((*bool)(&dbgRq), \"request\", false, \"request log\")\n\tflag.BoolVar((*bool)(&dbgRep), \"reply\", false, \"reply log\")\n\tflag.BoolVar(&verbose, \"v\", false, \"more info in request/response logging\")\n\tflag.BoolVar(&colorize, \"color\", false, \"colorize log output\")\n}\n\nfunc initLog() {\n\tlogFile = os.Stdout\n\tif config.LogFile != \"\" {\n\t\tif f, err := os.OpenFile(expandTilde(config.LogFile),\n\t\t\tos.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600); err != nil {\n\t\t\tfmt.Printf(\"Can't open log file, logging to stdout: %v\\n\", err)\n\t\t} else {\n\t\t\tlogFile = f\n\t\t}\n\t}\n\tlog.SetOutput(logFile)\n\tif colorize {\n\t\tcolor.SetDefaultColor(color.ANSI)\n\t} else {\n\t\tcolor.SetDefaultColor(color.NoColor)\n\t}\n\terrorLog = log.New(logFile, color.Red(\"[ERROR] \"), log.LstdFlags)\n\tdebugLog = log.New(logFile, color.Blue(\"[DEBUG] \"), log.LstdFlags)\n\trequestLog = log.New(logFile, color.Green(\"[>>>>>] \"), log.LstdFlags)\n\tresponseLog = log.New(logFile, color.Yellow(\"[<<<<<] \"), log.LstdFlags)\n}\n\nfunc (d infoLogging) Printf(format string, args ...interface{}) {\n\tif d {\n\t\tlog.Printf(format, args...)\n\t}\n}\n\nfunc (d infoLogging) Println(args ...interface{}) {\n\tif d {\n\t\tlog.Println(args...)\n\t}\n}\n\nfunc (d debugLogging) Printf(format string, args ...interface{}) {\n\tif d {\n\t\tdebugLog.Printf(format, args...)\n\t}\n}\n\nfunc (d debugLogging) Println(args ...interface{}) {\n\tif d {\n\t\tdebugLog.Println(args...)\n\t}\n}\n\nfunc (d errorLogging) Printf(format string, args ...interface{}) {\n\tif d {\n\t\terrorLog.Printf(format, args...)\n\t}\n}\n\nfunc (d errorLogging) Println(args ...interface{}) {\n\tif d {\n\t\terrorLog.Println(args...)\n\t}\n}\n\nfunc (d requestLogging) Printf(format string, args ...interface{}) {\n\tif d {\n\t\trequestLog.Printf(format, args...)\n\t}\n}\n\nfunc (d responseLogging) Printf(format string, args ...interface{}) {\n\tif d {\n\t\tresponseLog.Printf(format, args...)\n\t}\n}\n\nfunc Fatal(args ...interface{}) {\n\tfmt.Println(args...)\n\tos.Exit(1)\n}\n\nfunc Fatalf(format string, args ...interface{}) {\n\tfmt.Printf(format, args...)\n\tos.Exit(1)\n}\n"
  },
  {
    "path": "main.go",
    "content": "package main\n\nimport (\n\t// \"flag\"\n\t\"os\"\n\t\"os/exec\"\n\t\"runtime\"\n\t// \"runtime/pprof\"\n\t\"sync\"\n\t\"syscall\"\n)\n\n// var cpuprofile = flag.String(\"cpuprofile\", \"\", \"write cpu profile to file\")\nvar (\n\tquit     chan struct{}\n\trelaunch bool\n)\n\n// This code is from goagain\nfunc lookPath() (argv0 string, err error) {\n\targv0, err = exec.LookPath(os.Args[0])\n\tif nil != err {\n\t\treturn\n\t}\n\tif _, err = os.Stat(argv0); nil != err {\n\t\treturn\n\t}\n\treturn\n}\n\nfunc main() {\n\tquit = make(chan struct{})\n\t// Parse flags after load config to allow override options in config\n\tcmdLineConfig := parseCmdLineConfig()\n\tif cmdLineConfig.PrintVer {\n\t\tprintVersion()\n\t\tos.Exit(0)\n\t}\n\n\tparseConfig(cmdLineConfig.RcFile, cmdLineConfig)\n\n\tinitSelfListenAddr()\n\tinitLog()\n\tinitAuth()\n\tinitSiteStat()\n\tinitPAC() // initPAC uses siteStat, so must init after site stat\n\n\tinitStat()\n\n\tinitParentPool()\n\n\t/*\n\t\tif *cpuprofile != \"\" {\n\t\t\tf, err := os.Create(*cpuprofile)\n\t\t\tif err != nil {\n\t\t\t\tFatal(err)\n\t\t\t}\n\t\t\tpprof.StartCPUProfile(f)\n\t\t}\n\t*/\n\n\tif config.Core > 0 {\n\t\truntime.GOMAXPROCS(config.Core)\n\t}\n\n\tgo sigHandler()\n\tgo runSSH()\n\tif config.EstimateTimeout {\n\t\tgo runEstimateTimeout()\n\t} else {\n\t\tinfo.Println(\"timeout estimation disabled\")\n\t}\n\n\tvar wg sync.WaitGroup\n\twg.Add(len(listenProxy))\n\tfor _, proxy := range listenProxy {\n\t\tgo proxy.Serve(&wg, quit)\n\t}\n\n\twg.Wait()\n\n\tif relaunch {\n\t\tinfo.Println(\"Relunching cow...\")\n\t\t// Need to fork me.\n\t\targv0, err := lookPath()\n\t\tif nil != err {\n\t\t\terrl.Println(err)\n\t\t\treturn\n\t\t}\n\n\t\terr = syscall.Exec(argv0, os.Args, os.Environ())\n\t\tif err != nil {\n\t\t\terrl.Println(err)\n\t\t}\n\t}\n\tdebug.Println(\"the main process is , exiting...\")\n}\n"
  },
  {
    "path": "main_unix.go",
    "content": "// +build darwin freebsd linux netbsd openbsd\n\npackage main\n\nimport (\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n)\n\nfunc sigHandler() {\n\tsigChan := make(chan os.Signal, 1)\n\tsignal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1)\n\n\tfor sig := range sigChan {\n\t\t// May handle other signals in the future.\n\t\tinfo.Printf(\"%v caught, exit\\n\", sig)\n\t\tstoreSiteStat(siteStatExit)\n\t\tif sig == syscall.SIGUSR1 {\n\t\t\trelaunch = true\n\t\t}\n\t\tclose(quit)\n\t\tbreak\n\t}\n\t/*\n\t\tif *cpuprofile != \"\" {\n\t\t\tpprof.StopCPUProfile()\n\t\t}\n\t*/\n}\n"
  },
  {
    "path": "main_windows.go",
    "content": "package main\n\nimport (\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n)\n\nfunc sigHandler() {\n\t// TODO On Windows, these signals will not be triggered on closing cmd\n\t// window. How to detect this?\n\tsigChan := make(chan os.Signal, 1)\n\tsignal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)\n\n\tfor sig := range sigChan {\n\t\t// May handle other signals in the future.\n\t\tinfo.Printf(\"%v caught, exit\\n\", sig)\n\t\tstoreSiteStat(siteStatExit)\n\t\t// Windows has no SIGUSR1 signal, so relaunching is not supported now.\n\t\t/*\n\t\t\tif sig == syscall.SIGUSR1 {\n\t\t\t\trelaunch = true\n\t\t\t}\n\t\t*/\n\t\tclose(quit)\n\t\tbreak\n\t}\n\t/*\n\t\tif *cpuprofile != \"\" {\n\t\t\tpprof.StopCPUProfile()\n\t\t}\n\t*/\n}\n"
  },
  {
    "path": "pac.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"net\"\n\t\"strings\"\n\t\"sync\"\n\t\"text/template\"\n\t\"time\"\n)\n\nvar pac struct {\n\ttemplate       *template.Template\n\ttopLevelDomain string\n\tdirectList     string\n\t// Assignments and reads to directList are in different goroutines. Go\n\t// does not guarantee atomic assignment, so we should protect these racing\n\t// access.\n\tdLRWMutex sync.RWMutex\n}\n\nfunc getDirectList() string {\n\tpac.dLRWMutex.RLock()\n\tdl := pac.directList\n\tpac.dLRWMutex.RUnlock()\n\treturn dl\n}\n\nfunc updateDirectList() {\n\tdl := strings.Join(siteStat.GetDirectList(), \"\\\",\\n\\\"\")\n\tpac.dLRWMutex.Lock()\n\tpac.directList = dl\n\tpac.dLRWMutex.Unlock()\n}\n\nfunc init() {\n\tconst pacRawTmpl = `var direct = 'DIRECT';\nvar httpProxy = 'PROXY {{.ProxyAddr}}; DIRECT';\n\nvar directList = [\n\"\",\n\"{{.DirectDomains}}\"\n];\n\nvar directAcc = {};\nfor (var i = 0; i < directList.length; i += 1) {\n\tdirectAcc[directList[i]] = true;\n}\n\nvar topLevel = {\n{{.TopLevel}}\n};\n\n// hostIsIP determines whether a host address is an IP address and whether\n// it is private. Currenly only handles IPv4 addresses.\nfunction hostIsIP(host) {\n\tvar part = host.split('.');\n\tif (part.length != 4) {\n\t\treturn [false, false];\n\t}\n\tvar n;\n\tfor (var i = 3; i >= 0; i--) {\n\t\tif (part[i].length === 0 || part[i].length > 3) {\n\t\t\treturn [false, false];\n\t\t}\n\t\tn = Number(part[i]);\n\t\tif (isNaN(n) || n < 0 || n > 255) {\n\t\t\treturn [false, false];\n\t\t}\n\t}\n\tif (part[0] == '127' || part[0] == '10' || (part[0] == '192' && part[1] == '168')) {\n\t\treturn [true, true];\n\t}\n\tif (part[0] == '172') {\n\t\tn = Number(part[1]);\n\t\tif (16 <= n && n <= 31) {\n\t\t\treturn [true, true];\n\t\t}\n\t}\n\treturn [true, false];\n}\n\nfunction host2Domain(host) {\n\tvar arr, isIP, isPrivate;\n\tarr = hostIsIP(host);\n\tisIP = arr[0];\n\tisPrivate = arr[1];\n\tif (isPrivate) {\n\t\treturn \"\";\n\t}\n\tif (isIP) {\n\t\treturn host;\n\t}\n\n\tvar lastDot = host.lastIndexOf('.');\n\tif (lastDot === -1) {\n\t\treturn \"\"; // simple host name has no domain\n\t}\n\t// Find the second last dot\n\tdot2ndLast = host.lastIndexOf(\".\", lastDot-1);\n\tif (dot2ndLast === -1)\n\t\treturn host;\n\n\tvar part = host.substring(dot2ndLast+1, lastDot);\n\tif (topLevel[part]) {\n\t\tvar dot3rdLast = host.lastIndexOf(\".\", dot2ndLast-1);\n\t\tif (dot3rdLast === -1) {\n\t\t\treturn host;\n\t\t}\n\t\treturn host.substring(dot3rdLast+1);\n\t}\n\treturn host.substring(dot2ndLast+1);\n}\n\nfunction FindProxyForURL(url, host) {\n\tif (url.substring(0,4) == \"ftp:\")\n\t\treturn direct;\n\tif (host.substring(0,7) == \"::ffff:\")\n\t\treturn direct;\n\tif (host.indexOf(\".local\", host.length - 6) !== -1) {\n\t\treturn direct;\n\t}\n\tvar domain = host2Domain(host);\n\tif (host.length == domain.length) {\n\t\treturn directAcc[host] ? direct : httpProxy;\n\t}\n\treturn (directAcc[host] || directAcc[domain]) ? direct : httpProxy;\n}\n`\n\tvar err error\n\tpac.template, err = template.New(\"pac\").Parse(pacRawTmpl)\n\tif err != nil {\n\t\tFatal(\"Internal error on generating pac file template:\", err)\n\t}\n\n\tvar buf bytes.Buffer\n\tfor k, _ := range topLevelDomain {\n\t\tbuf.WriteString(fmt.Sprintf(\"\\t\\\"%s\\\": true,\\n\", k))\n\t}\n\tpac.topLevelDomain = buf.String()[:buf.Len()-2] // remove the final comma\n}\n\n// No need for content-length as we are closing connection\nvar pacHeader = []byte(\"HTTP/1.1 200 OK\\r\\nServer: cow-proxy\\r\\n\" +\n\t\"Content-Type: application/x-ns-proxy-autoconfig\\r\\nConnection: close\\r\\n\\r\\n\")\n\n// Different client will have different proxy URL, so generate it upon each request.\nfunc genPAC(c *clientConn) []byte {\n\tbuf := new(bytes.Buffer)\n\n\thproxy, ok := c.proxy.(*httpProxy)\n\tif !ok {\n\t\tpanic(\"sendPAC should only be called for http proxy\")\n\t}\n\n\tproxyAddr := hproxy.addrInPAC\n\tif proxyAddr == \"\" {\n\t\thost, _, err := net.SplitHostPort(c.LocalAddr().String())\n\t\t// This is the only check to split host port on tcp addr's string\n\t\t// representation in COW. Keep it so we will notice if there's any\n\t\t// problem in the future.\n\t\tif err != nil {\n\t\t\tpanic(\"split host port on local address error\")\n\t\t}\n\t\tproxyAddr = net.JoinHostPort(host, hproxy.port)\n\t}\n\n\tdl := getDirectList()\n\n\tif dl == \"\" {\n\t\t// Empty direct domain list\n\t\tbuf.Write(pacHeader)\n\t\tpacproxy := fmt.Sprintf(\"function FindProxyForURL(url, host) { return 'PROXY %s; DIRECT'; };\",\n\t\t\tproxyAddr)\n\t\tbuf.Write([]byte(pacproxy))\n\t\treturn buf.Bytes()\n\t}\n\n\tdata := struct {\n\t\tProxyAddr     string\n\t\tDirectDomains string\n\t\tTopLevel      string\n\t}{\n\t\tproxyAddr,\n\t\tdl,\n\t\tpac.topLevelDomain,\n\t}\n\n\tbuf.Write(pacHeader)\n\tif err := pac.template.Execute(buf, data); err != nil {\n\t\terrl.Println(\"Error generating pac file:\", err)\n\t\tpanic(\"Error generating pac file\")\n\t}\n\treturn buf.Bytes()\n}\n\nfunc initPAC() {\n\t// we can't control goroutine scheduling, make sure when\n\t// initPAC is done, direct list is updated\n\tupdateDirectList()\n\tgo func() {\n\t\tfor {\n\t\t\ttime.Sleep(time.Minute)\n\t\t\tupdateDirectList()\n\t\t}\n\t}()\n}\n\nfunc sendPAC(c *clientConn) error {\n\t_, err := c.Write(genPAC(c))\n\tif err != nil {\n\t\tdebug.Printf(\"cli(%s) error sending PAC: %s\", c.RemoteAddr(), err)\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "pac.js",
    "content": "var direct = 'DIRECT';\nvar httpProxy = 'PROXY';\n\nvar directList = [\n\t\"\", // corresponds to simple host name and ip address\n\t\"taobao.com\",\n\t\"www.baidu.com\"\n];\n\nvar directAcc = {};\nfor (var i = 0; i < directList.length; i += 1) {\n\tdirectAcc[directList[i]] = true;\n}\n\nvar topLevel = {\n        \"ac\": true,\n        \"co\": true,\n        \"com\": true,\n        \"edu\": true,\n        \"gov\": true,\n        \"net\": true,\n        \"org\": true\n};\n\n// hostIsIP determines whether a host address is an IP address and whether\n// it is private. Currenly only handles IPv4 addresses.\nfunction hostIsIP(host) {\n\tvar part = host.split('.');\n\tif (part.length != 4) {\n\t\treturn [false, false];\n\t}\n\tvar n;\n\tfor (var i = 3; i >= 0; i--) {\n\t\tif (part[i].length === 0 || part[i].length > 3) {\n\t\t\treturn [false, false];\n\t\t}\n\t\tn = Number(part[i]);\n\t\tif (isNaN(n) || n < 0 || n > 255) {\n\t\t\treturn [false, false];\n\t\t}\n\t}\n\tif (part[0] == '127' || part[0] == '10' || (part[0] == '192' && part[1] == '168')) {\n\t\treturn [true, true];\n\t}\n\tif (part[0] == '172') {\n\t\tn = Number(part[1]);\n\t\tif (16 <= n && n <= 31) {\n\t\t\treturn [true, true];\n\t\t}\n\t}\n\treturn [true, false];\n}\n\nfunction host2Domain(host) {\n\tvar arr, isIP, isPrivate;\n\tarr = hostIsIP(host);\n\tisIP = arr[0];\n\tisPrivate = arr[1];\n\tif (isPrivate) {\n\t\treturn \"\";\n\t}\n\tif (isIP) {\n\t\treturn host;\n\t}\n\n\tvar lastDot = host.lastIndexOf('.');\n\tif (lastDot === -1) {\n\t\treturn \"\"; // simple host name has no domain\n\t}\n\t// Find the second last dot\n\tdot2ndLast = host.lastIndexOf(\".\", lastDot-1);\n\tif (dot2ndLast === -1)\n\t\treturn host;\n\n\tvar part = host.substring(dot2ndLast+1, lastDot);\n\tif (topLevel[part]) {\n\t\tvar dot3rdLast = host.lastIndexOf(\".\", dot2ndLast-1);\n\t\tif (dot3rdLast === -1) {\n\t\t\treturn host;\n\t\t}\n\t\treturn host.substring(dot3rdLast+1);\n\t}\n\treturn host.substring(dot2ndLast+1);\n}\n\nfunction FindProxyForURL(url, host) {\n\tif (url.substring(0,4) == \"ftp:\")\n\t\treturn direct;\n\tif (host.indexOf(\".local\", host.length - 6) !== -1) {\n\t\treturn direct;\n\t}\n\tvar domain = host2Domain(host);\n\tif (host.length == domain.length) {\n\t\treturn directAcc[host] ? direct : httpProxy;\n\t}\n\treturn (directAcc[host] || directAcc[domain]) ? direct : httpProxy;\n}\n\n// Tests\n\nvar testData, td, i;\n\ntestData = [\n\t{ ip: '127.0.0.1', isIP: true, isPrivate: true },\n\t{ ip: '127.2.1.1', isIP: true, isPrivate: true },\n\t{ ip: '192.168.1.1', isIP: true, isPrivate: true },\n\t{ ip: '172.16.1.1', isIP: true, isPrivate: true },\n\t{ ip: '172.20.1.1', isIP: true, isPrivate: true },\n\t{ ip: '172.31.1.1', isIP: true, isPrivate: true },\n\t{ ip: '172.15.1.1', isIP: true, isPrivate: false },\n\t{ ip: '172.32.1.1', isIP: true, isPrivate: false },\n\t{ ip: '10.16.1.1', isIP: true, isPrivate: true },\n\t{ ip: '12.3.4.5', isIP: true, isPrivate: false },\n\t{ ip: '1.2.3.4.5', isIP: false, isPrivate: false },\n\t{ ip: 'google.com', isIP: false, isPrivate: false },\n\t{ ip: 'www.google.com.hk', isIP: false, isPrivate: false }\n];\n\nfor (i = 0; i < testData.length; i += 1) {\n\ttd = testData[i];\n\tarr = hostIsIP(td.ip);\n\tif (arr[0] !== td.isIP) {\n\t\tif (td.isIP) {\n\t\t\tconsole.log(td.ip + \" is ip\");\n\t\t} else {\n\t\t\tconsole.log(td.ip + \" is NOT ip\");\n\t\t}\n\t}\n\tif (arr[0] !== td.isIP) {\n\t\tif (td.isIP) {\n\t\t\tconsole.log(td.ip + \" is private ip\");\n\t\t} else {\n\t\t\tconsole.log(td.ip + \" is NOT private ip\");\n\t\t}\n\t}\n}\n\ntestData = [\n\t// private ip should return direct\n\t{ host: '192.168.1.1', mode: direct},\n\t{ host: '10.1.1.1', mode: direct},\n\t{ host: '172.16.2.1', mode: direct},\n\t{ host: '172.20.255.255', mode: direct},\n\t{ host: '172.31.255.255', mode: direct},\n\t{ host: '192.168.2.255', mode: direct},\n\n\t// simple host should return direct\n\t{ host: 'localhost', mode: direct},\n\t{ host: 'simple', mode: direct},\n\n\t// non private ip should return proxy\n\t{ host: '172.32.2.255', mode: httpProxy},\n\t{ host: '172.15.0.255', mode: httpProxy},\n\t{ host: '12.20.2.1', mode: httpProxy},\n\n\t// host in direct domain/host should return direct\n\t{ host: 'taobao.com', mode: direct},\n\t{ host: 'www.taobao.com', mode: direct},\n\t{ host: 'www.baidu.com', mode: direct},\n\n\t// host not in direct domain should return proxy\n\t{ host: 'baidu.com', mode: httpProxy},\n\t{ host: 'foo.baidu.com', mode: httpProxy},\n\t{ host: 'google.com', mode: httpProxy},\n\t{ host: 'www.google.com', mode: httpProxy},\n\t{ host: 'www.google.com.hk', mode: httpProxy},\n\n\t// host in local domain should return direct\n\t{ host: 'test.local', mode: direct},\n\t{ host: '.local', mode: direct},\n];\n\nfor (i = 0; i < testData.length; i += 1) {\n\ttd = testData[i];\n\tif (FindProxyForURL(\"\", td.host) !== td.mode) {\n\t\tif (td.mode === direct) {\n\t\t\tconsole.log(td.host + \" should return direct\");\n\t\t} else {\n\t\t\tconsole.log(td.host + \" should return proxy\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "parent_proxy.go",
    "content": "package main\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\tss \"github.com/shadowsocks/shadowsocks-go/shadowsocks\"\n\t\"hash/crc32\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net\"\n\t\"sort\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n)\n\n// Interface that all types of parent proxies should support.\ntype ParentProxy interface {\n\tconnect(*URL) (net.Conn, error)\n\tgetServer() string // for use in updating server latency\n\tgenConfig() string // for upgrading config\n}\n\n// Interface for different proxy selection strategy.\ntype ParentPool interface {\n\tadd(ParentProxy)\n\tempty() bool\n\t// Select a proxy from the pool and connect. May try several proxies until\n\t// one that succees, return nil and error if all parent proxies fail.\n\tconnect(*URL) (net.Conn, error)\n}\n\n// Init parentProxy to be backup pool. So config parsing have a pool to add\n// parent proxies.\nvar parentProxy ParentPool = &backupParentPool{}\n\nfunc initParentPool() {\n\tbackPool, ok := parentProxy.(*backupParentPool)\n\tif !ok {\n\t\tpanic(\"initial parent pool should be backup pool\")\n\t}\n\tif debug {\n\t\tprintParentProxy(backPool.parent)\n\t}\n\tif len(backPool.parent) == 0 {\n\t\tinfo.Println(\"no parent proxy server\")\n\t\treturn\n\t}\n\tif len(backPool.parent) == 1 && config.LoadBalance != loadBalanceBackup {\n\t\tdebug.Println(\"only 1 parent, no need for load balance\")\n\t\tconfig.LoadBalance = loadBalanceBackup\n\t}\n\n\tswitch config.LoadBalance {\n\tcase loadBalanceHash:\n\t\tdebug.Println(\"hash parent pool\", len(backPool.parent))\n\t\tparentProxy = &hashParentPool{*backPool}\n\tcase loadBalanceLatency:\n\t\tdebug.Println(\"latency parent pool\", len(backPool.parent))\n\t\tgo updateParentProxyLatency()\n\t\tparentProxy = newLatencyParentPool(backPool.parent)\n\t}\n}\n\nfunc printParentProxy(parent []ParentWithFail) {\n\tdebug.Println(\"avaiable parent proxies:\")\n\tfor _, pp := range parent {\n\t\tswitch pc := pp.ParentProxy.(type) {\n\t\tcase *shadowsocksParent:\n\t\t\tdebug.Println(\"\\tshadowsocks: \", pc.server)\n\t\tcase *httpParent:\n\t\t\tdebug.Println(\"\\thttp parent: \", pc.server)\n\t\tcase *socksParent:\n\t\t\tdebug.Println(\"\\tsocks parent: \", pc.server)\n\t\tcase *cowParent:\n\t\t\tdebug.Println(\"\\tcow parent: \", pc.server)\n\t\t}\n\t}\n}\n\ntype ParentWithFail struct {\n\tParentProxy\n\tfail int\n}\n\n// Backup load balance strategy:\n// Select proxy in the order they appear in config.\ntype backupParentPool struct {\n\tparent []ParentWithFail\n}\n\nfunc (pp *backupParentPool) empty() bool {\n\treturn len(pp.parent) == 0\n}\n\nfunc (pp *backupParentPool) add(parent ParentProxy) {\n\tpp.parent = append(pp.parent, ParentWithFail{parent, 0})\n}\n\nfunc (pp *backupParentPool) connect(url *URL) (srvconn net.Conn, err error) {\n\treturn connectInOrder(url, pp.parent, 0)\n}\n\n// Hash load balance strategy:\n// Each host will use a proxy based on a hash value.\ntype hashParentPool struct {\n\tbackupParentPool\n}\n\nfunc (pp *hashParentPool) connect(url *URL) (srvconn net.Conn, err error) {\n\tstart := int(crc32.ChecksumIEEE([]byte(url.Host)) % uint32(len(pp.parent)))\n\tdebug.Printf(\"hash host %s try %d parent first\", url.Host, start)\n\treturn connectInOrder(url, pp.parent, start)\n}\n\nfunc (parent *ParentWithFail) connect(url *URL) (srvconn net.Conn, err error) {\n\tconst maxFailCnt = 30\n\tsrvconn, err = parent.ParentProxy.connect(url)\n\tif err != nil {\n\t\tif parent.fail < maxFailCnt && !networkBad() {\n\t\t\tparent.fail++\n\t\t}\n\t\treturn\n\t}\n\tparent.fail = 0\n\treturn\n}\n\nfunc connectInOrder(url *URL, pp []ParentWithFail, start int) (srvconn net.Conn, err error) {\n\tconst baseFailCnt = 9\n\tvar skipped []int\n\tnproxy := len(pp)\n\n\tif nproxy == 0 {\n\t\treturn nil, errors.New(\"no parent proxy\")\n\t}\n\n\tfor i := 0; i < nproxy; i++ {\n\t\tproxyId := (start + i) % nproxy\n\t\tparent := &pp[proxyId]\n\t\t// skip failed server, but try it with some probability\n\t\tif parent.fail > 0 && rand.Intn(parent.fail+baseFailCnt) != 0 {\n\t\t\tskipped = append(skipped, proxyId)\n\t\t\tcontinue\n\t\t}\n\t\tif srvconn, err = parent.connect(url); err == nil {\n\t\t\treturn\n\t\t}\n\t}\n\t// last resort, try skipped one, not likely to succeed\n\tfor _, skippedId := range skipped {\n\t\tif srvconn, err = pp[skippedId].connect(url); err == nil {\n\t\t\treturn\n\t\t}\n\t}\n\treturn nil, err\n}\n\ntype ParentWithLatency struct {\n\tParentProxy\n\tlatency time.Duration\n}\n\ntype latencyParentPool struct {\n\tparent []ParentWithLatency\n}\n\nfunc newLatencyParentPool(parent []ParentWithFail) *latencyParentPool {\n\tlp := &latencyParentPool{}\n\tfor _, p := range parent {\n\t\tlp.add(p.ParentProxy)\n\t}\n\treturn lp\n}\n\nfunc (pp *latencyParentPool) empty() bool {\n\treturn len(pp.parent) == 0\n}\n\nfunc (pp *latencyParentPool) add(parent ParentProxy) {\n\tpp.parent = append(pp.parent, ParentWithLatency{parent, 0})\n}\n\n// Sort interface.\nfunc (pp *latencyParentPool) Len() int {\n\treturn len(pp.parent)\n}\n\nfunc (pp *latencyParentPool) Swap(i, j int) {\n\tp := pp.parent\n\tp[i], p[j] = p[j], p[i]\n}\n\nfunc (pp *latencyParentPool) Less(i, j int) bool {\n\tp := pp.parent\n\treturn p[i].latency < p[j].latency\n}\n\nconst latencyMax = time.Hour\n\nvar latencyMutex sync.RWMutex\n\nfunc (pp *latencyParentPool) connect(url *URL) (srvconn net.Conn, err error) {\n\tvar lp []ParentWithLatency\n\t// Read slice first.\n\tlatencyMutex.RLock()\n\tlp = pp.parent\n\tlatencyMutex.RUnlock()\n\n\tvar skipped []int\n\tnproxy := len(lp)\n\tif nproxy == 0 {\n\t\treturn nil, errors.New(\"no parent proxy\")\n\t}\n\n\tfor i := 0; i < nproxy; i++ {\n\t\tparent := lp[i]\n\t\tif parent.latency >= latencyMax {\n\t\t\tskipped = append(skipped, i)\n\t\t\tcontinue\n\t\t}\n\t\tif srvconn, err = parent.connect(url); err == nil {\n\t\t\tdebug.Println(\"lowest latency proxy\", parent.getServer())\n\t\t\treturn\n\t\t}\n\t\tparent.latency = latencyMax\n\t}\n\t// last resort, try skipped one, not likely to succeed\n\tfor _, skippedId := range skipped {\n\t\tif srvconn, err = lp[skippedId].connect(url); err == nil {\n\t\t\treturn\n\t\t}\n\t}\n\treturn nil, err\n}\n\nfunc (parent *ParentWithLatency) updateLatency(wg *sync.WaitGroup) {\n\tdefer wg.Done()\n\tproxy := parent.ParentProxy\n\tserver := proxy.getServer()\n\n\thost, port, err := net.SplitHostPort(server)\n\tif err != nil {\n\t\tpanic(\"split host port parent server error\" + err.Error())\n\t}\n\n\t// Resolve host name first, so latency does not include resolve time.\n\tip, err := net.LookupHost(host)\n\tif err != nil {\n\t\tparent.latency = latencyMax\n\t\treturn\n\t}\n\tipPort := net.JoinHostPort(ip[0], port)\n\n\tconst N = 3\n\tvar total time.Duration\n\tfor i := 0; i < N; i++ {\n\t\tnow := time.Now()\n\t\tcn, err := net.DialTimeout(\"tcp\", ipPort, dialTimeout)\n\t\tif err != nil {\n\t\t\tdebug.Println(\"latency update dial:\", err)\n\t\t\ttotal += time.Minute // 1 minute as penalty\n\t\t\tcontinue\n\t\t}\n\t\ttotal += time.Now().Sub(now)\n\t\tcn.Close()\n\n\t\ttime.Sleep(5 * time.Millisecond)\n\t}\n\tparent.latency = total / N\n\tdebug.Println(\"latency\", server, parent.latency)\n}\n\nfunc (pp *latencyParentPool) updateLatency() {\n\t// Create a copy, update latency for the copy.\n\tvar cp latencyParentPool\n\tcp.parent = append(cp.parent, pp.parent...)\n\n\t// cp.parent is value instead of pointer, if we use `_, p := range cp.parent`,\n\t// the value in cp.parent will not be updated.\n\tvar wg sync.WaitGroup\n\twg.Add(len(cp.parent))\n\tfor i, _ := range cp.parent {\n\t\tcp.parent[i].updateLatency(&wg)\n\t}\n\twg.Wait()\n\n\t// Sort according to latency.\n\tsort.Stable(&cp)\n\tdebug.Println(\"latency lowest proxy\", cp.parent[0].getServer())\n\n\t// Update parent slice.\n\tlatencyMutex.Lock()\n\tpp.parent = cp.parent\n\tlatencyMutex.Unlock()\n}\n\nfunc updateParentProxyLatency() {\n\tlp, ok := parentProxy.(*latencyParentPool)\n\tif !ok {\n\t\treturn\n\t}\n\n\tfor {\n\t\tlp.updateLatency()\n\t\ttime.Sleep(60 * time.Second)\n\t}\n}\n\n// http parent proxy\ntype httpParent struct {\n\tserver     string\n\tuserPasswd string // for upgrade config\n\tauthHeader []byte\n}\n\ntype httpConn struct {\n\tnet.Conn\n\tparent *httpParent\n}\n\nfunc (s httpConn) String() string {\n\treturn \"http parent proxy \" + s.parent.server\n}\n\nfunc newHttpParent(server string) *httpParent {\n\treturn &httpParent{server: server}\n}\n\nfunc (hp *httpParent) getServer() string {\n\treturn hp.server\n}\n\nfunc (hp *httpParent) genConfig() string {\n\tif hp.userPasswd != \"\" {\n\t\treturn fmt.Sprintf(\"proxy = http://%s@%s\", hp.userPasswd, hp.server)\n\t} else {\n\t\treturn fmt.Sprintf(\"proxy = http://%s\", hp.server)\n\t}\n}\n\nfunc (hp *httpParent) initAuth(userPasswd string) {\n\tif userPasswd == \"\" {\n\t\treturn\n\t}\n\thp.userPasswd = userPasswd\n\tb64 := base64.StdEncoding.EncodeToString([]byte(userPasswd))\n\thp.authHeader = []byte(headerProxyAuthorization + \": Basic \" + b64 + CRLF)\n}\n\nfunc (hp *httpParent) connect(url *URL) (net.Conn, error) {\n\tc, err := net.Dial(\"tcp\", hp.server)\n\tif err != nil {\n\t\terrl.Printf(\"can't connect to http parent %s for %s: %v\\n\",\n\t\t\thp.server, url.HostPort, err)\n\t\treturn nil, err\n\t}\n\tdebug.Printf(\"connected to: %s via http parent: %s\\n\",\n\t\turl.HostPort, hp.server)\n\treturn httpConn{c, hp}, nil\n}\n\n// shadowsocks parent proxy\ntype shadowsocksParent struct {\n\tserver string\n\tmethod string // method and passwd are for upgrade config\n\tpasswd string\n\tcipher *ss.Cipher\n}\n\ntype shadowsocksConn struct {\n\tnet.Conn\n\tparent *shadowsocksParent\n}\n\nfunc (s shadowsocksConn) String() string {\n\treturn \"shadowsocks proxy \" + s.parent.server\n}\n\n// In order to use parent proxy in the order specified in the config file, we\n// insert an uninitialized proxy into parent proxy list, and initialize it\n// when all its config have been parsed.\n\nfunc newShadowsocksParent(server string) *shadowsocksParent {\n\treturn &shadowsocksParent{server: server}\n}\n\nfunc (sp *shadowsocksParent) getServer() string {\n\treturn sp.server\n}\n\nfunc (sp *shadowsocksParent) genConfig() string {\n\tmethod := sp.method\n\tif method == \"\" {\n\t\tmethod = \"table\"\n\t}\n\treturn fmt.Sprintf(\"proxy = ss://%s:%s@%s\", method, sp.passwd, sp.server)\n}\n\nfunc (sp *shadowsocksParent) initCipher(method, passwd string) {\n\tsp.method = method\n\tsp.passwd = passwd\n\tcipher, err := ss.NewCipher(method, passwd)\n\tif err != nil {\n\t\tFatal(\"create shadowsocks cipher:\", err)\n\t}\n\tsp.cipher = cipher\n}\n\nfunc (sp *shadowsocksParent) connect(url *URL) (net.Conn, error) {\n\tc, err := ss.Dial(url.HostPort, sp.server, sp.cipher.Copy())\n\tif err != nil {\n\t\terrl.Printf(\"can't connect to shadowsocks parent %s for %s: %v\\n\",\n\t\t\tsp.server, url.HostPort, err)\n\t\treturn nil, err\n\t}\n\tdebug.Println(\"connected to:\", url.HostPort, \"via shadowsocks:\", sp.server)\n\treturn shadowsocksConn{c, sp}, nil\n}\n\n// cow parent proxy\ntype cowParent struct {\n\tserver string\n\tmethod string\n\tpasswd string\n\tcipher *ss.Cipher\n}\n\ntype cowConn struct {\n\tnet.Conn\n\tparent *cowParent\n}\n\nfunc (s cowConn) String() string {\n\treturn \"cow proxy \" + s.parent.server\n}\n\nfunc newCowParent(srv, method, passwd string) *cowParent {\n\tcipher, err := ss.NewCipher(method, passwd)\n\tif err != nil {\n\t\tFatal(\"create cow cipher:\", err)\n\t}\n\treturn &cowParent{srv, method, passwd, cipher}\n}\n\nfunc (cp *cowParent) getServer() string {\n\treturn cp.server\n}\n\nfunc (cp *cowParent) genConfig() string {\n\tmethod := cp.method\n\tif method == \"\" {\n\t\tmethod = \"table\"\n\t}\n\treturn fmt.Sprintf(\"proxy = cow://%s:%s@%s\", method, cp.passwd, cp.server)\n}\n\nfunc (cp *cowParent) connect(url *URL) (net.Conn, error) {\n\tc, err := net.Dial(\"tcp\", cp.server)\n\tif err != nil {\n\t\terrl.Printf(\"can't connect to cow parent %s for %s: %v\\n\",\n\t\t\tcp.server, url.HostPort, err)\n\t\treturn nil, err\n\t}\n\tdebug.Printf(\"connected to: %s via cow parent: %s\\n\",\n\t\turl.HostPort, cp.server)\n\tssconn := ss.NewConn(c, cp.cipher.Copy())\n\treturn cowConn{ssconn, cp}, nil\n}\n\n// For socks documentation, refer to rfc 1928 http://www.ietf.org/rfc/rfc1928.txt\n\nvar socksError = [...]string{\n\t1: \"General SOCKS server failure\",\n\t2: \"Connection not allowed by ruleset\",\n\t3: \"Network unreachable\",\n\t4: \"Host unreachable\",\n\t5: \"Connection refused\",\n\t6: \"TTL expired\",\n\t7: \"Command not supported\",\n\t8: \"Address type not supported\",\n\t9: \"to X'FF' unassigned\",\n}\n\nvar socksProtocolErr = errors.New(\"socks protocol error\")\n\nvar socksMsgVerMethodSelection = []byte{\n\t0x5, // version 5\n\t1,   // n method\n\t0,   // no authorization required\n}\n\n// socks5 parent proxy\ntype socksParent struct {\n\tserver string\n}\n\ntype socksConn struct {\n\tnet.Conn\n\tparent *socksParent\n}\n\nfunc (s socksConn) String() string {\n\treturn \"socks proxy \" + s.parent.server\n}\n\nfunc newSocksParent(server string) *socksParent {\n\treturn &socksParent{server}\n}\n\nfunc (sp *socksParent) getServer() string {\n\treturn sp.server\n}\n\nfunc (sp *socksParent) genConfig() string {\n\treturn fmt.Sprintf(\"proxy = socks5://%s\", sp.server)\n}\n\nfunc (sp *socksParent) connect(url *URL) (net.Conn, error) {\n\tc, err := net.Dial(\"tcp\", sp.server)\n\tif err != nil {\n\t\terrl.Printf(\"can't connect to socks parent %s for %s: %v\\n\",\n\t\t\tsp.server, url.HostPort, err)\n\t\treturn nil, err\n\t}\n\thasErr := false\n\tdefer func() {\n\t\tif hasErr {\n\t\t\tc.Close()\n\t\t}\n\t}()\n\n\tvar n int\n\tif n, err = c.Write(socksMsgVerMethodSelection); n != 3 || err != nil {\n\t\terrl.Printf(\"sending ver/method selection msg %v n = %v\\n\", err, n)\n\t\thasErr = true\n\t\treturn nil, err\n\t}\n\n\t// version/method selection\n\trepBuf := make([]byte, 2)\n\t_, err = io.ReadFull(c, repBuf)\n\tif err != nil {\n\t\terrl.Printf(\"read ver/method selection error %v\\n\", err)\n\t\thasErr = true\n\t\treturn nil, err\n\t}\n\tif repBuf[0] != 5 || repBuf[1] != 0 {\n\t\terrl.Printf(\"socks ver/method selection reply error ver %d method %d\",\n\t\t\trepBuf[0], repBuf[1])\n\t\thasErr = true\n\t\treturn nil, err\n\t}\n\t// debug.Println(\"Socks version selection done\")\n\n\t// send connect request\n\thost := url.Host\n\tport, err := strconv.Atoi(url.Port)\n\tif err != nil {\n\t\terrl.Printf(\"should not happen, port error %v\\n\", port)\n\t\thasErr = true\n\t\treturn nil, err\n\t}\n\n\thostLen := len(host)\n\tbufLen := 5 + hostLen + 2 // last 2 is port\n\treqBuf := make([]byte, bufLen)\n\treqBuf[0] = 5 // version 5\n\treqBuf[1] = 1 // cmd: connect\n\t// reqBuf[2] = 0 // rsv: set to 0 when initializing\n\treqBuf[3] = 3 // atyp: domain name\n\treqBuf[4] = byte(hostLen)\n\tcopy(reqBuf[5:], host)\n\tbinary.BigEndian.PutUint16(reqBuf[5+hostLen:5+hostLen+2], uint16(port))\n\n\tif n, err = c.Write(reqBuf); err != nil || n != bufLen {\n\t\terrl.Printf(\"send socks request err %v n %d\\n\", err, n)\n\t\thasErr = true\n\t\treturn nil, err\n\t}\n\n\t// I'm not clear why the buffer is fixed at 10. The rfc document does not say this.\n\t// Polipo set this to 10 and I also observed the reply is always 10.\n\treplyBuf := make([]byte, 10)\n\tif n, err = c.Read(replyBuf); err != nil {\n\t\t// Seems that socks server will close connection if it can't find host\n\t\tif err != io.EOF {\n\t\t\terrl.Printf(\"read socks reply err %v n %d\\n\", err, n)\n\t\t}\n\t\thasErr = true\n\t\treturn nil, errors.New(\"connection failed (by socks server \" + sp.server + \"). No such host?\")\n\t}\n\t// debug.Printf(\"Socks reply length %d\\n\", n)\n\n\tif replyBuf[0] != 5 {\n\t\terrl.Printf(\"socks reply connect %s VER %d not supported\\n\", url.HostPort, replyBuf[0])\n\t\thasErr = true\n\t\treturn nil, socksProtocolErr\n\t}\n\tif replyBuf[1] != 0 {\n\t\terrl.Printf(\"socks reply connect %s error %s\\n\", url.HostPort, socksError[replyBuf[1]])\n\t\thasErr = true\n\t\treturn nil, socksProtocolErr\n\t}\n\tif replyBuf[3] != 1 {\n\t\terrl.Printf(\"socks reply connect %s ATYP %d\\n\", url.HostPort, replyBuf[3])\n\t\thasErr = true\n\t\treturn nil, socksProtocolErr\n\t}\n\n\tdebug.Println(\"connected to:\", url.HostPort, \"via socks server:\", sp.server)\n\t// Now the socket can be used to pass data.\n\treturn socksConn{c, sp}, nil\n}\n"
  },
  {
    "path": "proxy.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/cyfdecyf/bufio\"\n\t\"github.com/cyfdecyf/leakybuf\"\n\tss \"github.com/shadowsocks/shadowsocks-go/shadowsocks\"\n)\n\n// As I'm using ReadSlice to read line, it's possible to get\n// bufio.ErrBufferFull while reading line, so set it to a large value to\n// prevent such problems.\n//\n// For limits about URL and HTTP header size, refer to:\n// http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url\n// \"de facto limit of 2000 characters\"\n// http://www.mnot.net/blog/2011/07/11/what_proxies_must_do\n// \"URIs should be allowed at least 8000 octets, and HTTP headers should have\n// 4000 as an absolute minimum\".\n// In practice, there are sites using cookies larger than 4096 bytes,\n// e.g. www.fitbit.com. So set http buffer size to 8192 to be safe.\nconst httpBufSize = 8192\n\n// Hold at most 4MB memory as buffer for parsing http request/response and\n// holding post data.\nvar httpBuf = leakybuf.NewLeakyBuf(512, httpBufSize)\n\n// If no keep-alive header in response, use this as the keep-alive value.\nconst defaultServerConnTimeout = 15 * time.Second\n\n// Close client connection if no new requests received in some time.\n// (On OS X, the default soft limit of open file descriptor is 256, which is\n// very conservative and easy to cause problem if we are not careful to limit\n// open fds.)\nconst clientConnTimeout = 15 * time.Second\nconst fullKeepAliveHeader = \"Keep-Alive: timeout=15\\r\\n\"\n\n// If client closed connection for HTTP CONNECT method in less then 1 second,\n// consider it as an ssl error. This is only effective for Chrome which will\n// drop connection immediately upon SSL error.\nconst sslLeastDuration = time.Second\n\n// Some code are learnt from the http package\n\n// encapulate actual error for an retry error\ntype RetryError struct {\n\terror\n}\n\nfunc isErrRetry(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\t_, ok := err.(RetryError)\n\treturn ok\n}\n\nvar zeroTime time.Time\n\ntype directConn struct {\n\tnet.Conn\n}\n\nfunc (dc directConn) String() string {\n\treturn \"direct connection\"\n}\n\ntype serverConnState byte\n\nconst (\n\tsvConnected serverConnState = iota\n\tsvSendRecvResponse\n\tsvStopped\n)\n\ntype serverConn struct {\n\tnet.Conn\n\tbufRd       *bufio.Reader\n\tbuf         []byte // buffer for the buffered reader\n\thostPort    string\n\tstate       serverConnState\n\twillCloseOn time.Time\n\tsiteInfo    *VisitCnt\n\tvisited     bool\n}\n\ntype clientConn struct {\n\tnet.Conn // connection to the proxy client\n\tbufRd    *bufio.Reader\n\tbuf      []byte // buffer for the buffered reader\n\tproxy    Proxy\n}\n\nvar (\n\terrPageSent      = errors.New(\"error page has sent\")\n\terrClientTimeout = errors.New(\"read client request timeout\")\n\terrAuthRequired  = errors.New(\"authentication requried\")\n)\n\ntype Proxy interface {\n\tServe(*sync.WaitGroup, <-chan struct{})\n\tAddr() string\n\tgenConfig() string // for upgrading config\n}\n\nvar listenProxy []Proxy\n\nfunc addListenProxy(p Proxy) {\n\tlistenProxy = append(listenProxy, p)\n}\n\ntype httpProxy struct {\n\taddr      string // listen address, contains port\n\tport      string // for use when generating PAC\n\taddrInPAC string // proxy server address to use in PAC\n}\n\nfunc newHttpProxy(addr, addrInPAC string) *httpProxy {\n\t_, port, err := net.SplitHostPort(addr)\n\tif err != nil {\n\t\tpanic(\"proxy addr\" + err.Error())\n\t}\n\treturn &httpProxy{addr, port, addrInPAC}\n}\n\nfunc (proxy *httpProxy) genConfig() string {\n\tif proxy.addrInPAC != \"\" {\n\t\treturn fmt.Sprintf(\"listen = http://%s %s\", proxy.addr, proxy.addrInPAC)\n\t} else {\n\t\treturn fmt.Sprintf(\"listen = http://%s\", proxy.addr)\n\t}\n}\n\nfunc (proxy *httpProxy) Addr() string {\n\treturn proxy.addr\n}\n\nfunc (hp *httpProxy) Serve(wg *sync.WaitGroup, quit <-chan struct{}) {\n\tdefer func() {\n\t\twg.Done()\n\t}()\n\tln, err := net.Listen(\"tcp\", hp.addr)\n\tif err != nil {\n\t\tfmt.Println(\"listen http failed:\", err)\n\t\treturn\n\t}\n\tvar exit bool\n\tgo func() {\n\t\t<-quit\n\t\texit = true\n\t\tln.Close()\n\t}()\n\thost, _, _ := net.SplitHostPort(hp.addr)\n\tvar pacURL string\n\tif host == \"\" || host == \"0.0.0.0\" {\n\t\tpacURL = fmt.Sprintf(\"http://<hostip>:%s/pac\", hp.port)\n\t} else if hp.addrInPAC == \"\" {\n\t\tpacURL = fmt.Sprintf(\"http://%s/pac\", hp.addr)\n\t} else {\n\t\tpacURL = fmt.Sprintf(\"http://%s/pac\", hp.addrInPAC)\n\t}\n\tinfo.Printf(\"COW %s listen http %s, PAC url %s\\n\", version, hp.addr, pacURL)\n\n\tfor {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil && !exit {\n\t\t\terrl.Printf(\"http proxy(%s) accept %v\\n\", ln.Addr(), err)\n\t\t\tif isErrTooManyOpenFd(err) {\n\t\t\t\tconnPool.CloseAll()\n\t\t\t}\n\t\t\ttime.Sleep(time.Millisecond)\n\t\t\tcontinue\n\t\t}\n\t\tif exit {\n\t\t\tdebug.Println(\"exiting the http listner\")\n\t\t\tbreak\n\t\t}\n\t\tc := newClientConn(conn, hp)\n\t\tgo c.serve()\n\n\t}\n}\n\ntype cowProxy struct {\n\taddr   string\n\tmethod string\n\tpasswd string\n\tcipher *ss.Cipher\n}\n\nfunc newCowProxy(method, passwd, addr string) *cowProxy {\n\tcipher, err := ss.NewCipher(method, passwd)\n\tif err != nil {\n\t\tFatal(\"can't initialize cow proxy server\", err)\n\t}\n\treturn &cowProxy{addr, method, passwd, cipher}\n}\n\nfunc (cp *cowProxy) genConfig() string {\n\tmethod := cp.method\n\tif method == \"\" {\n\t\tmethod = \"table\"\n\t}\n\treturn fmt.Sprintf(\"listen = cow://%s:%s@%s\", method, cp.passwd, cp.addr)\n}\n\nfunc (cp *cowProxy) Addr() string {\n\treturn cp.addr\n}\n\nfunc (cp *cowProxy) Serve(wg *sync.WaitGroup, quit <-chan struct{}) {\n\tdefer func() {\n\t\twg.Done()\n\t}()\n\n\tln, err := net.Listen(\"tcp\", cp.addr)\n\tif err != nil {\n\t\tfmt.Println(\"listen cow failed:\", err)\n\t\treturn\n\t}\n\tinfo.Printf(\"COW %s cow proxy address %s\\n\", version, cp.addr)\n\tvar exit bool\n\tgo func() {\n\t\t<-quit\n\t\texit = true\n\t\tln.Close()\n\t}()\n\tfor {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil && !exit {\n\t\t\terrl.Printf(\"cow proxy(%s) accept %v\\n\", ln.Addr(), err)\n\t\t\tif isErrTooManyOpenFd(err) {\n\t\t\t\tconnPool.CloseAll()\n\t\t\t}\n\t\t\ttime.Sleep(time.Millisecond)\n\t\t\tcontinue\n\t\t}\n\t\tif exit {\n\t\t\tdebug.Println(\"exiting cow listner\")\n\t\t\tbreak\n\t\t}\n\t\tssConn := ss.NewConn(conn, cp.cipher.Copy())\n\t\tc := newClientConn(ssConn, cp)\n\t\tgo c.serve()\n\t}\n}\n\nfunc newClientConn(cli net.Conn, proxy Proxy) *clientConn {\n\tbuf := httpBuf.Get()\n\tc := &clientConn{\n\t\tConn:  cli,\n\t\tbuf:   buf,\n\t\tbufRd: bufio.NewReaderFromBuf(cli, buf),\n\t\tproxy: proxy,\n\t}\n\tif debug {\n\t\tdebug.Printf(\"cli(%s) connected, total %d clients\\n\",\n\t\t\tcli.RemoteAddr(), incCliCnt())\n\t}\n\treturn c\n}\n\nfunc (c *clientConn) releaseBuf() {\n\tif c.bufRd != nil {\n\t\t// debug.Println(\"release client buffer\")\n\t\thttpBuf.Put(c.buf)\n\t\tc.buf = nil\n\t\tc.bufRd = nil\n\t}\n}\n\nfunc (c *clientConn) Close() {\n\tc.releaseBuf()\n\tif debug {\n\t\tdebug.Printf(\"cli(%s) closed, total %d clients\\n\",\n\t\t\tc.RemoteAddr(), decCliCnt())\n\t}\n\tc.Conn.Close()\n}\n\nfunc (c *clientConn) setReadTimeout(msg string) {\n\t// Always keep connections alive for cow conn from client for more reuse.\n\t// For other client connections, set read timeout so we can close the\n\t// connection after a period of idle to reduce number of open connections.\n\tif _, ok := c.Conn.(*ss.Conn); !ok {\n\t\t// make actual timeout a little longer than keep-alive value sent to client\n\t\tsetConnReadTimeout(c.Conn, clientConnTimeout+2*time.Second, msg)\n\t}\n}\n\nfunc (c *clientConn) unsetReadTimeout(msg string) {\n\tif _, ok := c.Conn.(*ss.Conn); !ok {\n\t\tunsetConnReadTimeout(c.Conn, msg)\n\t}\n}\n\n// Listen address as key, not including port part.\nvar selfListenAddr map[string]bool\n\n// Called in main, so no need to protect concurrent initialization.\nfunc initSelfListenAddr() {\n\tselfListenAddr = make(map[string]bool)\n\t// Add empty host to self listen addr, in case there's no Host header.\n\tselfListenAddr[\"\"] = true\n\tfor _, proxy := range listenProxy {\n\t\taddr := proxy.Addr()\n\t\t// Handle wildcard address.\n\t\tif addr[0] == ':' || strings.HasPrefix(addr, \"0.0.0.0\") {\n\t\t\tfor _, ad := range hostAddr() {\n\t\t\t\tselfListenAddr[ad] = true\n\t\t\t}\n\t\t\tselfListenAddr[\"localhost\"] = true\n\t\t\tcontinue\n\t\t}\n\n\t\thost, _, err := net.SplitHostPort(addr)\n\t\tif err != nil {\n\t\t\tpanic(\"listen addr invalid: \" + addr)\n\t\t}\n\t\tselfListenAddr[host] = true\n\t\tif host == \"127.0.0.1\" {\n\t\t\tselfListenAddr[\"localhost\"] = true\n\t\t} else if host == \"localhost\" {\n\t\t\tselfListenAddr[\"127.0.0.1\"] = true\n\t\t}\n\t}\n}\n\nfunc isSelfRequest(r *Request) bool {\n\tif r.URL.HostPort != \"\" {\n\t\treturn false\n\t}\n\t// Maxthon sometimes sends requests without host in request line,\n\t// in that case, get host information from Host header.\n\t// But if client PAC setting is using cow server's DNS name, we can't\n\t// decide if the request is for cow itself (need reverse lookup).\n\t// So if request path seems like getting PAC, simply return true.\n\tif r.URL.Path == \"/pac\" || strings.HasPrefix(r.URL.Path, \"/pac?\") {\n\t\treturn true\n\t}\n\tr.URL.ParseHostPort(r.Header.Host)\n\tif selfListenAddr[r.URL.Host] {\n\t\treturn true\n\t}\n\tdebug.Printf(\"fixed request with no host in request line %s\\n\", r)\n\treturn false\n}\n\nfunc (c *clientConn) serveSelfURL(r *Request) (err error) {\n\tif _, ok := c.proxy.(*httpProxy); !ok {\n\t\tgoto end\n\t}\n\tif r.Method != \"GET\" {\n\t\tgoto end\n\t}\n\tif r.URL.Path == \"/pac\" || strings.HasPrefix(r.URL.Path, \"/pac?\") {\n\t\tsendPAC(c)\n\t\t// PAC header contains connection close, send non nil error to close\n\t\t// client connection.\n\t\treturn errPageSent\n\t}\nend:\n\tsendErrorPage(c, \"404 not found\", \"Page not found\",\n\t\tgenErrMsg(r, nil, \"Serving request to COW proxy.\"))\n\terrl.Printf(\"cli(%s) page not found, serving request to cow %s\\n%s\",\n\t\tc.RemoteAddr(), r, r.Verbose())\n\treturn errPageSent\n}\n\nfunc (c *clientConn) shouldRetry(r *Request, sv *serverConn, re error) bool {\n\tif !isErrRetry(re) {\n\t\treturn false\n\t}\n\terr, _ := re.(RetryError)\n\tif !r.responseNotSent() {\n\t\tif debug {\n\t\t\tdebug.Printf(\"cli(%s) has sent some response, can't retry %v\\n\", c.RemoteAddr(), r)\n\t\t}\n\t\treturn false\n\t}\n\tif r.partial {\n\t\tif debug {\n\t\t\tdebug.Printf(\"cli(%s) partial request, can't retry %v\\n\", c.RemoteAddr(), r)\n\t\t}\n\t\tsendErrorPage(c, \"502 partial request\", err.Error(),\n\t\t\tgenErrMsg(r, sv, \"Request is too large to hold in buffer, can't retry. \"+\n\t\t\t\t\"Refresh to retry may work.\"))\n\t\treturn false\n\t} else if r.raw == nil {\n\t\tmsg := \"Please report issue to the developer: Non partial request with buffer released\"\n\t\terrl.Println(msg, r)\n\t\tpanic(msg)\n\t}\n\tif r.tooManyRetry() {\n\t\tif sv.maybeFake() {\n\t\t\t// Sometimes GFW reset will got EOF error leading to retry too many times.\n\t\t\t// In that case, consider the url as temp blocked and try parent proxy.\n\t\t\tsiteStat.TempBlocked(r.URL)\n\t\t\tr.tryCnt = 0\n\t\t\treturn true\n\t\t}\n\t\tdebug.Printf(\"cli(%s) can't retry %v tryCnt=%d\\n\", c.RemoteAddr(), r, r.tryCnt)\n\t\tsendErrorPage(c, \"502 retry failed\", \"Can't finish HTTP request\",\n\t\t\tgenErrMsg(r, sv, \"Has tried several times.\"))\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc dbgPrintRq(c *clientConn, r *Request) {\n\tif r.Trailer {\n\t\terrl.Printf(\"cli(%s) request  %s has Trailer header\\n%s\",\n\t\t\tc.RemoteAddr(), r, r.Verbose())\n\t}\n\tif dbgRq {\n\t\tif verbose {\n\t\t\tdbgRq.Printf(\"cli(%s) request  %s\\n%s\", c.RemoteAddr(), r, r.Verbose())\n\t\t} else {\n\t\t\tdbgRq.Printf(\"cli(%s) request  %s\\n\", c.RemoteAddr(), r)\n\t\t}\n\t}\n}\n\ntype SinkWriter struct{}\n\nfunc (s SinkWriter) Write(p []byte) (int, error) {\n\treturn len(p), nil\n}\n\nfunc (c *clientConn) serve() {\n\tvar r Request\n\tvar rp Response\n\tvar sv *serverConn\n\tvar err error\n\n\tvar authed bool\n\t// For cow proxy server, authentication is done by matching password.\n\tif _, ok := c.proxy.(*cowProxy); ok {\n\t\tauthed = true\n\t}\n\n\tdefer func() {\n\t\tr.releaseBuf()\n\t\tc.Close()\n\t}()\n\n\t// Refer to implementation.md for the design choices on parsing the request\n\t// and response.\n\tfor {\n\t\tif c.bufRd == nil || c.buf == nil {\n\t\t\tpanic(\"client read buffer nil\")\n\t\t}\n\n\t\tif err = parseRequest(c, &r); err != nil {\n\t\t\tdebug.Printf(\"cli(%s) parse request %v\\n\", c.RemoteAddr(), err)\n\t\t\tif err == io.EOF || isErrConnReset(err) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != errClientTimeout {\n\t\t\t\tsendErrorPage(c, \"404 Bad request\", \"Bad request\", err.Error())\n\t\t\t\treturn\n\t\t\t}\n\t\t\tsendErrorPage(c, statusRequestTimeout, statusRequestTimeout,\n\t\t\t\t\"Your browser didn't send a complete request in time.\")\n\t\t\treturn\n\t\t}\n\t\tdbgPrintRq(c, &r)\n\n\t\t// PAC may leak frequently visited sites information. But if cow\n\t\t// requires authentication for PAC, some clients may not be able\n\t\t// handle it. (e.g. Proxy SwitchySharp extension on Chrome.)\n\t\tif isSelfRequest(&r) {\n\t\t\tif err = c.serveSelfURL(&r); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif auth.required && !authed {\n\t\t\tif err = Authenticate(c, &r); err != nil {\n\t\t\t\terrl.Printf(\"cli(%s) %v\\n\", c.RemoteAddr(), err)\n\t\t\t\t// Request may have body. To make things simple, close\n\t\t\t\t// connection so we don't need to skip request body before\n\t\t\t\t// reading the next request.\n\t\t\t\treturn\n\t\t\t}\n\t\t\tauthed = true\n\t\t}\n\n\t\tif r.isConnect && !config.TunnelAllowedPort[r.URL.Port] {\n\t\t\tsendErrorPage(c, statusForbidden, \"Forbidden tunnel port\",\n\t\t\t\tgenErrMsg(&r, nil, \"Please contact proxy admin.\"))\n\t\t\treturn\n\t\t}\n\n\t\tif r.ExpectContinue {\n\t\t\tsendErrorPage(c, statusExpectFailed, \"Expect header not supported\",\n\t\t\t\t\"Please contact COW's developer if you see this.\")\n\t\t\t// Client may have sent request body at this point. Simply close\n\t\t\t// connection so we don't need to handle this case.\n\t\t\t// NOTE: sendErrorPage tells client the connection will keep alive, but\n\t\t\t// actually it will close here.\n\t\t\treturn\n\t\t}\n\n\tretry:\n\t\tr.tryOnce()\n\t\tif bool(debug) && r.isRetry() {\n\t\t\tdebug.Printf(\"cli(%s) retry request tryCnt=%d %v\\n\", c.RemoteAddr(), r.tryCnt, &r)\n\t\t}\n\t\tif sv, err = c.getServerConn(&r); err != nil {\n\t\t\tif debug {\n\t\t\t\tdebug.Printf(\"cli(%s) failed to get server conn %v\\n\", c.RemoteAddr(), &r)\n\t\t\t}\n\t\t\t// Failed connection will send error page back to the client.\n\t\t\t// For CONNECT, the client read buffer is released in copyClient2Server,\n\t\t\t// so can't go back to getRequest.\n\t\t\tif err == errPageSent && !r.isConnect {\n\t\t\t\tif r.hasBody() {\n\t\t\t\t\t// skip request body\n\t\t\t\t\tdebug.Printf(\"cli(%s) skip request body %v\\n\", c.RemoteAddr(), &r)\n\t\t\t\t\tsendBody(SinkWriter{}, c.bufRd, int(r.ContLen), r.Chunking)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif r.isConnect {\n\t\t\t// server connection will be closed in doConnect\n\t\t\terr = sv.doConnect(&r, c)\n\t\t\tif c.shouldRetry(&r, sv, err) {\n\t\t\t\tgoto retry\n\t\t\t}\n\t\t\t// debug.Printf(\"doConnect %s to %s done\\n\", c.RemoteAddr(), r.URL.HostPort)\n\t\t\treturn\n\t\t}\n\n\t\tif err = sv.doRequest(c, &r, &rp); err != nil {\n\t\t\t// For client I/O error, we can actually put server connection to\n\t\t\t// pool. But let's make thing simple for now.\n\t\t\tsv.Close()\n\t\t\tif c.shouldRetry(&r, sv, err) {\n\t\t\t\tgoto retry\n\t\t\t} else if err == errPageSent && (!r.hasBody() || r.hasSent()) {\n\t\t\t\t// Can only continue if request has no body, or request body\n\t\t\t\t// has been read.\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\t// Put server connection to pool, so other clients can use it.\n\t\t_, isCowConn := sv.Conn.(cowConn)\n\t\tif rp.ConnectionKeepAlive || isCowConn {\n\t\t\tif debug {\n\t\t\t\tdebug.Printf(\"cli(%s) connPool put %s\", c.RemoteAddr(), sv.hostPort)\n\t\t\t}\n\t\t\t// If the server connection is not going to be used soon,\n\t\t\t// release buffer before putting back to pool can save memory.\n\t\t\tsv.releaseBuf()\n\t\t\tconnPool.Put(sv)\n\t\t} else {\n\t\t\tif debug {\n\t\t\t\tdebug.Printf(\"cli(%s) server %s close conn\\n\", c.RemoteAddr(), sv.hostPort)\n\t\t\t}\n\t\t\tsv.Close()\n\t\t}\n\n\t\tif !r.ConnectionKeepAlive {\n\t\t\tif debug {\n\t\t\t\tdebug.Printf(\"cli(%s) close connection\\n\", c.RemoteAddr())\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc genErrMsg(r *Request, sv *serverConn, what string) string {\n\tif sv == nil {\n\t\treturn fmt.Sprintf(\"<p>HTTP Request <strong>%v</strong></p> <p>%s</p>\", r, what)\n\t}\n\treturn fmt.Sprintf(\"<p>HTTP Request <strong>%v</strong></p> <p>%s</p> <p>Using %s.</p>\",\n\t\tr, what, sv.Conn)\n}\n\nfunc (c *clientConn) handleBlockedRequest(r *Request, err error) error {\n\tsiteStat.TempBlocked(r.URL)\n\treturn RetryError{err}\n}\n\nfunc (c *clientConn) handleServerReadError(r *Request, sv *serverConn, err error, msg string) error {\n\tif debug {\n\t\tdebug.Printf(\"cli(%s) server read error %s %T %v %v\\n\",\n\t\t\tc.RemoteAddr(), msg, err, err, r)\n\t}\n\tif err == io.EOF {\n\t\treturn RetryError{err}\n\t}\n\tif sv.maybeFake() && maybeBlocked(err) {\n\t\treturn c.handleBlockedRequest(r, err)\n\t}\n\tif r.responseNotSent() {\n\t\tsendErrorPage(c, \"502 read error\", err.Error(), genErrMsg(r, sv, msg))\n\t\treturn errPageSent\n\t}\n\terrl.Printf(\"cli(%s) unhandled server read error %s %v %s\\n\", c.RemoteAddr(), msg, err, r)\n\treturn err\n}\n\nfunc (c *clientConn) handleServerWriteError(r *Request, sv *serverConn, err error, msg string) error {\n\t// This function is only called in doRequest, no response is sent to client.\n\t// So if visiting blocked site, can always retry request.\n\tif sv.maybeFake() && isErrConnReset(err) {\n\t\tsiteStat.TempBlocked(r.URL)\n\t}\n\treturn RetryError{err}\n}\n\nfunc dbgPrintRep(c *clientConn, r *Request, rp *Response) {\n\tif rp.Trailer {\n\t\terrl.Printf(\"cli(%s) response %s has Trailer header\\n%s\",\n\t\t\tc.RemoteAddr(), rp, rp.Verbose())\n\t}\n\tif dbgRep {\n\t\tif verbose {\n\t\t\tdbgRep.Printf(\"cli(%s) response %s %s\\n%s\",\n\t\t\t\tc.RemoteAddr(), r, rp, rp.Verbose())\n\t\t} else {\n\t\t\tdbgRep.Printf(\"cli(%s) response %s %s\\n\",\n\t\t\t\tc.RemoteAddr(), r, rp)\n\t\t}\n\t}\n}\n\nfunc (c *clientConn) readResponse(sv *serverConn, r *Request, rp *Response) (err error) {\n\tsv.initBuf()\n\tdefer func() {\n\t\trp.releaseBuf()\n\t}()\n\n\t/*\n\t\tif r.partial {\n\t\t\treturn RetryError{errors.New(\"debug retry for partial request\")}\n\t\t}\n\t*/\n\n\t/*\n\t\t// force retry for debugging\n\t\tif r.tryCnt == 1 {\n\t\t\treturn RetryError{errors.New(\"debug retry in readResponse\")}\n\t\t}\n\t*/\n\n\tif err = parseResponse(sv, r, rp); err != nil {\n\t\treturn c.handleServerReadError(r, sv, err, \"parse response\")\n\t}\n\tdbgPrintRep(c, r, rp)\n\t// After have received the first reponses from the server, we consider\n\t// ther server as real instead of fake one caused by wrong DNS reply. So\n\t// don't time out later.\n\tsv.state = svSendRecvResponse\n\tr.state = rsRecvBody\n\tr.releaseBuf()\n\n\tif _, err = c.Write(rp.rawResponse()); err != nil {\n\t\treturn err\n\t}\n\n\trp.releaseBuf()\n\n\tif rp.hasBody(r.Method) {\n\t\tif err = sendBody(c, sv.bufRd, int(rp.ContLen), rp.Chunking); err != nil {\n\t\t\tif debug {\n\t\t\t\tdebug.Printf(\"cli(%s) send body %v\\n\", c.RemoteAddr(), err)\n\t\t\t}\n\t\t\t// Non persistent connection will return nil upon successful response reading\n\t\t\tif err == io.EOF {\n\t\t\t\t// For persistent connection, EOF from server is error.\n\t\t\t\t// Response header has been read, server using persistent\n\t\t\t\t// connection indicates the end of response and proxy should\n\t\t\t\t// not got EOF while reading response.\n\t\t\t\t// The client connection will be closed to indicate this error.\n\t\t\t\t// Proxy can't send error page here because response header has\n\t\t\t\t// been sent.\n\t\t\t\treturn fmt.Errorf(\"read response body unexpected EOF %v\", rp)\n\t\t\t} else if isErrOpRead(err) {\n\t\t\t\treturn c.handleServerReadError(r, sv, err, \"read response body\")\n\t\t\t}\n\t\t\t// errl.Printf(\"cli(%s) sendBody error %T %v %v\", err, err, r)\n\t\t\treturn err\n\t\t}\n\t}\n\tr.state = rsDone\n\t/*\n\t\tif debug {\n\t\t\tdebug.Printf(\"[Finished] %v request %s %s\\n\", c.RemoteAddr(), r.Method, r.URL)\n\t\t}\n\t*/\n\tif rp.ConnectionKeepAlive {\n\t\tif rp.KeepAlive == time.Duration(0) {\n\t\t\tsv.willCloseOn = time.Now().Add(defaultServerConnTimeout)\n\t\t} else {\n\t\t\t// debug.Printf(\"cli(%s) server %s keep-alive %v\\n\", c.RemoteAddr(), sv.hostPort, rp.KeepAlive)\n\t\t\tsv.willCloseOn = time.Now().Add(rp.KeepAlive)\n\t\t}\n\t}\n\treturn\n}\n\nfunc (c *clientConn) getServerConn(r *Request) (*serverConn, error) {\n\tsiteInfo := siteStat.GetVisitCnt(r.URL)\n\t// For CONNECT method, always create new connection.\n\tif r.isConnect {\n\t\treturn c.createServerConn(r, siteInfo)\n\t}\n\tsv := connPool.Get(r.URL.HostPort, siteInfo.AsDirect())\n\tif sv != nil {\n\t\t// For websites like feedly, the site itself is not blocked, but the\n\t\t// content it loads may result reset. So we should reset server\n\t\t// connection state to just connected.\n\t\tsv.state = svConnected\n\t\tif debug {\n\t\t\tdebug.Printf(\"cli(%s) connPool get %s\\n\", c.RemoteAddr(), r.URL.HostPort)\n\t\t}\n\t\treturn sv, nil\n\t}\n\tif debug {\n\t\tdebug.Printf(\"cli(%s) connPool no conn %s\", c.RemoteAddr(), r.URL.HostPort)\n\t}\n\treturn c.createServerConn(r, siteInfo)\n}\n\nfunc connectDirect2(url *URL, siteInfo *VisitCnt, recursive bool) (net.Conn, error) {\n\tvar c net.Conn\n\tvar err error\n\tif siteInfo.AlwaysDirect() {\n\t\tc, err = net.Dial(\"tcp\", url.HostPort)\n\t} else {\n\t\tto := dialTimeout\n\t\tif siteInfo.OnceBlocked() && to >= defaultDialTimeout {\n\t\t\t// If once blocked, decrease timeout to switch to parent proxy faster.\n\t\t\tto = minDialTimeout\n\t\t} else if siteInfo.AsDirect() {\n\t\t\t// If usually can be accessed directly, increase timeout to avoid\n\t\t\t// problems when network condition is bad.\n\t\t\tto = maxTimeout\n\t\t}\n\t\tc, err = net.DialTimeout(\"tcp\", url.HostPort, to)\n\t}\n\tif err != nil {\n\t\tdebug.Printf(\"error direct connect to: %s %v\\n\", url.HostPort, err)\n\t\tif isErrTooManyOpenFd(err) && !recursive {\n\t\t\treturn connectDirect2(url, siteInfo, true)\n\t\t}\n\t\treturn nil, err\n\t}\n\t// debug.Println(\"directly connected to\", url.HostPort)\n\treturn directConn{c}, nil\n}\n\nfunc connectDirect(url *URL, siteInfo *VisitCnt) (net.Conn, error) {\n\treturn connectDirect2(url, siteInfo, false)\n}\n\nfunc isErrTimeout(err error) bool {\n\tif ne, ok := err.(net.Error); ok {\n\t\treturn ne.Timeout()\n\t}\n\treturn false\n}\n\nfunc isHttpErrCode(err error) bool {\n\tif config.HttpErrorCode <= 0 {\n\t\treturn false\n\t}\n\tif err == CustomHttpErr {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc maybeBlocked(err error) bool {\n\tif parentProxy.empty() {\n\t\treturn false\n\t}\n\treturn isErrTimeout(err) || isErrConnReset(err) || isHttpErrCode(err)\n}\n\n// Connect to requested server according to whether it's visit count.\n// If direct connection fails, try parent proxies.\nfunc (c *clientConn) connect(r *Request, siteInfo *VisitCnt) (srvconn net.Conn, err error) {\n\tvar errMsg string\n\tif config.AlwaysProxy {\n\t\tif srvconn, err = parentProxy.connect(r.URL); err == nil {\n\t\t\treturn\n\t\t}\n\t\terrMsg = genErrMsg(r, nil, \"Parent proxy connection failed, always use parent proxy.\")\n\t\tgoto fail\n\t}\n\tif siteInfo.AsBlocked() && !parentProxy.empty() {\n\t\t// In case of connection error to socks server, fallback to direct connection\n\t\tif srvconn, err = parentProxy.connect(r.URL); err == nil {\n\t\t\treturn\n\t\t}\n\t\tif siteInfo.AlwaysBlocked() {\n\t\t\terrMsg = genErrMsg(r, nil, \"Parent proxy connection failed, always blocked site.\")\n\t\t\tgoto fail\n\t\t}\n\t\tif siteInfo.AsTempBlocked() {\n\t\t\terrMsg = genErrMsg(r, nil, \"Parent proxy connection failed, temporarily blocked site.\")\n\t\t\tgoto fail\n\t\t}\n\t\tif srvconn, err = connectDirect(r.URL, siteInfo); err == nil {\n\t\t\treturn\n\t\t}\n\t\terrMsg = genErrMsg(r, nil, \"Parent proxy and direct connection failed, maybe blocked site.\")\n\t} else {\n\t\t// In case of error on direction connection, try parent server\n\t\tif srvconn, err = connectDirect(r.URL, siteInfo); err == nil {\n\t\t\treturn\n\t\t}\n\t\tif parentProxy.empty() {\n\t\t\terrMsg = genErrMsg(r, nil, \"Direct connection failed, no parent proxy.\")\n\t\t\tgoto fail\n\t\t}\n\t\tif siteInfo.AlwaysDirect() {\n\t\t\terrMsg = genErrMsg(r, nil, \"Direct connection failed, always direct site.\")\n\t\t\tgoto fail\n\t\t}\n\t\t// net.Dial does two things: DNS lookup and TCP connection.\n\t\t// GFW may cause failure here: make it time out or reset connection.\n\t\t// debug.Printf(\"type of err %T %v\\n\", err, err)\n\n\t\t// RST during TCP handshake is valid and would return as connection\n\t\t// refused error. My observation is that GFW does not use RST to stop\n\t\t// TCP handshake.\n\t\t// To simplify things and avoid error in my observation, always try\n\t\t// parent proxy in case of Dial error.\n\t\tvar socksErr error\n\t\tif srvconn, socksErr = parentProxy.connect(r.URL); socksErr == nil {\n\t\t\tc.handleBlockedRequest(r, err)\n\t\t\tif debug {\n\t\t\t\tdebug.Printf(\"cli(%s) direct connection failed, use parent proxy for %v\\n\",\n\t\t\t\t\tc.RemoteAddr(), r)\n\t\t\t}\n\t\t\treturn srvconn, nil\n\t\t}\n\t\terrMsg = genErrMsg(r, nil,\n\t\t\t\"Direct and parent proxy connection failed, maybe blocked site.\")\n\t}\n\nfail:\n\tsendErrorPage(c, \"504 Connection failed\", err.Error(), errMsg)\n\treturn nil, errPageSent\n}\n\nfunc (c *clientConn) createServerConn(r *Request, siteInfo *VisitCnt) (*serverConn, error) {\n\tsrvconn, err := c.connect(r, siteInfo)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsv := newServerConn(srvconn, r.URL.HostPort, siteInfo)\n\tif debug {\n\t\tdebug.Printf(\"cli(%s) connected to %s %d concurrent connections\\n\",\n\t\t\tc.RemoteAddr(), sv.hostPort, incSrvConnCnt(sv.hostPort))\n\t}\n\treturn sv, nil\n}\n\n// Should call initBuf before reading http response from server. This allows\n// us not init buf for connect method which does not need to parse http\n// respnose.\nfunc newServerConn(c net.Conn, hostPort string, siteInfo *VisitCnt) *serverConn {\n\tsv := &serverConn{\n\t\tConn:     c,\n\t\thostPort: hostPort,\n\t\tsiteInfo: siteInfo,\n\t}\n\treturn sv\n}\n\nfunc (sv *serverConn) isDirect() bool {\n\t_, ok := sv.Conn.(directConn)\n\treturn ok\n}\n\nfunc (sv *serverConn) updateVisit() {\n\tif sv.visited {\n\t\treturn\n\t}\n\tsv.visited = true\n\tif sv.isDirect() {\n\t\tsv.siteInfo.DirectVisit()\n\t} else {\n\t\tsv.siteInfo.BlockedVisit()\n\t}\n}\n\nfunc (sv *serverConn) initBuf() {\n\tif sv.bufRd == nil {\n\t\tsv.buf = httpBuf.Get()\n\t\tsv.bufRd = bufio.NewReaderFromBuf(sv, sv.buf)\n\t}\n}\n\nfunc (sv *serverConn) releaseBuf() {\n\tif sv.bufRd != nil {\n\t\t// debug.Println(\"release server buffer\")\n\t\thttpBuf.Put(sv.buf)\n\t\tsv.buf = nil\n\t\tsv.bufRd = nil\n\t}\n}\n\nfunc (sv *serverConn) Close() error {\n\tsv.releaseBuf()\n\tif debug {\n\t\tdebug.Printf(\"close connection to %s remains %d concurrent connections\\n\",\n\t\t\tsv.hostPort, decSrvConnCnt(sv.hostPort))\n\t}\n\treturn sv.Conn.Close()\n}\n\nfunc (sv *serverConn) maybeFake() bool {\n\treturn sv.state == svConnected && sv.isDirect() && !sv.siteInfo.AlwaysDirect()\n}\n\nfunc setConnReadTimeout(cn net.Conn, d time.Duration, msg string) {\n\tif err := cn.SetReadDeadline(time.Now().Add(d)); err != nil {\n\t\terrl.Println(\"set readtimeout:\", msg, err)\n\t}\n}\n\nfunc unsetConnReadTimeout(cn net.Conn, msg string) {\n\tif err := cn.SetReadDeadline(zeroTime); err != nil {\n\t\t// It's possible that conn has been closed, so use debug log.\n\t\tdebug.Println(\"unset readtimeout:\", msg, err)\n\t}\n}\n\nfunc (sv *serverConn) setReadTimeout(msg string) {\n\tto := readTimeout\n\tif sv.siteInfo.OnceBlocked() && to > defaultReadTimeout {\n\t\tto = minReadTimeout\n\t} else if sv.siteInfo.AsDirect() {\n\t\tto = maxTimeout\n\t}\n\tsetConnReadTimeout(sv.Conn, to, msg)\n}\n\nfunc (sv *serverConn) unsetReadTimeout(msg string) {\n\tunsetConnReadTimeout(sv.Conn, msg)\n}\n\nfunc (sv *serverConn) maybeSSLErr(cliStart time.Time) bool {\n\t// If client closes connection very soon, maybe there's SSL error, maybe\n\t// not (e.g. user stopped request).\n\t// COW can't tell which is the case, so this detection is not reliable.\n\treturn sv.state > svConnected && time.Now().Sub(cliStart) < sslLeastDuration\n}\n\nfunc (sv *serverConn) mayBeClosed() bool {\n\tif _, ok := sv.Conn.(cowConn); ok {\n\t\tdebug.Println(\"cow parent would keep alive\")\n\t\treturn false\n\t}\n\treturn time.Now().After(sv.willCloseOn)\n}\n\n// Use smaller buffer for connection method as the buffer will be hold for a\n// very long time.\nconst connectBufSize = 4096\n\n// Hold at most 2M memory for connection buffer. This can support 256\n// concurrent connect method.\nvar connectBuf = leakybuf.NewLeakyBuf(512, connectBufSize)\n\nfunc copyServer2Client(sv *serverConn, c *clientConn, r *Request) (err error) {\n\tbuf := connectBuf.Get()\n\tdefer func() {\n\t\tconnectBuf.Put(buf)\n\t}()\n\n\t/*\n\t\t// force retry for debugging\n\t\tif r.tryCnt == 1 && sv.maybeFake() {\n\t\t\ttime.Sleep(1)\n\t\t\treturn RetryError{errors.New(\"debug retry in copyServer2Client\")}\n\t\t}\n\t*/\n\n\ttotal := 0\n\tconst directThreshold = 8192\n\treadTimeoutSet := false\n\tfor {\n\t\t// debug.Println(\"srv->cli\")\n\t\tif sv.maybeFake() {\n\t\t\tsv.setReadTimeout(\"srv->cli\")\n\t\t\treadTimeoutSet = true\n\t\t} else if readTimeoutSet {\n\t\t\tsv.unsetReadTimeout(\"srv->cli\")\n\t\t\treadTimeoutSet = false\n\t\t}\n\t\tvar n int\n\t\tif n, err = sv.Read(buf); err != nil {\n\t\t\tif sv.maybeFake() && maybeBlocked(err) {\n\t\t\t\tsiteStat.TempBlocked(r.URL)\n\t\t\t\tdebug.Printf(\"srv->cli blocked site %s detected, err: %v retry\\n\", r.URL.HostPort, err)\n\t\t\t\treturn RetryError{err}\n\t\t\t}\n\t\t\t// Expected error besides EOF: \"use of closed network connection\",\n\t\t\t// this is to make blocking read return.\n\t\t\t// debug.Printf(\"copyServer2Client read data: %v\\n\", err)\n\t\t\treturn\n\t\t}\n\t\ttotal += n\n\t\tif _, err = c.Write(buf[0:n]); err != nil {\n\t\t\t// debug.Printf(\"copyServer2Client write data: %v\\n\", err)\n\t\t\treturn\n\t\t}\n\t\t// debug.Printf(\"srv(%s)->cli(%s) sent %d bytes data\\n\", r.URL.HostPort, c.RemoteAddr(), total)\n\t\t// set state to rsRecvBody to indicate the request has partial response sent to client\n\t\tr.state = rsRecvBody\n\t\tsv.state = svSendRecvResponse\n\t\tif total > directThreshold {\n\t\t\tsv.updateVisit()\n\t\t}\n\t}\n}\n\ntype serverWriter struct {\n\trq *Request\n\tsv *serverConn\n}\n\nfunc newServerWriter(r *Request, sv *serverConn) *serverWriter {\n\treturn &serverWriter{r, sv}\n}\n\n// Write to server, store written data in request buffer if necessary.\n// We have to save request body in order to retry request.\n// FIXME: too tighly coupled with Request.\nfunc (sw *serverWriter) Write(p []byte) (int, error) {\n\tif sw.rq.raw == nil {\n\t\t// buffer released\n\t} else if sw.rq.raw.Len() >= 2*httpBufSize {\n\t\t// Avoid using too much memory to hold request body. If a request is\n\t\t// not buffered completely, COW can't retry and can release memory\n\t\t// immediately.\n\t\tdebug.Println(sw.rq, \"request body too large, not buffering any more\")\n\t\tsw.rq.releaseBuf()\n\t\tsw.rq.partial = true\n\t} else if sw.rq.responseNotSent() {\n\t\tsw.rq.raw.Write(p)\n\t} else { // has sent response, happens when saving data for CONNECT method\n\t\tsw.rq.releaseBuf()\n\t}\n\treturn sw.sv.Write(p)\n}\n\nfunc copyClient2Server(c *clientConn, sv *serverConn, r *Request, srvStopped notification, done chan struct{}) (err error) {\n\t// sv.maybeFake may change during execution in this function.\n\t// So need a variable to record the whether timeout is set.\n\tdeadlineIsSet := false\n\tdefer func() {\n\t\tif deadlineIsSet {\n\t\t\t// May need to retry, unset timeout here to avoid read client\n\t\t\t// timeout on retry. Note c.Conn maybe closed when calling this.\n\t\t\tunsetConnReadTimeout(c.Conn, \"cli->srv after err\")\n\t\t}\n\t\tclose(done)\n\t}()\n\n\tvar n int\n\n\tif r.isRetry() {\n\t\tif debug {\n\t\t\tdebug.Printf(\"cli(%s)->srv(%s) retry request %d bytes of buffered body\\n\",\n\t\t\t\tc.RemoteAddr(), r.URL.HostPort, len(r.rawBody()))\n\t\t}\n\t\tif _, err = sv.Write(r.rawBody()); err != nil {\n\t\t\tdebug.Println(\"cli->srv send to server error\")\n\t\t\treturn\n\t\t}\n\t}\n\n\tw := newServerWriter(r, sv)\n\tif c.bufRd != nil {\n\t\tn = c.bufRd.Buffered()\n\t\tif n > 0 {\n\t\t\tbuffered, _ := c.bufRd.Peek(n) // should not return error\n\t\t\tif _, err = w.Write(buffered); err != nil {\n\t\t\t\t// debug.Printf(\"cli->srv write buffered err: %v\\n\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tif debug {\n\t\t\tdebug.Printf(\"cli(%s)->srv(%s) released read buffer\\n\",\n\t\t\t\tc.RemoteAddr(), r.URL.HostPort)\n\t\t}\n\t\tc.releaseBuf()\n\t}\n\n\tvar start time.Time\n\tif config.DetectSSLErr {\n\t\tstart = time.Now()\n\t}\n\tbuf := connectBuf.Get()\n\tdefer func() {\n\t\tconnectBuf.Put(buf)\n\t}()\n\tfor {\n\t\t// debug.Println(\"cli->srv\")\n\t\tif sv.maybeFake() {\n\t\t\tsetConnReadTimeout(c.Conn, time.Second, \"cli->srv\")\n\t\t\tdeadlineIsSet = true\n\t\t} else if deadlineIsSet {\n\t\t\t// maybeFake may trun to false after timeout, but timeout should be unset\n\t\t\tunsetConnReadTimeout(c.Conn, \"cli->srv before read\")\n\t\t\tdeadlineIsSet = false\n\t\t}\n\t\tif n, err = c.Read(buf); err != nil {\n\t\t\tif config.DetectSSLErr && sv.maybeFake() && (isErrConnReset(err) || err == io.EOF) &&\n\t\t\t\tsv.maybeSSLErr(start) {\n\t\t\t\tdebug.Println(\"client connection closed very soon, taken as SSL error:\", r)\n\t\t\t\tsiteStat.TempBlocked(r.URL)\n\t\t\t} else if isErrTimeout(err) && !srvStopped.hasNotified() {\n\t\t\t\t// debug.Printf(\"cli(%s)->srv(%s) timeout\\n\", c.RemoteAddr(), r.URL.HostPort)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// debug.Printf(\"cli->srv read err: %v\\n\", err)\n\t\t\treturn\n\t\t}\n\n\t\t// copyServer2Client will detect write to closed server. Just store client content for retry.\n\t\tif _, err = w.Write(buf[:n]); err != nil {\n\t\t\t// XXX is it enough to only do block detection in copyServer2Client?\n\t\t\t/*\n\t\t\t\tif sv.maybeFake() && isErrConnReset(err) {\n\t\t\t\t\tsiteStat.TempBlocked(r.URL)\n\t\t\t\t\terrl.Printf(\"copyClient2Server blocked site %d detected, retry\\n\", r.URL.HostPort)\n\t\t\t\t\treturn RetryError{err}\n\t\t\t\t}\n\t\t\t*/\n\t\t\t// debug.Printf(\"cli->srv write err: %v\\n\", err)\n\t\t\treturn\n\t\t}\n\t\t// debug.Printf(\"cli(%s)->srv(%s) sent %d bytes data\\n\", c.RemoteAddr(), r.URL.HostPort, n)\n\t}\n}\n\nvar connEstablished = []byte(\"HTTP/1.1 200 Tunnel established\\r\\n\\r\\n\")\n\n// Do HTTP CONNECT\nfunc (sv *serverConn) doConnect(r *Request, c *clientConn) (err error) {\n\tr.state = rsCreated\n\n\t_, isHttpConn := sv.Conn.(httpConn)\n\t_, isCowConn := sv.Conn.(cowConn)\n\tif isHttpConn || isCowConn {\n\t\tif debug {\n\t\t\tdebug.Printf(\"cli(%s) send CONNECT request to parent\\n\", c.RemoteAddr())\n\t\t}\n\t\tif err = sv.sendHTTPProxyRequestHeader(r, c); err != nil {\n\t\t\tdebug.Printf(\"cli(%s) error send CONNECT request to parent: %v\\n\",\n\t\t\t\tc.RemoteAddr(), err)\n\t\t\treturn err\n\t\t}\n\t} else if !r.isRetry() {\n\t\t// debug.Printf(\"send connection confirmation to %s->%s\\n\", c.RemoteAddr(), r.URL.HostPort)\n\t\tif _, err = c.Write(connEstablished); err != nil {\n\t\t\tdebug.Printf(\"cli(%s) error send 200 Connecion established: %v\\n\",\n\t\t\t\tc.RemoteAddr(), err)\n\t\t\treturn err\n\t\t}\n\t}\n\n\tvar cli2srvErr error\n\tdone := make(chan struct{})\n\tsrvStopped := newNotification()\n\tgo func() {\n\t\t// debug.Printf(\"doConnect: cli(%s)->srv(%s)\\n\", c.RemoteAddr(), r.URL.HostPort)\n\t\tcli2srvErr = copyClient2Server(c, sv, r, srvStopped, done)\n\t\t// Close sv to force read from server in copyServer2Client return.\n\t\t// Note: there's no other code closing the server connection for CONNECT.\n\t\tsv.Close()\n\t}()\n\n\t// debug.Printf(\"doConnect: srv(%s)->cli(%s)\\n\", r.URL.HostPort, c.RemoteAddr())\n\terr = copyServer2Client(sv, c, r)\n\tif isErrRetry(err) {\n\t\tsrvStopped.notify()\n\t\t<-done\n\t\t// debug.Printf(\"doConnect: cli(%s)->srv(%s) stopped\\n\", c.RemoteAddr(), r.URL.HostPort)\n\t} else {\n\t\t// close client connection to force read from client in copyClient2Server return\n\t\tc.Conn.Close()\n\t}\n\tif isErrRetry(cli2srvErr) {\n\t\treturn cli2srvErr\n\t}\n\treturn\n}\n\nfunc (sv *serverConn) sendHTTPProxyRequestHeader(r *Request, c *clientConn) (err error) {\n\tif _, err = sv.Write(r.proxyRequestLine()); err != nil {\n\t\treturn c.handleServerWriteError(r, sv, err,\n\t\t\t\"send proxy request line to http parent\")\n\t}\n\tif hc, ok := sv.Conn.(httpConn); ok && hc.parent.authHeader != nil {\n\t\t// Add authorization header for parent http proxy\n\t\tif _, err = sv.Write(hc.parent.authHeader); err != nil {\n\t\t\treturn c.handleServerWriteError(r, sv, err,\n\t\t\t\t\"send proxy authorization header to http parent\")\n\t\t}\n\t}\n\t// When retry, body is in raw buffer.\n\tif _, err = sv.Write(r.rawHeaderBody()); err != nil {\n\t\treturn c.handleServerWriteError(r, sv, err,\n\t\t\t\"send proxy request header to http parent\")\n\t}\n\t/*\n\t\tif bool(dbgRq) && verbose {\n\t\t\tdebug.Printf(\"request to http proxy:\\n%s%s\", r.proxyRequestLine(), r.rawHeaderBody())\n\t\t}\n\t*/\n\treturn\n}\n\nfunc (sv *serverConn) sendRequestHeader(r *Request, c *clientConn) (err error) {\n\t// Send request to the server\n\tswitch sv.Conn.(type) {\n\tcase httpConn, cowConn:\n\t\treturn sv.sendHTTPProxyRequestHeader(r, c)\n\t}\n\t/*\n\t\tif bool(debug) && verbose {\n\t\t\tdebug.Printf(\"request to server\\n%s\", r.rawRequest())\n\t\t}\n\t*/\n\tif _, err = sv.Write(r.rawRequest()); err != nil {\n\t\terr = c.handleServerWriteError(r, sv, err, \"send request to server\")\n\t}\n\treturn\n}\n\nfunc (sv *serverConn) sendRequestBody(r *Request, c *clientConn) (err error) {\n\t// Send request body. If this is retry, r.raw contains request body and is\n\t// sent while sending raw request.\n\tif !r.hasBody() || r.isRetry() {\n\t\treturn\n\t}\n\n\terr = sendBody(newServerWriter(r, sv), c.bufRd, int(r.ContLen), r.Chunking)\n\tif err != nil {\n\t\terrl.Printf(\"cli(%s) send request body error %v %s\\n\", c.RemoteAddr(), err, r)\n\t\tif isErrOpWrite(err) {\n\t\t\terr = c.handleServerWriteError(r, sv, err, \"send request body\")\n\t\t}\n\t\treturn\n\t}\n\tif debug {\n\t\tdebug.Printf(\"cli(%s) request body sent %s\\n\", c.RemoteAddr(), r)\n\t}\n\treturn\n}\n\n// Do HTTP request other that CONNECT\nfunc (sv *serverConn) doRequest(c *clientConn, r *Request, rp *Response) (err error) {\n\tr.state = rsCreated\n\tif err = sv.sendRequestHeader(r, c); err != nil {\n\t\treturn\n\t}\n\tif err = sv.sendRequestBody(r, c); err != nil {\n\t\treturn\n\t}\n\tr.state = rsSent\n\tif err = c.readResponse(sv, r, rp); err == nil {\n\t\tsv.updateVisit()\n\t}\n\treturn err\n}\n\n// Send response body if header specifies content length\nfunc sendBodyWithContLen(w io.Writer, r *bufio.Reader, contLen int) (err error) {\n\t// debug.Println(\"Sending body with content length\", contLen)\n\tif contLen == 0 {\n\t\treturn\n\t}\n\tif err = copyN(w, r, contLen, httpBufSize); err != nil {\n\t\tdebug.Println(\"sendBodyWithContLen error:\", err)\n\t}\n\treturn\n}\n\n// Use this function until we find Trailer headers actually in use.\nfunc skipTrailer(r *bufio.Reader) error {\n\t// It's possible to get trailer headers, but the body will always end with\n\t// a line with just CRLF.\n\tfor {\n\t\ts, err := r.ReadSlice('\\n')\n\t\tif err != nil {\n\t\t\terrl.Println(\"skip trailer:\", err)\n\t\t\treturn err\n\t\t}\n\t\tif len(s) == 2 && s[0] == '\\r' && s[1] == '\\n' {\n\t\t\treturn nil\n\t\t}\n\t\terrl.Printf(\"skip trailer: %#v\", string(s))\n\t\tif len(s) == 1 || len(s) == 2 {\n\t\t\treturn fmt.Errorf(\"malformed chunk body end: %#v\", string(s))\n\t\t}\n\t}\n}\n\nfunc skipCRLF(r *bufio.Reader) (err error) {\n\tvar buf [2]byte\n\tif _, err = io.ReadFull(r, buf[:]); err != nil {\n\t\terrl.Println(\"skip chunk body end:\", err)\n\t\treturn\n\t}\n\tif buf[0] != '\\r' || buf[1] != '\\n' {\n\t\treturn fmt.Errorf(\"malformed chunk body end: %#v\", string(buf[:]))\n\t}\n\treturn\n}\n\n// Send response body if header specifies chunked encoding. rdSize specifies\n// the size of each read on Reader, it should be set to be the buffer size of\n// the Reader, this parameter is added for testing.\nfunc sendBodyChunked(w io.Writer, r *bufio.Reader, rdSize int) (err error) {\n\t// debug.Println(\"Sending chunked body\")\n\tfor {\n\t\tvar s []byte\n\t\t// Read chunk size line, ignore chunk extension if any.\n\t\tif s, err = r.PeekSlice('\\n'); err != nil {\n\t\t\terrl.Println(\"peek chunk size:\", err)\n\t\t\treturn\n\t\t}\n\t\tsmid := bytes.IndexByte(s, ';')\n\t\tif smid == -1 {\n\t\t\tsmid = len(s)\n\t\t} else {\n\t\t\t// use error log to find usage of chunk extension\n\t\t\terrl.Printf(\"got chunk extension: %s\\n\", s)\n\t\t}\n\t\tvar size int64\n\t\tif size, err = ParseIntFromBytes(TrimSpace(s[:smid]), 16); err != nil {\n\t\t\terrl.Println(\"chunk size invalid:\", err)\n\t\t\treturn\n\t\t}\n\t\t/*\n\t\t\tif debug {\n\t\t\t\t// To debug getting malformed response status line with \"0\\r\\n\".\n\t\t\t\tif c, ok := w.(*clientConn); ok {\n\t\t\t\t\tdebug.Printf(\"cli(%s) chunk size %d %#v\\n\", c.RemoteAddr(), size, string(s))\n\t\t\t\t}\n\t\t\t}\n\t\t*/\n\t\tif size == 0 {\n\t\t\tr.Skip(len(s))\n\t\t\tif err = skipCRLF(r); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif _, err = w.Write([]byte(chunkEnd)); err != nil {\n\t\t\t\tdebug.Println(\"send chunk ending:\", err)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\t// RFC 2616 19.3 only suggest tolerating single LF for\n\t\t// headers, not for chunked encoding. So assume the server will send\n\t\t// CRLF. If not, the following parse int may find errors.\n\t\ttotal := len(s) + int(size) + 2 // total data size for this chunk, including ending CRLF\n\t\t// PeekSlice will not advance reader, so we can just copy total sized data.\n\t\tif err = copyN(w, r, total, rdSize); err != nil {\n\t\t\tdebug.Println(\"copy chunked data:\", err)\n\t\t\treturn\n\t\t}\n\t}\n}\n\nconst chunkEnd = \"0\\r\\n\\r\\n\"\n\nfunc sendBodySplitIntoChunk(w io.Writer, r *bufio.Reader) (err error) {\n\t// debug.Printf(\"sendBodySplitIntoChunk called\\n\")\n\tvar b []byte\n\tfor {\n\t\tb, err = r.ReadNext()\n\t\t// debug.Println(\"split into chunk n =\", n, \"err =\", err)\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\t// EOF is expected here as the server is closing connection.\n\t\t\t\t// debug.Println(\"end chunked encoding\")\n\t\t\t\t_, err = w.Write([]byte(chunkEnd))\n\t\t\t\tif err != nil {\n\t\t\t\t\tdebug.Println(\"write chunk end 0\", err)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdebug.Println(\"read error in sendBodySplitIntoChunk\", err)\n\t\t\treturn\n\t\t}\n\n\t\tchunkSize := []byte(fmt.Sprintf(\"%x\\r\\n\", len(b)))\n\t\tif _, err = w.Write(chunkSize); err != nil {\n\t\t\tdebug.Printf(\"write chunk size %v\\n\", err)\n\t\t\treturn\n\t\t}\n\t\tif _, err = w.Write(b); err != nil {\n\t\t\tdebug.Println(\"write chunk data:\", err)\n\t\t\treturn\n\t\t}\n\t\tif _, err = w.Write([]byte(CRLF)); err != nil {\n\t\t\tdebug.Println(\"write chunk ending CRLF:\", err)\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// Send message body.\nfunc sendBody(w io.Writer, bufRd *bufio.Reader, contLen int, chunk bool) (err error) {\n\t// chunked encoding has precedence over content length\n\t// COW does not sanitize response header, but can correctly handle it\n\tif chunk {\n\t\terr = sendBodyChunked(w, bufRd, httpBufSize)\n\t} else if contLen >= 0 {\n\t\t// It's possible to have content length 0 if server response has no\n\t\t// body.\n\t\terr = sendBodyWithContLen(w, bufRd, int(contLen))\n\t} else {\n\t\t// Must be reading server response here, because sendBody is called in\n\t\t// reading response iff chunked or content length > 0.\n\t\terr = sendBodySplitIntoChunk(w, bufRd)\n\t}\n\treturn\n}\n"
  },
  {
    "path": "proxy_test.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"github.com/cyfdecyf/bufio\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestSendBodyChunked(t *testing.T) {\n\ttestData := []struct {\n\t\traw  string\n\t\twant string // empty means same as raw\n\t}{\n\t\t{\"1a; ignore-stuff-here\\r\\nabcdefghijklmnopqrstuvwxyz\\r\\n10\\r\\n1234567890abcdef\\r\\n0\\r\\n\\r\\n\", \"\"},\n\t\t{\"0\\r\\n\\r\\n\", \"\"},\n\t\t/*\n\t\t\t{\"0\\n\\r\\n\", \"0\\r\\n\\r\\n\"}, // test for buggy web servers\n\t\t\t{\"1a; ignore-stuff-here\\nabcdefghijklmnopqrstuvwxyz\\r\\n10\\n1234567890abcdef\\n0\\n\\n\",\n\t\t\t\t// COW will only sanitize CRLF at chunk ending\n\t\t\t\t\"1a; ignore-stuff-here\\nabcdefghijklmnopqrstuvwxyz\\r\\n10\\n1234567890abcdef\\r\\n0\\r\\n\\r\\n\"},\n\t\t*/\n\t}\n\n\t// supress error log when finding chunk extension\n\terrl = false\n\tdefer func() {\n\t\terrl = true\n\t}()\n\t// use different reader buffer size to test for both all buffered and partially buffered chunk\n\tsizeArr := []int{32, 64, 128}\n\tfor _, size := range sizeArr {\n\t\tfor _, td := range testData {\n\t\t\tr := bufio.NewReaderSize(strings.NewReader(td.raw), size)\n\t\t\tw := new(bytes.Buffer)\n\n\t\t\tif err := sendBodyChunked(w, r, size); err != nil {\n\t\t\t\tt.Fatalf(\"sent data %q err: %v\\n\", w.Bytes(), err)\n\t\t\t}\n\t\t\tif td.want == \"\" {\n\t\t\t\tif w.String() != td.raw {\n\t\t\t\t\tt.Errorf(\"sendBodyChunked wrong with buf size %d, raw data is:\\n%q\\ngot:\\n%q\\n\",\n\t\t\t\t\t\tsize, td.raw, w.String())\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif w.String() != td.want {\n\t\t\t\t\tt.Errorf(\"sendBodyChunked wrong with buf sizwe %d, raw data is:\\n%q\\nwant:\\n%q\\ngot :\\n%q\\n\",\n\t\t\t\t\t\tsize, td.raw, td.want, w.String())\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestInitSelfListenAddr(t *testing.T) {\n\tlistenProxy = []Proxy{newHttpProxy(\"0.0.0.0:7777\", \"\")}\n\tinitSelfListenAddr()\n\n\ttestData := []struct {\n\t\tr    Request\n\t\tself bool\n\t}{\n\t\t{Request{Header: Header{Host: \"google.com:443\"}, URL: &URL{}}, false},\n\t\t{Request{Header: Header{Host: \"localhost\"}, URL: &URL{}}, true},\n\t\t{Request{Header: Header{Host: \"127.0.0.1:7777\"}, URL: &URL{}}, true},\n\t\t{Request{Header: Header{Host: \"\"}, URL: &URL{HostPort: \"google.com\"}}, false},\n\t\t{Request{Header: Header{Host: \"localhost\"}, URL: &URL{HostPort: \"google.com\"}}, false},\n\t}\n\n\tfor _, td := range testData {\n\t\tif isSelfRequest(&td.r) != td.self {\n\t\t\tt.Error(td.r.Host, \"isSelfRequest should be\", td.self)\n\t\t}\n\t\tif td.self && td.r.URL.Host == \"\" {\n\t\t\tt.Error(\"isSelfRequest should set url host\", td.r.Header.Host)\n\t\t}\n\t}\n\n\t// Another set of listen addr.\n\tlistenProxy = []Proxy{\n\t\tnewHttpProxy(\"192.168.1.1:7777\", \"\"),\n\t\tnewHttpProxy(\"127.0.0.1:8888\", \"\"),\n\t}\n\tinitSelfListenAddr()\n\n\ttestData2 := []struct {\n\t\tr    Request\n\t\tself bool\n\t}{\n\t\t{Request{Header: Header{Host: \"google.com:443\"}, URL: &URL{}}, false},\n\t\t{Request{Header: Header{Host: \"localhost\"}, URL: &URL{}}, true},\n\t\t{Request{Header: Header{Host: \"127.0.0.1:8888\"}, URL: &URL{}}, true},\n\t\t{Request{Header: Header{Host: \"192.168.1.1\"}, URL: &URL{}}, true},\n\t\t{Request{Header: Header{Host: \"192.168.1.2\"}, URL: &URL{}}, false},\n\t\t{Request{Header: Header{Host: \"\"}, URL: &URL{HostPort: \"google.com\"}}, false},\n\t\t{Request{Header: Header{Host: \"localhost\"}, URL: &URL{HostPort: \"google.com\"}}, false},\n\t}\n\n\tfor _, td := range testData2 {\n\t\tif isSelfRequest(&td.r) != td.self {\n\t\t\tt.Error(td.r.Host, \"isSelfRequest should be\", td.self)\n\t\t}\n\t\tif td.self && td.r.URL.Host == \"\" {\n\t\t\tt.Error(\"isSelfRequest should set url host\", td.r.Header.Host)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "proxy_unix.go",
    "content": "// +build darwin freebsd linux netbsd openbsd\n\npackage main\n\nimport (\n\t\"net\"\n\t\"syscall\"\n\t\"strings\"\n)\n\nfunc isErrConnReset(err error) bool {\n\tif ne, ok := err.(*net.OpError); ok {\n\t\treturn strings.Contains(ne.Err.Error(), syscall.ECONNRESET.Error())\n\t}\n\treturn false\n}\n\nfunc isDNSError(err error) bool {\n\tif _, ok := err.(*net.DNSError); ok {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc isErrOpWrite(err error) bool {\n\tne, ok := err.(*net.OpError)\n\tif !ok {\n\t\treturn false\n\t}\n\treturn ne.Op == \"write\"\n}\n\nfunc isErrOpRead(err error) bool {\n\tne, ok := err.(*net.OpError)\n\tif !ok {\n\t\treturn false\n\t}\n\treturn ne.Op == \"read\"\n}\n\nfunc isErrTooManyOpenFd(err error) bool {\n\tif ne, ok := err.(*net.OpError); ok && (ne.Err == syscall.EMFILE || ne.Err == syscall.ENFILE) {\n\t\terrl.Println(\"too many open fd\")\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "proxy_windows.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"strings\"\n\t\"syscall\"\n)\n\nvar _ = fmt.Println\n\nfunc isErrConnReset(err error) bool {\n\t// fmt.Printf(\"calling isErrConnReset for err type: %v Error() %s\\n\",\n\t// reflect.TypeOf(err), err.Error())\n\tif ne, ok := err.(*net.OpError); ok {\n\t\t// fmt.Println(\"isErrConnReset net.OpError.Err type:\", reflect.TypeOf(ne))\n\t\tif errno, enok := ne.Err.(syscall.Errno); enok {\n\t\t\t// I got these number by print. Only tested on XP.\n\t\t\t// fmt.Printf(\"isErrConnReset errno: %d\\n\", errno)\n\t\t\treturn errno == 64 || errno == 10054\n\t\t}\n\t}\n\treturn false\n}\n\nfunc isDNSError(err error) bool {\n\t/*\n\t\tfmt.Printf(\"calling isDNSError for err type: %v %s\\n\",\n\t\t\treflect.TypeOf(err), err.Error())\n\t*/\n\t// DNS error are not of type DNSError on Windows, so I used this ugly\n\t// hack.\n\terrMsg := err.Error()\n\treturn strings.Contains(errMsg, \"No such host\") ||\n\t\tstrings.Contains(errMsg, \"GetAddrInfoW\") ||\n\t\tstrings.Contains(errMsg, \"dial tcp\")\n}\n\nfunc isErrOpWrite(err error) bool {\n\tne, ok := err.(*net.OpError)\n\tif !ok {\n\t\treturn false\n\t}\n\treturn ne.Op == \"WSASend\"\n}\n\nfunc isErrOpRead(err error) bool {\n\tne, ok := err.(*net.OpError)\n\tif !ok {\n\t\treturn false\n\t}\n\treturn ne.Op == \"WSARecv\"\n}\n\nfunc isErrTooManyOpenFd(err error) bool {\n\t// TODO implement this.\n\treturn false\n}\n"
  },
  {
    "path": "script/README.md",
    "content": "# About cow-taskbar.exe\n\nCopied `goagent.exe`, modified the string table and icon using reshack.\n\nThanks for the taskbar project created by @phuslu.\n\n# About cow-hide.exe\n\nAllow you to run COW as a background process, without any notifications. Provided by @xupefei's [cow-hide](https://github.com/xupefei/cow-hide) project.\n\nIcon from [IconArchive](http://www.iconarchive.com/show/animal-icons-by-martin-berube/cow-icon.html), thanks to the author Martin Berube.\n\n"
  },
  {
    "path": "script/build.sh",
    "content": "#!/bin/bash\n\ncd \"$( dirname \"${BASH_SOURCE[0]}\" )/..\"\n\nversion=`grep '^version=' ./install-cow.sh | sed -s 's/version=//'`\necho \"creating cow binary version $version\"\n\nmkdir -p bin\nbuild() {\n    local name\n    local goos\n    local goarch\n    local goarm\n    local cgo\n    local armv\n\n    goos=\"GOOS=$1\"\n    goarch=\"GOARCH=$2\"\n    arch=$3\n    if [[ $2 == \"arm\" ]]; then\n        armv=`echo $arch | grep -o [0-9]`\n        goarm=\"GOARM=$armv\"\n    fi\n\n    if [[ $1 == \"darwin\" ]]; then\n        # Enable CGO for OS X so change network location will not cause problem.\n        cgo=\"CGO_ENABLED=1\"\n    else\n        cgo=\"CGO_ENABLED=0\"\n    fi\n\n    name=cow-$arch-$version\n    echo \"building $name\"\n    echo $cgo $goos $goarch $goarm go build\n    eval $cgo $goos $goarch $goarm go build || exit 1\n    if [[ $1 == \"windows\" ]]; then\n        mv cow.exe script\n        pushd script\n        sed -e 's/$/\\r/' ../doc/sample-config/rc > rc.txt\n        zip $name.zip cow.exe cow-taskbar.exe cow-hide.exe rc.txt\n        rm -f cow.exe rc.txt\n        mv $name.zip ../bin/\n        popd\n    else\n        mv cow bin/$name\n        gzip -f bin/$name\n    fi\n}\n\nbuild darwin amd64 mac64\n#build darwin 386 mac32\nbuild linux amd64 linux64\nbuild linux 386 linux32\nbuild linux arm linux-armv5tel\nbuild linux arm linux-armv6l\nbuild linux arm linux-armv7l\nbuild windows amd64 win64\nbuild windows 386 win32\n"
  },
  {
    "path": "script/debugrc",
    "content": "listen = cow://aes-128-cfb:foobar@127.0.0.1:8899\n"
  },
  {
    "path": "script/httprc",
    "content": "listen = http://127.0.0.1:7788\nproxy = cow://aes-128-cfb:foobar@127.0.0.1:8899\nalwaysProxy = true\n"
  },
  {
    "path": "script/log-group-by-client.sh",
    "content": "#!/bin/bash\n\nif [[ $# != 1 ]]; then\n    echo \"Usage: $0 <log file>\"\n    exit 1\nfi\n\nlog=$1\n\n#clients=`egrep 'cli\\([^)]+\\) connected, total' $log | cut -d ' ' -f 4`\n\n#for c in $clients; do\n    #echo $c\n#done\n\nsort --stable --key 4,4 --key 3,3 $log | sed -e \"/closed, total/s,\\$,\\n\\n,\" > $log-grouped\n\n"
  },
  {
    "path": "script/set-version.sh",
    "content": "#!/bin/bash\n\ncd \"$( dirname \"${BASH_SOURCE[0]}\" )/..\"\n\nif [ $# != 1 ]; then\n    echo \"Usage: $0 <version>\"\n    exit 1\nfi\n\nversion=$1\n#echo $version\n\nsed -i -e \"s,\\(\\tversion \\+= \\)\\\".*\\\"$,\\1\\\"$version\\\",\" config.go\nsed -i -e \"s/version=.*$/version=$version/\" install-cow.sh\nsed -i -e \"s/当前版本：[^ ]\\+ \\(.*\\)\\$/当前版本：$version \\1/\" README.md\nsed -i -e \"s/Current version: [^ ]\\+ \\(.*\\)\\$/Current version: $version \\1/\" README-en.md\n"
  },
  {
    "path": "script/test.sh",
    "content": "#!/bin/bash\n\ncd \"$( dirname \"${BASH_SOURCE[0]}\" )/..\"\n\nif ! go build; then\n    echo \"build failed\"\n    exit 1\nfi\n\nPROXY_ADDR=127.0.0.1:7788\nCOW_ADDR=127.0.0.1:8899\n\nif [[ -z \"$TRAVIS\" ]]; then\n    RCDIR=~/.cow/\nelse # on travis\n    RCDIR=./script/\nfi\n\n./cow -rc $RCDIR/debugrc -listen=cow://aes-128-cfb:foobar@$COW_ADDR &\nparent_pid=$!\n./cow -rc ./script/httprc -listen=http://$PROXY_ADDR &\ncow_pid=$!\n\nstop_cow() {\n    kill -SIGTERM $parent_pid\n    kill -SIGTERM $cow_pid\n}\ntrap 'stop_cow' TERM INT\n\nsleep 1\n\ntest_get() {\n    local url\n    url=$1\n    target=$2\n    noproxy=$3\n    code=$4\n\n    echo -n \"GET $url \"\n    if [[ -z $code ]]; then\n        code=\"200\"\n    fi\n\n    # get 5 times\n    for i in {1..2}; do\n        # -s silent to disable progress meter, but enable --show-error\n        # -i to include http header\n        # -L to follow redirect so we should always get HTTP 200\n        if [[ -n $noproxy ]]; then\n            cont=`curl -s --show-error -i -L $url 2>&1`\n        else\n            cont=`curl -s --show-error -i -L -x $PROXY_ADDR $url 2>&1`\n        fi\n        ok=`echo $cont | grep -E -o \"HTTP/1\\.1 +$code\"`\n        html=`echo $cont | grep -E -o -i \"$target\"`\n        if [[ -z $ok || -z $html ]] ; then\n            echo \"==============================\"\n            echo \"GET $url FAILED!!!\"\n            echo \"$ok\"\n            echo \"$html\"\n            echo $cont\n            echo \"==============================\"\n            kill -SIGTERM $cow_pid\n            exit 1\n        fi\n        sleep 0.3\n    done\n    echo \"passed\"\n}\n\ntest_get $PROXY_ADDR/pac \"apple.com\" \"noproxy\" # test for pac\ntest_get google.com \"<html\" # 301 redirect\ntest_get www.google.com \"<html\" # 302 redirect , chunked encoding\ntest_get www.reddit.com \"<html\" # chunked encoding\ntest_get openvpn.net \"</html>\" # blocked site, all kinds of block method\ntest_get https://google.com \"<html\" # test for HTTP connect\ntest_get https://www.google.com \"<html\"\ntest_get https://www.twitter.com \"</html>\"\n\n# Sites that may timeout on travis.\nif [[ -z $TRAVIS ]]; then\n    test_get plan9.bell-labs.com/magic/man2html/1/2l \"<head>\" \"\" \"404\" # single LF in response header\n    test_get www.wpxap.com \"<html\" # HTTP 1.0 server\n    test_get youku.com \"<html\" # 302 redirect\n    test_get douban.com \"</html>\" # 301 redirect\n    test_get www.taobao.com \"<html>\" # chunked encoding, weird can't tests for </html> in script\n    test_get https://www.alipay.com \"<html>\"\nfi\n\nstop_cow\nsleep 0.5\nrm -f ./script/stat*\nexit 0\n"
  },
  {
    "path": "script/upload.sh",
    "content": "#!/bin/bash\n\ncd \"$( dirname \"${BASH_SOURCE[0]}\" )/..\"\n\nif [[ $# != 2 ]]; then\n    echo \"upload.sh <username> <passwd>\"\n    exit 1\nfi\n\nversion=`grep '^version=' ./install-cow.sh | sed -s 's/version=//'`\nusername=$1\npasswd=$2\n\nupload() {\n    summary=$1\n    file=$2\n    googlecode_upload.py -l Featured -u \"$username\" -w \"$passwd\" -s \"$summary\" -p cow-proxy \"$file\"\n}\n\nupload \"$version for Linux 32bit\" bin/cow-linux32-$version.gz\nupload \"$version for Linux 64bit\" bin/cow-linux64-$version.gz\nupload \"$version for Windows 64bit\" bin/cow-win64-$version.zip\nupload \"$version for Windows 32bit\" bin/cow-win32-$version.zip\nupload \"$version for OS X 64bit\" bin/cow-mac64-$version.gz\n"
  },
  {
    "path": "site_blocked.go",
    "content": "package main\n\nvar blockedDomainList = []string{\n\t\"bit.ly\",\n\t\"j.mp\",\n\t\"bitly.com\",\n\t\"fbcdn.net\",\n\t\"facebook.com\",\n\t\"plus.google.com\",\n\t\"plusone.google.com\",\n\t\"t.co\",\n\t\"twimg.com\",\n\t\"twitpic.com\",\n\t\"twitter.com\",\n\t\"youtu.be\",\n\t\"youtube.com\",\n\t\"ytimg.com\",\n}\n"
  },
  {
    "path": "site_direct.go",
    "content": "package main\n\nvar directDomainList = []string{\n\t// 视频\n\t\"xunlei.com\", // 迅雷\n\t\"kankan.com\",\n\t\"kanimg.com\",\n\n\t\"tdimg.com\", // 土豆\n\t\"tudou.com\",\n\t\"tudouui.com\",\n\n\t\"soku.com\", // 优酷\n\t\"youku.com\",\n\t\"ykimg.com\",\n\n\t\"ku6.cn\", // 酷六\n\t\"ku6.com\",\n\t\"ku6cdn.com\",\n\t\"ku6img.com\",\n\n\t// 电商\n\t\"z.cn\",\n\t\"amazon.cn\",\n\n\t\"360buy.com\",\n\t\"360buyimg.com\",\n\t\"jd.com\",\n\n\t\"51buy.com\",\n\t\"icson.com\",\n\n\t\"dangdang.com\",\n\t\"ddimg.cn\",\n\n\t\"yihaodian.com\",\n\t\"yihaodianimg.com\",\n\n\t\"paipai.com\",\n\t\"paipaiimg.com\",\n\n\t\"tmall.com\",\n\t\"taobao.com\",\n\t\"taobaocdn.com\",\n\t\"tbcdn.cn\",\n\t\"etao.com\",\n\n\t\"aicdn.com\",\n\t\"alicdn.com\",\n\t\"alimama.cn\",\n\t\"alimama.com\",\n\t\"alipay.com\",\n\t\"alipayobjects.com\",\n\n\t// 银行\n\t\"bankcomm.com\",\n\t\"bankofchina.com\",\n\t\"95559.com.cn\",\n\t\"abchina.com\",\n\t\"95599.cn\",\n\t\"boc.cn\",\n\t\"ccb.com\",\n\t\"cmbchina.com\",\n\t\"icbc.com.cn\",\n\t\"spdb.com.cn\",\n\n\t// 社交\n\t\"douban.com\",\n\t\"t.cn\",\n\t\"weibo.com\",\n\t\"zhihu.com\",\n\t\"kaixin001.com\",\n\t\"qq.com\",\n\n\t\"renren.com\",\n\t\"rrimg.com\",\n\t\"xiaonei.com\",\n\t\"xnimg.cn\",\n\t\"xnpic.com\",\n\n\t\"dianping.com\", // 点评\n\t\"dpfile.com\",\n\n\t\"huaban.com\", // 又拍云的几个\n\t\"yupoo.com\",\n\t\"upyun.com\",\n\t\"upaiyun.com\",\n\n\t// 新闻门户\n\t\"ifanr.cn\",\n\t\"ifanr.com\",\n\t\"163.com\",\n\t\"hexun.com\",\n\t\"sina.com.cn\",\n\t\"sinaapp.com\",\n\t\"sinaimg.cn\",\n\t\"sinajs.cn\",\n\t\"sohu.com\",\n\t\"solidot.org\",\n\n\t// 搜索\n\t\"bing.com\",\n\t\"bing.com.cn\",\n\t\"baidu.com\",\n\t\"bdstatic.com\",\n\t\"bdimg.com\",\n\t\"youdao.com\",\n\t\"sogou.com\",\n\n\t// Apple\n\t\"apple.com\",\n\t\"apple.com.cn\",\n\t\"icloud.com\",\n\n\t// 其他\n\t\"macromedia.com\",\n\t\"mmcdn.cn\",\n\t\"12306.cn\",\n}\n"
  },
  {
    "path": "sitestat.go",
    "content": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"math/rand\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/cyfdecyf/bufio\"\n)\n\nfunc init() {\n\trand.Seed(time.Now().Unix())\n}\n\n// VisitCnt and SiteStat are used to track how many times a site is visited.\n// With this information: COW knows which sites are frequently visited, and\n// judging whether a site is blocked or not is more reliable.\n\nconst (\n\tdirectDelta  = 5\n\tblockedDelta = 5\n\tmaxCnt       = 100 // no protect to update visit cnt, smaller value is unlikely to overflow\n\tuserCnt      = -1  // this represents user specified host or domain\n)\n\ntype siteVisitMethod int\n\n// minus operation on visit count may get negative value, so use signed int\ntype vcntint int8\n\ntype Date time.Time\n\nconst dateLayout = \"2006-01-02\"\n\nfunc (d Date) MarshalJSON() ([]byte, error) {\n\treturn []byte(\"\\\"\" + time.Time(d).Format(dateLayout) + \"\\\"\"), nil\n}\n\nfunc (d *Date) UnmarshalJSON(input []byte) error {\n\tif len(input) != len(dateLayout)+2 {\n\t\treturn errors.New(fmt.Sprintf(\"unmarshaling date: invalid input %s\", string(input)))\n\t}\n\tinput = input[1 : len(dateLayout)+1]\n\tt, err := time.Parse(dateLayout, string(input))\n\t*d = Date(t)\n\treturn err\n}\n\n// COW don't need very accurate visit count, so update to visit count value is\n// not protected.\ntype VisitCnt struct {\n\tDirect    vcntint   `json:\"direct\"`\n\tBlocked   vcntint   `json:\"block\"`\n\tRecent    Date      `json:\"recent\"`\n\trUpdated  bool      // whether Recent is updated, we only need date precision\n\tblockedOn time.Time // when is the site last blocked\n}\n\nfunc newVisitCnt(direct, blocked vcntint) *VisitCnt {\n\treturn &VisitCnt{direct, blocked, Date(time.Now()), true, zeroTime}\n}\n\nfunc newVisitCntWithTime(direct, blocked vcntint, t time.Time) *VisitCnt {\n\treturn &VisitCnt{direct, blocked, Date(t), true, zeroTime}\n}\n\nfunc (vc *VisitCnt) userSpecified() bool {\n\treturn vc.Blocked == userCnt || vc.Direct == userCnt\n}\n\nconst siteStaleThreshold = 10 * 24 * time.Hour\n\nfunc (vc *VisitCnt) isStale() bool {\n\treturn time.Now().Sub(time.Time(vc.Recent)) > siteStaleThreshold\n}\n\n// shouldNotSave returns true if the a VisitCnt is not visited for a long time\n// (several days) or is specified by user.\nfunc (vc *VisitCnt) shouldNotSave() bool {\n\treturn vc.userSpecified() || vc.isStale() || (vc.Blocked == 0 && vc.Direct == 0)\n}\n\nconst tmpBlockedTimeout = 2 * time.Minute\n\nfunc (vc *VisitCnt) AsTempBlocked() bool {\n\treturn time.Now().Sub(vc.blockedOn) < tmpBlockedTimeout\n}\n\nfunc (vc *VisitCnt) AsDirect() bool {\n\treturn (vc.Blocked == 0) || (vc.Direct-vc.Blocked >= directDelta) || vc.AlwaysDirect()\n}\n\nfunc (vc *VisitCnt) AsBlocked() bool {\n\tif vc.Blocked == userCnt || vc.AsTempBlocked() {\n\t\treturn true\n\t}\n\t// add some randomness to fix mistake\n\tdelta := vc.Blocked - vc.Direct\n\treturn delta >= blockedDelta && rand.Intn(int(delta)) != 0\n}\n\nfunc (vc *VisitCnt) AlwaysDirect() bool {\n\treturn vc.Direct == userCnt\n}\n\nfunc (vc *VisitCnt) AlwaysBlocked() bool {\n\treturn vc.Blocked == userCnt\n}\n\nfunc (vc *VisitCnt) OnceBlocked() bool {\n\treturn vc.Blocked > 0 || vc.AlwaysBlocked() || vc.AsTempBlocked()\n}\n\nfunc (vc *VisitCnt) tempBlocked() {\n\tvc.blockedOn = time.Now()\n}\n\n// time.Time is composed of 3 fields, so need lock to protect update. As\n// update of last visit is not frequent (at most once for each domain), use a\n// global lock to avoid associating a lock to each VisitCnt.\nvar visitLock sync.Mutex\n\n// visit updates visit cnt\nfunc (vc *VisitCnt) visit(inc *vcntint) {\n\tif *inc < maxCnt {\n\t\t*inc++\n\t}\n\t// Because of concurrent update, possible for *inc to overflow and become\n\t// negative, but very unlikely.\n\tif *inc > maxCnt || *inc < 0 {\n\t\t*inc = maxCnt\n\t}\n\n\tif !vc.rUpdated {\n\t\tvc.rUpdated = true\n\t\tvisitLock.Lock()\n\t\tvc.Recent = Date(time.Now())\n\t\tvisitLock.Unlock()\n\t}\n}\n\nfunc (vc *VisitCnt) DirectVisit() {\n\tif networkBad() || vc.userSpecified() {\n\t\treturn\n\t}\n\t// one successful direct visit probably means the site is not actually\n\t// blocked\n\tvc.visit(&vc.Direct)\n\tvc.Blocked = 0\n}\n\nfunc (vc *VisitCnt) BlockedVisit() {\n\tif networkBad() || vc.userSpecified() {\n\t\treturn\n\t}\n\t// When a site changes from direct to blocked by GFW, COW should learn\n\t// this quickly and remove it from the PAC ASAP. So change direct to 0\n\t// once there's a single blocked visit, this ensures the site is removed\n\t// upon the next PAC update.\n\tvc.visit(&vc.Blocked)\n\tvc.Direct = 0\n}\n\ntype SiteStat struct {\n\tUpdate Date                 `json:\"update\"`\n\tVcnt   map[string]*VisitCnt `json:\"site_info\"` // Vcnt uses host as key\n\tvcLock sync.RWMutex\n\n\t// Whether a domain has blocked host. Used to avoid considering a domain as\n\t// direct though it has blocked hosts.\n\thasBlockedHost map[string]bool\n\thbhLock        sync.RWMutex\n}\n\nfunc newSiteStat() *SiteStat {\n\treturn &SiteStat{\n\t\tVcnt:           map[string]*VisitCnt{},\n\t\thasBlockedHost: map[string]bool{},\n\t}\n}\n\nfunc (ss *SiteStat) get(s string) *VisitCnt {\n\tss.vcLock.RLock()\n\tVcnt, ok := ss.Vcnt[s]\n\tss.vcLock.RUnlock()\n\tif ok {\n\t\treturn Vcnt\n\t}\n\treturn nil\n}\n\nfunc (ss *SiteStat) create(s string) (vcnt *VisitCnt) {\n\tvcnt = newVisitCnt(0, 0)\n\tss.vcLock.Lock()\n\tss.Vcnt[s] = vcnt\n\tss.vcLock.Unlock()\n\treturn\n}\n\n// Caller should guarantee that always direct url does not attempt\n// blocked visit.\nfunc (ss *SiteStat) TempBlocked(url *URL) {\n\tdebug.Printf(\"%s temp blocked\\n\", url.Host)\n\n\tvcnt := ss.get(url.Host)\n\tif vcnt == nil {\n\t\tpanic(\"TempBlocked should always get existing visitCnt\")\n\t}\n\tvcnt.tempBlocked()\n\n\t// Mistakenly consider a partial blocked domain as direct will make that\n\t// domain into PAC and never have a chance to correct the error.\n\t// Once using blocked visit, a host is considered to maybe blocked even if\n\t// it's block visit count decrease to 0. As hasBlockedHost is not saved,\n\t// upon next start up of COW, the information will reflect the current\n\t// status of that host.\n\tss.hbhLock.RLock()\n\tt := ss.hasBlockedHost[url.Domain]\n\tss.hbhLock.RUnlock()\n\tif !t {\n\t\tss.hbhLock.Lock()\n\t\tss.hasBlockedHost[url.Domain] = true\n\t\tss.hbhLock.Unlock()\n\t}\n}\n\nvar alwaysDirectVisitCnt = newVisitCnt(userCnt, 0)\n\nfunc (ss *SiteStat) GetVisitCnt(url *URL) (vcnt *VisitCnt) {\n\tif parentProxy.empty() { // no way to retry, so always visit directly\n\t\treturn alwaysDirectVisitCnt\n\t}\n\tif url.Domain == \"\" { // simple host or private ip\n\t\treturn alwaysDirectVisitCnt\n\t}\n\tif vcnt = ss.get(url.Host); vcnt != nil {\n\t\treturn\n\t}\n\tif len(url.Domain) != len(url.Host) {\n\t\tif dmcnt := ss.get(url.Domain); dmcnt != nil && dmcnt.userSpecified() {\n\t\t\t// if the domain is not specified by user, should create a new host\n\t\t\t// visitCnt\n\t\t\treturn dmcnt\n\t\t}\n\t}\n\treturn ss.create(url.Host)\n}\n\nfunc (ss *SiteStat) store(statPath string) (err error) {\n\tnow := time.Now()\n\tvar savedSS *SiteStat\n\tif ss.Update == Date(zeroTime) {\n\t\tss.Update = Date(time.Now())\n\t}\n\tif now.Sub(time.Time(ss.Update)) > siteStaleThreshold {\n\t\t// Not updated for a long time, don't drop any record\n\t\tsavedSS = ss\n\t\t// Changing update time too fast will also drop useful record\n\t\tsavedSS.Update = Date(time.Time(ss.Update).Add(siteStaleThreshold / 2))\n\t\tif time.Time(savedSS.Update).After(now) {\n\t\t\tsavedSS.Update = Date(now)\n\t\t}\n\t} else {\n\t\tsavedSS = newSiteStat()\n\t\tsavedSS.Update = Date(now)\n\t\tss.vcLock.RLock()\n\t\tfor site, vcnt := range ss.Vcnt {\n\t\t\tif vcnt.shouldNotSave() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tsavedSS.Vcnt[site] = vcnt\n\t\t}\n\t\tss.vcLock.RUnlock()\n\t}\n\n\tb, err := json.MarshalIndent(savedSS, \"\", \"\\t\")\n\tif err != nil {\n\t\terrl.Println(\"Error marshalling site stat:\", err)\n\t\tpanic(\"internal error: error marshalling site\")\n\t}\n\n\t// Store stat into temp file first and then rename.\n\t// Ensures atomic update to stat file to avoid file damage.\n\n\t// Create tmp file inside config firectory to avoid cross FS rename.\n\tf, err := ioutil.TempFile(config.dir, \"stat\")\n\tif err != nil {\n\t\terrl.Println(\"create tmp file to store stat\", err)\n\t\treturn\n\t}\n\tif _, err = f.Write(b); err != nil {\n\t\terrl.Println(\"Error writing stat file:\", err)\n\t\tf.Close()\n\t\treturn\n\t}\n\tf.Close()\n\n\t// Windows don't allow rename to existing file.\n\tos.Remove(statPath + \".bak\")\n\tos.Rename(statPath, statPath+\".bak\")\n\tif err = os.Rename(f.Name(), statPath); err != nil {\n\t\terrl.Println(\"rename new stat file\", err)\n\t\treturn\n\t}\n\treturn\n}\n\nfunc (ss *SiteStat) loadList(lst []string, direct, blocked vcntint) {\n\tfor _, d := range lst {\n\t\tss.Vcnt[d] = newVisitCntWithTime(direct, blocked, zeroTime)\n\t}\n}\n\nfunc (ss *SiteStat) loadBuiltinList() {\n\tss.loadList(blockedDomainList, 0, userCnt)\n\tss.loadList(directDomainList, userCnt, 0)\n}\n\nfunc (ss *SiteStat) loadUserList() {\n\tif directList, err := loadSiteList(config.DirectFile); err == nil {\n\t\tss.loadList(directList, userCnt, 0)\n\t}\n\tif blockedList, err := loadSiteList(config.BlockedFile); err == nil {\n\t\tss.loadList(blockedList, 0, userCnt)\n\t}\n}\n\n// Filter sites covered by user specified domains, also filter out stale\n// sites.\nfunc (ss *SiteStat) filterSites() {\n\t// It's not safe to remove element while iterating over a map.\n\tvar removeSites []string\n\n\t// find what to remove first\n\tss.vcLock.RLock()\n\tfor site, vcnt := range ss.Vcnt {\n\t\tif vcnt.userSpecified() {\n\t\t\tcontinue\n\t\t}\n\t\tif vcnt.isStale() {\n\t\t\tremoveSites = append(removeSites, site)\n\t\t\tcontinue\n\t\t}\n\t\tvar dmcnt *VisitCnt\n\t\tdomain := host2Domain(site)\n\t\tif domain != site {\n\t\t\tdmcnt = ss.get(domain)\n\t\t}\n\t\tif dmcnt != nil && dmcnt.userSpecified() {\n\t\t\tremoveSites = append(removeSites, site)\n\t\t}\n\t}\n\tss.vcLock.RUnlock()\n\n\t// do remove\n\tss.vcLock.Lock()\n\tfor _, site := range removeSites {\n\t\tdelete(ss.Vcnt, site)\n\t}\n\tss.vcLock.Unlock()\n}\n\nfunc (ss *SiteStat) load(file string) (err error) {\n\tdefer func() {\n\t\t// load builtin list first, so user list can override builtin\n\t\tss.loadBuiltinList()\n\t\tss.loadUserList()\n\t\tss.filterSites()\n\t\tfor host, vcnt := range ss.Vcnt {\n\t\t\tif vcnt.OnceBlocked() {\n\t\t\t\tss.hasBlockedHost[host2Domain(host)] = true\n\t\t\t}\n\t\t}\n\t}()\n\tif file == \"\" {\n\t\treturn\n\t}\n\tif err = isFileExists(file); err != nil {\n\t\tif !os.IsNotExist(err) {\n\t\t\terrl.Println(\"Error loading stat:\", err)\n\t\t}\n\t\treturn\n\t}\n\tvar f *os.File\n\tif f, err = os.Open(file); err != nil {\n\t\terrl.Printf(\"Error opening site stat %s: %v\\n\", file, err)\n\t\treturn\n\t}\n\tdefer f.Close()\n\tb, err := ioutil.ReadAll(f)\n\tif err != nil {\n\t\terrl.Println(\"Error reading site stat:\", err)\n\t\treturn\n\t}\n\tif err = json.Unmarshal(b, ss); err != nil {\n\t\terrl.Println(\"Error decoding site stat:\", err)\n\t\treturn\n\t}\n\treturn\n}\n\nfunc (ss *SiteStat) GetDirectList() []string {\n\tlst := make([]string, 0)\n\t// anyway to do more fine grained locking?\n\tss.vcLock.RLock()\n\tfor site, vc := range ss.Vcnt {\n\t\tif ss.hasBlockedHost[host2Domain(site)] {\n\t\t\tcontinue\n\t\t}\n\t\tif vc.AsDirect() {\n\t\t\tlst = append(lst, site)\n\t\t}\n\t}\n\tss.vcLock.RUnlock()\n\treturn lst\n}\n\nvar siteStat = newSiteStat()\n\nfunc initSiteStat() {\n\terr := siteStat.load(config.StatFile)\n\tif err != nil {\n\t\t// Simply try to load the stat.back, create a new object to avoid error\n\t\t// in default site list.\n\t\tsiteStat = newSiteStat()\n\t\terr = siteStat.load(config.StatFile + \".bak\")\n\t\t// After all its not critical , simply re-create a stat object if anything is not ok\n\t\tif err != nil {\n\t\t\tsiteStat = newSiteStat()\n\t\t\tsiteStat.load(\"\") // load default site list\n\t\t}\n\t}\n\n\t// Dump site stat while running, so we don't always need to close cow to\n\t// get updated stat.\n\tgo func() {\n\t\tfor {\n\t\t\ttime.Sleep(5 * time.Minute)\n\t\t\tstoreSiteStat(siteStatCont)\n\t\t}\n\t}()\n}\n\nconst (\n\tsiteStatExit = iota\n\tsiteStatCont\n)\n\n// Lock ensures only one goroutine calling store.\n// siteStatFini ensures no more calls after going to exit.\nvar storeLock sync.Mutex\nvar siteStatFini bool\n\nfunc storeSiteStat(cont byte) {\n\tstoreLock.Lock()\n\tdefer storeLock.Unlock()\n\n\tif siteStatFini {\n\t\treturn\n\t}\n\tsiteStat.store(config.StatFile)\n\tif cont == siteStatExit {\n\t\tsiteStatFini = true\n\t}\n}\n\nfunc loadSiteList(fpath string) (lst []string, err error) {\n\tif fpath == \"\" {\n\t\treturn\n\t}\n\tif err = isFileExists(fpath); err != nil {\n\t\tif !os.IsNotExist(err) {\n\t\t\tinfo.Printf(\"Error loading domaint list: %v\\n\", err)\n\t\t}\n\t\treturn\n\t}\n\tf, err := os.Open(fpath)\n\tif err != nil {\n\t\terrl.Println(\"Error opening domain list:\", err)\n\t\treturn\n\t}\n\tdefer f.Close()\n\n\tscanner := bufio.NewScanner(f)\n\tlst = make([]string, 0)\n\tfor scanner.Scan() {\n\t\tsite := strings.TrimSpace(scanner.Text())\n\t\tif site == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tlst = append(lst, site)\n\t}\n\tif scanner.Err() != nil {\n\t\terrl.Printf(\"Error reading domain list %s: %v\\n\", fpath, scanner.Err())\n\t}\n\treturn lst, scanner.Err()\n}\n"
  },
  {
    "path": "sitestat_test.go",
    "content": "package main\n\nimport (\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n)\n\nvar _ = os.Remove\n\nfunc TestNetworkBad(t *testing.T) {\n\tif networkBad() {\n\t\tt.Error(\"Network by default should be good\")\n\t}\n}\n\nfunc TestDateMarshal(t *testing.T) {\n\td := Date(time.Date(2013, 2, 4, 0, 0, 0, 0, time.UTC))\n\tj, err := d.MarshalJSON()\n\tif err != nil {\n\t\tt.Error(\"Error marshalling json:\", err)\n\t}\n\tif string(j) != \"\\\"2013-02-04\\\"\" {\n\t\tt.Error(\"Date marshal result wrong, got:\", string(j))\n\t}\n\n\terr = d.UnmarshalJSON([]byte(\"\\\"2013-01-01\\\"\"))\n\tif err != nil {\n\t\tt.Error(\"Error unmarshaling Date:\", err)\n\t}\n\ttm := time.Time(d)\n\tif tm.Year() != 2013 || tm.Month() != 1 || tm.Day() != 1 {\n\t\tt.Error(\"Unmarshaled date wrong, got:\", tm)\n\t}\n}\n\nfunc TestSiteStatLoadStore(t *testing.T) {\n\tss := newSiteStat()\n\tss.load(\"testdata/nosuchfile\") // load buildin and user specified list\n\tif len(ss.GetDirectList()) == 0 {\n\t\tt.Error(\"builtin site should appear in direct site list even with no stat file\")\n\t}\n\n\turl1, _ := ParseRequestURI(\"www.foobar.com\")\n\turl2, _ := ParseRequestURI(\"img.foobar.com\")\n\tvcnt1 := ss.GetVisitCnt(url1)\n\tvcnt1.DirectVisit()\n\tvcnt1.DirectVisit()\n\tvcnt1.DirectVisit()\n\tvcnt2 := ss.GetVisitCnt(url2)\n\tvcnt2.DirectVisit()\n\n\tblockurl1, _ := ParseRequestURI(\"blocked.com\")\n\tblockurl2, _ := ParseRequestURI(\"blockeurl2.com\")\n\tsi1 := ss.GetVisitCnt(blockurl1)\n\tsi1.BlockedVisit()\n\tsi2 := ss.GetVisitCnt(blockurl2)\n\tsi2.BlockedVisit()\n\n\t// make google.com with a large direct count, but plus.google.com is in blocked list\n\t// so it shouldn't be considered as direct site\n\tgurl, _ := ParseRequestURI(\"google.com\")\n\tgvcnt := ss.GetVisitCnt(gurl)\n\tgvcnt.Direct = 100\n\n\tconst stfile = \"testdata/stat\"\n\tif err := ss.store(stfile); err != nil {\n\t\tt.Fatal(\"store error:\", err)\n\t}\n\n\tld := newSiteStat()\n\tif err := ld.load(stfile); err != nil {\n\t\tt.Fatal(\"load stat error:\", err)\n\t}\n\tvc := ld.get(url1.Host)\n\tif vc == nil {\n\t\tt.Fatalf(\"load error, %s not loaded\\n\", url1.Host)\n\t}\n\tif vc.Direct != 3 {\n\t\tt.Errorf(\"load error, %s should have visit cnt 3, got: %d\\n\", url1.Host, vc.Direct)\n\t}\n\n\tvc = ld.get(blockurl1.Host)\n\tif vc == nil {\n\t\tt.Errorf(\"load error, %s not loaded\\n\", blockurl1.Host)\n\t}\n\n\t// test bulitin site\n\tap, _ := ParseRequestURI(\"apple.com\")\n\tsi := ld.GetVisitCnt(ap)\n\tif !si.AlwaysDirect() {\n\t\tt.Error(\"builtin site apple.com should always use direct access\")\n\t}\n\ttw, _ := ParseRequestURI(\"twitter.com\")\n\tsi = ld.GetVisitCnt(tw)\n\tif !si.AsBlocked() || !si.AlwaysBlocked() {\n\t\tt.Error(\"builtin site twitter.com should use blocked access\")\n\t}\n\tplus, _ := ParseRequestURI(\"plus.google.com\")\n\tsi = ld.GetVisitCnt(plus)\n\tif !si.AsBlocked() || !si.AlwaysBlocked() {\n\t\tt.Error(\"builtin site plus.google.com should use blocked access\")\n\t}\n\n\tdirectList := ld.GetDirectList()\n\tif len(directList) == 0 {\n\t\tt.Error(\"builtin site should appear in direct site list\")\n\t}\n\tif !ld.hasBlockedHost[\"google.com\"] {\n\t\tt.Error(\"google.com should have blocked host\")\n\t}\n\tfor _, d := range directList {\n\t\tif d == \"google.com\" {\n\t\t\tt.Errorf(\"direct list contains 2nd level domain which has sub host that's blocked\")\n\t\t}\n\t}\n\tos.Remove(stfile)\n}\n\nfunc TestSiteStatVisitCnt(t *testing.T) {\n\tss := newSiteStat()\n\n\tg1, _ := ParseRequestURI(\"www.gtemp.com\")\n\tg2, _ := ParseRequestURI(\"calendar.gtemp.com\")\n\tg3, _ := ParseRequestURI(\"docs.gtemp.com\")\n\n\tsg1 := ss.GetVisitCnt(g1)\n\tfor i := 0; i < 30; i++ {\n\t\tsg1.DirectVisit()\n\t}\n\tsg2 := ss.GetVisitCnt(g2)\n\tsg2.DirectVisit()\n\tsg3 := ss.GetVisitCnt(g3)\n\tsg3.DirectVisit()\n\n\tif ss.hasBlockedHost[g1.Domain] {\n\t\tt.Errorf(\"direct domain %s should not have host at first\\n\", g1.Domain)\n\t}\n\n\tvc := ss.get(g1.Host)\n\tif vc == nil {\n\t\tt.Fatalf(\"no VisitCnt for %s\\n\", g1.Host)\n\t}\n\tif vc.Direct != 30 {\n\t\tt.Errorf(\"direct cnt for %s not correct, should be 30, got: %d\\n\", g1.Host, vc.Direct)\n\t}\n\tif vc.Blocked != 0 {\n\t\tt.Errorf(\"block cnt for %s not correct, should be 0 before blocked visit, got: %d\\n\", g1.Host, vc.Blocked)\n\t}\n\tif vc.rUpdated != true {\n\t\tt.Errorf(\"VisitCnt lvUpdated should be true after visit\")\n\t}\n\n\tvc.BlockedVisit()\n\tif vc.Blocked != 1 {\n\t\tt.Errorf(\"blocked cnt for %s after 1 blocked visit should be 1, got: %d\\n\", g1.Host, vc.Blocked)\n\t}\n\tif vc.Direct != 0 {\n\t\tt.Errorf(\"direct cnt for %s after 1 blocked visit should be 0, got: %d\\n\", g1.Host, vc.Direct)\n\t}\n\tif vc.AsDirect() {\n\t\tt.Errorf(\"after blocked visit, a site should not be considered as direct\\n\")\n\t}\n\n\t// test blocked visit\n\tg4, _ := ParseRequestURI(\"plus.gtemp.com\")\n\tsi := ss.GetVisitCnt(g4)\n\tss.TempBlocked(g4)\n\t// should be blocked for 2 minutes\n\tif !si.AsTempBlocked() {\n\t\tt.Error(\"should be blocked for 2 minutes after blocked visit\")\n\t}\n\tsi.BlockedVisit() // After temp blocked, update blocked visit count\n\tif si.Blocked != 1 {\n\t\tt.Errorf(\"blocked cnt for %s not correct, should be 1, got: %d\\n\", g4.Host, vc.Blocked)\n\t}\n\tvc = ss.get(g4.Host)\n\tif vc == nil {\n\t\tt.Fatal(\"no VisitCnt for \", g4.Host)\n\t}\n\tif vc.Direct != 0 {\n\t\tt.Errorf(\"direct cnt for %s not correct, should be 0, got: %d\\n\", g4.Host, vc.Direct)\n\t}\n\tif !ss.hasBlockedHost[g4.Domain] {\n\t\tt.Errorf(\"direct domain %s should have blocked host after blocked visit\\n\", g4.Domain)\n\t}\n}\n\nfunc TestSiteStatGetVisitCnt(t *testing.T) {\n\tss := newSiteStat()\n\n\tg, _ := ParseRequestURI(\"gtemp.com\")\n\tsi := ss.GetVisitCnt(g)\n\tif !si.AsDirect() {\n\t\tt.Error(\"never visited site should be considered as direct\")\n\t}\n\tif si.AsBlocked() || si.AsTempBlocked() {\n\t\tt.Error(\"never visited site should not be considered as blocked/temp blocked\")\n\t}\n\tsi.DirectVisit()\n\tgw, _ := ParseRequestURI(\"www.gtemp.com\")\n\tsig := ss.GetVisitCnt(gw)\n\t// gtemp.com is not user specified, www.gtemp.com should get separate visitCnt\n\tif sig == si {\n\t\tt.Error(\"host should get separate visitCnt for not user specified domain\")\n\t}\n\n\tb, _ := ParseRequestURI(\"www.btemp.com\")\n\tss.Vcnt[b.Host] = newVisitCnt(userCnt, 0)\n\tvc := ss.get(b.Host)\n\tif !vc.userSpecified() {\n\t\tt.Error(\"should be user specified\")\n\t}\n\tif !vc.shouldNotSave() {\n\t\tt.Error(\"user specified should be dropped\")\n\t}\n\tsi = ss.GetVisitCnt(b)\n\tif !si.AlwaysDirect() {\n\t\tt.Errorf(\"%s should alwaysDirect\\n\", b.Host)\n\t}\n\tif si.AlwaysBlocked() {\n\t\tt.Errorf(\"%s should not alwaysBlocked\\n\", b.Host)\n\t}\n\tif si.OnceBlocked() {\n\t\tt.Errorf(\"%s should not onceBlocked\\n\", b.Host)\n\t}\n\tif !si.AsDirect() {\n\t\tt.Errorf(\"%s should use direct visit\\n\", b.Host)\n\t}\n\n\ttw, _ := ParseRequestURI(\"www.tblocked.com\")\n\tss.Vcnt[tw.Domain] = newVisitCnt(0, userCnt)\n\tsi = ss.GetVisitCnt(tw)\n\tif !si.AsBlocked() {\n\t\tt.Errorf(\"%s should use blocked visit\\n\", tw.Host)\n\t}\n\tif si.AlwaysDirect() {\n\t\tt.Errorf(\"%s should not alwaysDirect\\n\", tw.Host)\n\t}\n\tif !si.AlwaysBlocked() {\n\t\tt.Errorf(\"%s should not alwaysBlocked\\n\", tw.Host)\n\t}\n\tif !si.OnceBlocked() {\n\t\tt.Errorf(\"%s should onceBlocked\\n\", tw.Host)\n\t}\n\n\tg1, _ := ParseRequestURI(\"www.shoulddirect.com\")\n\tfor i := 0; i < directDelta; i++ {\n\t\tsi.DirectVisit()\n\t}\n\tsi = ss.GetVisitCnt(g1)\n\tif !si.AsDirect() {\n\t\tt.Errorf(\"%s direct %d times, should use direct visit\\n\", g1.Host, directDelta+1)\n\t}\n\tif si.OnceBlocked() {\n\t\tt.Errorf(\"%s has not blocked visit, should not has once blocked\\n\", g1.Host)\n\t}\n\tsi = ss.GetVisitCnt(g1)\n\tsi.BlockedVisit()\n\tif !si.OnceBlocked() {\n\t\tt.Errorf(\"%s has one blocked visit, should has once blocked\\n\", g1.Host)\n\t}\n}\n"
  },
  {
    "path": "ssh.go",
    "content": "package main\n\nimport (\n\t\"net\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"time\"\n)\n\nfunc SshRunning(socksServer string) bool {\n\tc, err := net.Dial(\"tcp\", socksServer)\n\tif err != nil {\n\t\treturn false\n\t}\n\tc.Close()\n\treturn true\n}\n\nfunc runOneSSH(server string) {\n\t// config parsing canonicalize sshServer config value\n\tarr := strings.SplitN(server, \":\", 3)\n\tsshServer, localPort, sshPort := arr[0], arr[1], arr[2]\n\talreadyRunPrinted := false\n\n\tsocksServer := \"127.0.0.1:\" + localPort\n\tfor {\n\t\tif SshRunning(socksServer) {\n\t\t\tif !alreadyRunPrinted {\n\t\t\t\tdebug.Println(\"ssh socks server\", socksServer, \"maybe already running\")\n\t\t\t\talreadyRunPrinted = true\n\t\t\t}\n\t\t\ttime.Sleep(30 * time.Second)\n\t\t\tcontinue\n\t\t}\n\n\t\t// -n redirects stdin from /dev/null\n\t\t// -N do not execute remote command\n\t\tdebug.Println(\"connecting to ssh server\", sshServer+\":\"+sshPort)\n\t\tcmd := exec.Command(\"ssh\", \"-n\", \"-N\", \"-D\", localPort, \"-p\", sshPort, sshServer)\n\t\tif err := cmd.Run(); err != nil {\n\t\t\tdebug.Println(\"ssh:\", err)\n\t\t}\n\t\tdebug.Println(\"ssh\", sshServer+\":\"+sshPort, \"exited, reconnect\")\n\t\ttime.Sleep(5 * time.Second)\n\t\talreadyRunPrinted = false\n\t}\n}\n\nfunc runSSH() {\n\tfor _, server := range config.SshServer {\n\t\tgo runOneSSH(server)\n\t}\n}\n"
  },
  {
    "path": "stat.go",
    "content": "// Proxy statistics.\n\npackage main\n\nimport (\n\t\"sync\"\n\t\"sync/atomic\"\n)\n\nvar status struct {\n\tcliCnt          int32          // number of client connections\n\tsrvConnCnt      map[string]int // number of connections for each host:port\n\tsrvConnCntMutex sync.Mutex\n}\n\nfunc initStat() {\n\tif !debug {\n\t\treturn\n\t}\n\tstatus.srvConnCnt = make(map[string]int)\n}\n\nfunc incCliCnt() int32 {\n\tatomic.AddInt32(&status.cliCnt, 1)\n\treturn status.cliCnt\n}\n\nfunc decCliCnt() int32 {\n\tatomic.AddInt32(&status.cliCnt, -1)\n\treturn status.cliCnt\n}\n\nfunc addSrvConnCnt(srv string, delta int) int {\n\tstatus.srvConnCntMutex.Lock()\n\tstatus.srvConnCnt[srv] += delta\n\tcnt := status.srvConnCnt[srv]\n\tstatus.srvConnCntMutex.Unlock()\n\treturn int(cnt)\n}\n\nfunc incSrvConnCnt(srv string) int {\n\treturn addSrvConnCnt(srv, 1)\n}\n\nfunc decSrvConnCnt(srv string) int {\n\treturn addSrvConnCnt(srv, -1)\n}\n"
  },
  {
    "path": "testdata/file",
    "content": ""
  },
  {
    "path": "timeoutset.go",
    "content": "package main\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\ntype TimeoutSet struct {\n\tsync.RWMutex\n\ttime    map[string]time.Time\n\ttimeout time.Duration\n}\n\nfunc NewTimeoutSet(timeout time.Duration) *TimeoutSet {\n\tts := &TimeoutSet{time: make(map[string]time.Time),\n\t\ttimeout: timeout,\n\t}\n\treturn ts\n}\n\nfunc (ts *TimeoutSet) add(key string) {\n\tnow := time.Now()\n\tts.Lock()\n\tts.time[key] = now\n\tts.Unlock()\n}\n\nfunc (ts *TimeoutSet) has(key string) bool {\n\tts.RLock()\n\tt, ok := ts.time[key]\n\tts.RUnlock()\n\tif !ok {\n\t\treturn false\n\t}\n\tif time.Now().Sub(t) > ts.timeout {\n\t\tts.del(key)\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (ts *TimeoutSet) del(key string) {\n\tts.Lock()\n\tdelete(ts.time, key)\n\tts.Unlock()\n}\n"
  },
  {
    "path": "util.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"crypto/md5\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"path\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/cyfdecyf/bufio\"\n)\n\nconst isWindows = runtime.GOOS == \"windows\"\n\ntype notification chan byte\n\nfunc newNotification() notification {\n\t// Notification channle has size 1, so sending a single one will not block\n\treturn make(chan byte, 1)\n}\n\nfunc (n notification) notify() {\n\tn <- 1\n}\n\nfunc (n notification) hasNotified() bool {\n\tselect {\n\tcase <-n:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc ASCIIToUpperInplace(b []byte) {\n\tfor i := 0; i < len(b); i++ {\n\t\tif 97 <= b[i] && b[i] <= 122 {\n\t\t\tb[i] -= 32\n\t\t}\n\t}\n}\n\nfunc ASCIIToUpper(b []byte) []byte {\n\tbuf := make([]byte, len(b))\n\tfor i := 0; i < len(b); i++ {\n\t\tif 97 <= b[i] && b[i] <= 122 {\n\t\t\tbuf[i] = b[i] - 32\n\t\t} else {\n\t\t\tbuf[i] = b[i]\n\t\t}\n\t}\n\treturn buf\n}\n\nfunc ASCIIToLowerInplace(b []byte) {\n\tfor i := 0; i < len(b); i++ {\n\t\tif 65 <= b[i] && b[i] <= 90 {\n\t\t\tb[i] += 32\n\t\t}\n\t}\n}\n\nfunc ASCIIToLower(b []byte) []byte {\n\tbuf := make([]byte, len(b))\n\tfor i := 0; i < len(b); i++ {\n\t\tif 65 <= b[i] && b[i] <= 90 {\n\t\t\tbuf[i] = b[i] + 32\n\t\t} else {\n\t\t\tbuf[i] = b[i]\n\t\t}\n\t}\n\treturn buf\n}\n\nfunc IsDigit(b byte) bool {\n\treturn '0' <= b && b <= '9'\n}\n\nvar spaceTbl = [256]bool{\n\t'\\t': true, // ht\n\t'\\n': true, // lf\n\t'\\r': true, // cr\n\t' ':  true, // sp\n}\n\nfunc IsSpace(b byte) bool {\n\treturn spaceTbl[b]\n}\n\nfunc TrimSpace(s []byte) []byte {\n\tst := 0\n\tend := len(s) - 1\n\tfor ; st < len(s) && IsSpace(s[st]); st++ {\n\t}\n\tif st == len(s) {\n\t\treturn s[:0]\n\t}\n\tfor ; end >= 0 && IsSpace(s[end]); end-- {\n\t}\n\treturn s[st : end+1]\n}\n\nfunc TrimTrailingSpace(s []byte) []byte {\n\tend := len(s) - 1\n\tfor ; end >= 0 && IsSpace(s[end]); end-- {\n\t}\n\treturn s[:end+1]\n}\n\n// FieldsN is simliar with bytes.Fields, but only consider space and '\\t' as\n// space, and will include all content in the final slice with ending white\n// space characters trimmed. bytes.Split can't split on both space and '\\t',\n// and considers two separator as an empty item. bytes.FieldsFunc can't\n// specify how much fields we need, which is required for parsing response\n// status line. Returns nil if n < 0.\nfunc FieldsN(s []byte, n int) [][]byte {\n\tif n <= 0 {\n\t\treturn nil\n\t}\n\tres := make([][]byte, n)\n\tna := 0\n\tfieldStart := -1\n\tvar i int\n\tfor ; i < len(s); i++ {\n\t\tissep := s[i] == ' ' || s[i] == '\\t'\n\t\tif fieldStart < 0 && !issep {\n\t\t\tfieldStart = i\n\t\t}\n\t\tif fieldStart >= 0 && issep {\n\t\t\tif na == n-1 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tres[na] = s[fieldStart:i]\n\t\t\tna++\n\t\t\tfieldStart = -1\n\t\t}\n\t}\n\tif fieldStart >= 0 { // must have na <= n-1 here\n\t\tres[na] = TrimSpace(s[fieldStart:])\n\t\tif len(res[na]) != 0 { // do not consider ending space as a field\n\t\t\tna++\n\t\t}\n\t}\n\treturn res[:na]\n}\n\nvar digitTbl = [256]int8{\n\t-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\n\t-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\n\t-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\n\t0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,\n\t-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,\n\t-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\n\t-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,\n\t-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\n}\n\n// ParseIntFromBytes parse hexidecimal number from given bytes.\n// No prefix (e.g. 0xdeadbeef) should given.\n// base can only be 10 or 16.\nfunc ParseIntFromBytes(b []byte, base int) (n int64, err error) {\n\t// Currently, we have to convert []byte to string to use strconv\n\t// Refer to: http://code.google.com/p/go/issues/detail?id=2632\n\t// That's why I created this function.\n\tif base != 10 && base != 16 {\n\t\terr = errors.New(fmt.Sprintf(\"invalid base: %d\", base))\n\t\treturn\n\t}\n\tif len(b) == 0 {\n\t\terr = errors.New(\"parse int from empty bytes\")\n\t\treturn\n\t}\n\n\tneg := false\n\tif b[0] == '+' {\n\t\tb = b[1:]\n\t} else if b[0] == '-' {\n\t\tb = b[1:]\n\t\tneg = true\n\t}\n\n\tfor _, d := range b {\n\t\tv := digitTbl[d]\n\t\tif v == -1 {\n\t\t\tn = 0\n\t\t\terr = errors.New(fmt.Sprintf(\"invalid number: %s\", b))\n\t\t\treturn\n\t\t}\n\t\tif int(v) >= base {\n\t\t\tn = 0\n\t\t\terr = errors.New(fmt.Sprintf(\"invalid base %d number: %s\", base, b))\n\t\t\treturn\n\t\t}\n\t\tn *= int64(base)\n\t\tn += int64(v)\n\t}\n\tif neg {\n\t\tn = -n\n\t}\n\treturn\n}\n\nfunc isFileExists(path string) error {\n\tstat, err := os.Stat(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !stat.Mode().IsRegular() {\n\t\treturn fmt.Errorf(\"%s is not regular file\", path)\n\t}\n\treturn nil\n}\n\nfunc isDirExists(path string) error {\n\tstat, err := os.Stat(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !stat.IsDir() {\n\t\treturn fmt.Errorf(\"%s is not directory\", path)\n\t}\n\treturn nil\n}\n\nfunc getUserHomeDir() string {\n\thome := os.Getenv(\"HOME\")\n\tif home == \"\" {\n\t\tfmt.Println(\"HOME environment variable is empty\")\n\t}\n\treturn home\n}\n\nfunc expandTilde(pth string) string {\n\tif len(pth) > 0 && pth[0] == '~' {\n\t\thome := getUserHomeDir()\n\t\treturn path.Join(home, pth[1:])\n\t}\n\treturn pth\n}\n\n// copyN copys N bytes from src to dst, reading at most rdSize for each read.\n// rdSize should <= buffer size of the buffered reader.\n// Returns any encountered error.\nfunc copyN(dst io.Writer, src *bufio.Reader, n, rdSize int) (err error) {\n\t// Most of the copy is copied from io.Copy\n\tfor n > 0 {\n\t\tvar b []byte\n\t\tvar er error\n\t\tif n > rdSize {\n\t\t\tb, er = src.ReadN(rdSize)\n\t\t} else {\n\t\t\tb, er = src.ReadN(n)\n\t\t}\n\t\tnr := len(b)\n\t\tn -= nr\n\t\tif nr > 0 {\n\t\t\tnw, ew := dst.Write(b)\n\t\t\tif ew != nil {\n\t\t\t\terr = ew\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif nr != nw {\n\t\t\t\terr = io.ErrShortWrite\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif er == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\tif er != nil {\n\t\t\terr = er\n\t\t\tbreak\n\t\t}\n\t}\n\treturn err\n}\n\nfunc md5sum(ss ...string) string {\n\th := md5.New()\n\tfor _, s := range ss {\n\t\tio.WriteString(h, s)\n\t}\n\treturn fmt.Sprintf(\"%x\", h.Sum(nil))\n}\n\n// hostIsIP determines whether a host address is an IP address and whether\n// it is private. Currenly only handles IPv4 addresses.\nfunc hostIsIP(host string) (isIP, isPrivate bool) {\n\tpart := strings.Split(host, \".\")\n\tif len(part) != 4 {\n\t\treturn false, false\n\t}\n\tfor _, i := range part {\n\t\tif len(i) == 0 || len(i) > 3 {\n\t\t\treturn false, false\n\t\t}\n\t\tn, err := strconv.Atoi(i)\n\t\tif err != nil || n < 0 || n > 255 {\n\t\t\treturn false, false\n\t\t}\n\t}\n\tif part[0] == \"127\" || part[0] == \"10\" || (part[0] == \"192\" && part[1] == \"168\") {\n\t\treturn true, true\n\t}\n\tif part[0] == \"172\" {\n\t\tn, _ := strconv.Atoi(part[1])\n\t\tif 16 <= n && n <= 31 {\n\t\t\treturn true, true\n\t\t}\n\t}\n\treturn true, false\n}\n\n// NetNbitIPv4Mask returns a IPMask with highest n bit set.\nfunc NewNbitIPv4Mask(n int) net.IPMask {\n\tif n > 32 {\n\t\tpanic(\"NewNbitIPv4Mask: bit number > 32\")\n\t}\n\tmask := []byte{0, 0, 0, 0}\n\tfor id := 0; id < 4; id++ {\n\t\tif n >= 8 {\n\t\t\tmask[id] = 0xff\n\t\t} else {\n\t\t\tmask[id] = ^byte(1<<(uint8(8-n)) - 1)\n\t\t\tbreak\n\t\t}\n\t\tn -= 8\n\t}\n\treturn net.IPMask(mask)\n}\n\nvar topLevelDomain = map[string]bool{\n\t\"ac\":  true,\n\t\"co\":  true,\n\t\"com\": true,\n\t\"edu\": true,\n\t\"gov\": true,\n\t\"net\": true,\n\t\"org\": true,\n}\n\nfunc trimLastDot(s string) string {\n\tif len(s) > 0 && s[len(s)-1] == '.' {\n\t\treturn s[:len(s)-1]\n\t}\n\treturn s\n}\n\n// host2Domain returns the domain of a host. It will recognize domains like\n// google.com.hk. Returns empty string for simple host and internal IP.\nfunc host2Domain(host string) (domain string) {\n\tisIP, isPrivate := hostIsIP(host)\n\tif isPrivate {\n\t\treturn \"\"\n\t}\n\tif isIP {\n\t\treturn host\n\t}\n\thost = trimLastDot(host)\n\tlastDot := strings.LastIndex(host, \".\")\n\tif lastDot == -1 {\n\t\treturn \"\"\n\t}\n\t// Find the 2nd last dot\n\tdot2ndLast := strings.LastIndex(host[:lastDot], \".\")\n\tif dot2ndLast == -1 {\n\t\treturn host\n\t}\n\n\tpart := host[dot2ndLast+1 : lastDot]\n\t// If the 2nd last part of a domain name equals to a top level\n\t// domain, search for the 3rd part in the host name.\n\t// So domains like bbc.co.uk will not be recorded as co.uk\n\tif topLevelDomain[part] {\n\t\tdot3rdLast := strings.LastIndex(host[:dot2ndLast], \".\")\n\t\tif dot3rdLast == -1 {\n\t\t\treturn host\n\t\t}\n\t\treturn host[dot3rdLast+1:]\n\t}\n\treturn host[dot2ndLast+1:]\n}\n\n// IgnoreUTF8BOM consumes UTF-8 encoded BOM character if present in the file.\nfunc IgnoreUTF8BOM(f *os.File) error {\n\tbom := make([]byte, 3)\n\tn, err := f.Read(bom)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif n != 3 {\n\t\treturn nil\n\t}\n\tif bytes.Equal(bom, []byte{0xEF, 0xBB, 0xBF}) {\n\t\tdebug.Println(\"UTF-8 BOM found\")\n\t\treturn nil\n\t}\n\t// No BOM found, seek back\n\t_, err = f.Seek(-3, 1)\n\treturn err\n}\n\n// Return all host IP addresses.\nfunc hostAddr() (addr []string) {\n\tallAddr, err := net.InterfaceAddrs()\n\tif err != nil {\n\t\tFatal(\"error getting host address\", err)\n\t}\n\tfor _, ad := range allAddr {\n\t\tads := ad.String()\n\t\tid := strings.Index(ads, \"/\")\n\t\tif id == -1 {\n\t\t\t// On windows, no network mask.\n\t\t\tid = len(ads)\n\t\t}\n\t\taddr = append(addr, ads[:id])\n\t}\n\treturn addr\n}\n"
  },
  {
    "path": "util_test.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/cyfdecyf/bufio\"\n)\n\nfunc TestASCIIToUpper(t *testing.T) {\n\ttestData := []struct {\n\t\traw   []byte\n\t\tupper []byte\n\t}{\n\t\t{[]byte(\"foobar\"), []byte(\"FOOBAR\")},\n\t\t{[]byte(\"fOoBAr\"), []byte(\"FOOBAR\")},\n\t\t{[]byte(\"..fOoBAr\\n\"), []byte(\"..FOOBAR\\n\")},\n\t}\n\tfor _, td := range testData {\n\t\tup := ASCIIToUpper(td.raw)\n\t\tif !bytes.Equal(up, td.upper) {\n\t\t\tt.Errorf(\"raw: %s, upper: %s\\n\", td.raw, up)\n\t\t}\n\t}\n}\n\nfunc TestASCIIToLower(t *testing.T) {\n\ttestData := []struct {\n\t\traw   []byte\n\t\tlower []byte\n\t}{\n\t\t{[]byte(\"FOOBAR\"), []byte(\"foobar\")},\n\t\t{[]byte(\"fOoBAr\"), []byte(\"foobar\")},\n\t\t{[]byte(\"..fOoBAr\\n\"), []byte(\"..foobar\\n\")},\n\t}\n\tfor _, td := range testData {\n\t\tlow := ASCIIToLower(td.raw)\n\t\tif !bytes.Equal(low, td.lower) {\n\t\t\tt.Errorf(\"raw: %s, lower: %s\\n\", td.raw, low)\n\t\t}\n\t}\n}\n\nfunc TestIsDigit(t *testing.T) {\n\tfor i := 0; i < 10; i++ {\n\t\tdigit := '0' + byte(i)\n\t\tletter := 'a' + byte(i)\n\n\t\tif IsDigit(digit) != true {\n\t\t\tt.Errorf(\"%c should return true\", digit)\n\t\t}\n\n\t\tif IsDigit(letter) == true {\n\t\t\tt.Errorf(\"%c should return false\", letter)\n\t\t}\n\t}\n}\n\nfunc TestIsSpace(t *testing.T) {\n\ttestData := []struct {\n\t\tc  byte\n\t\tis bool\n\t}{\n\t\t{'a', false},\n\t\t{'B', false},\n\t\t{'z', false},\n\t\t{'(', false},\n\t\t{'}', false},\n\t\t{' ', true},\n\t\t{'\\r', true},\n\t\t{'\\t', true},\n\t\t{'\\n', true},\n\t}\n\tfor _, td := range testData {\n\t\tif IsSpace(td.c) != td.is {\n\t\t\tt.Errorf(\"%v isspace wrong\", rune(td.c))\n\t\t}\n\t}\n}\n\nfunc TestTrimSpace(t *testing.T) {\n\ttestData := []struct {\n\t\told    string\n\t\ttrimed string\n\t}{\n\t\t{\"hello\", \"hello\"},\n\t\t{\" hello\", \"hello\"},\n\t\t{\"  hello\\r\\n \", \"hello\"},\n\t\t{\"  hello \\t  \", \"hello\"},\n\t\t{\"\", \"\"},\n\t\t{\"\\r\\n\", \"\"},\n\t}\n\tfor _, td := range testData {\n\t\ttrimed := string(TrimSpace([]byte(td.old)))\n\t\tif trimed != td.trimed {\n\t\t\tt.Errorf(\"%s trimmed to %s, wrong\", td.old, trimed)\n\t\t}\n\t}\n}\n\nfunc TestTrimTrailingSpace(t *testing.T) {\n\ttestData := []struct {\n\t\told    string\n\t\ttrimed string\n\t}{\n\t\t{\"hello\", \"hello\"},\n\t\t{\" hello\", \" hello\"},\n\t\t{\"  hello\\r\\n \", \"  hello\"},\n\t\t{\"  hello \\t  \", \"  hello\"},\n\t\t{\"\", \"\"},\n\t\t{\"\\r\\n\", \"\"},\n\t}\n\tfor _, td := range testData {\n\t\ttrimed := string(TrimTrailingSpace([]byte(td.old)))\n\t\tif trimed != td.trimed {\n\t\t\tt.Errorf(\"%s trimmed to %s, should be %s\\n\", td.old, trimed, td.trimed)\n\t\t}\n\t}\n}\n\nfunc TestFieldsN(t *testing.T) {\n\ttestData := []struct {\n\t\traw string\n\t\tn   int\n\t\tarr []string\n\t}{\n\t\t{\"\", 2, nil}, // this should not crash\n\t\t{\"hello world\", -1, nil},\n\t\t{\"hello \\t world welcome\", 1, []string{\"hello \\t world welcome\"}},\n\t\t{\"   hello \\t world welcome \", 1, []string{\"hello \\t world welcome\"}},\n\t\t{\"hello world\", 2, []string{\"hello\", \"world\"}},\n\t\t{\"  hello\\tworld  \", 2, []string{\"hello\", \"world\"}},\n\t\t// note \\r\\n in the middle of a string will be considered as a field\n\t\t{\"  hello  world  \\r\\n\", 4, []string{\"hello\", \"world\"}},\n\t\t{\" hello \\t world welcome\\r\\n\", 2, []string{\"hello\", \"world welcome\"}},\n\t\t{\" hello \\t world welcome \\t \", 2, []string{\"hello\", \"world welcome\"}},\n\t}\n\n\tfor _, td := range testData {\n\t\tarr := FieldsN([]byte(td.raw), td.n)\n\t\tif len(arr) != len(td.arr) {\n\t\t\tt.Fatalf(\"%q want %d fields, got %d\\n\", td.raw, len(td.arr), len(arr))\n\t\t}\n\t\tfor i := 0; i < len(arr); i++ {\n\t\t\tif string(arr[i]) != td.arr[i] {\n\t\t\t\tt.Errorf(\"%q %d item, want %q, got %q\\n\", td.raw, i, td.arr[i], arr[i])\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestParseIntFromBytes(t *testing.T) {\n\terrDummy := errors.New(\"dummy error\")\n\ttestData := []struct {\n\t\traw  []byte\n\t\tbase int\n\t\terr  error\n\t\tval  int64\n\t}{\n\t\t{[]byte(\"123\"), 10, nil, 123},\n\t\t{[]byte(\"+123\"), 10, nil, 123},\n\t\t{[]byte(\"-123\"), 10, nil, -123},\n\t\t{[]byte(\"0\"), 10, nil, 0},\n\t\t{[]byte(\"a\"), 10, errDummy, 0},\n\t\t{[]byte(\"aBc\"), 16, nil, 0xabc},\n\t\t{[]byte(\"+aBc\"), 16, nil, 0xabc},\n\t\t{[]byte(\"-aBc\"), 16, nil, -0xabc},\n\t\t{[]byte(\"213e\"), 16, nil, 0x213e},\n\t\t{[]byte(\"12deadbeef\"), 16, nil, 0x12deadbeef},\n\t\t{[]byte(\"213n\"), 16, errDummy, 0},\n\t}\n\tfor _, td := range testData {\n\t\tval, err := ParseIntFromBytes(td.raw, td.base)\n\t\tif err != nil && td.err == nil {\n\t\t\tt.Errorf(\"%s base %d should NOT return error: %v\\n\", td.raw, td.base, err)\n\t\t}\n\t\tif err == nil && td.err != nil {\n\t\t\tt.Errorf(\"%s base %d should return error\\n\", td.raw, td.base)\n\t\t}\n\t\tif val != td.val {\n\t\t\tt.Errorf(\"%s base %d got wrong value: %d\\n\", td.raw, td.base, val)\n\t\t}\n\t}\n}\n\nfunc TestCopyN(t *testing.T) {\n\ttestStr := \"go is really a nice language\"\n\tfor _, step := range []int{4, 9, 17, 32} {\n\t\tsrc := bufio.NewReader(strings.NewReader(testStr))\n\t\tdst := new(bytes.Buffer)\n\n\t\terr := copyN(dst, src, len(testStr), step)\n\t\tif err != nil {\n\t\t\tt.Error(\"unexpected err:\", err)\n\t\t\tbreak\n\t\t}\n\t\tif dst.String() != testStr {\n\t\t\tt.Errorf(\"step %d want %q, got: %q\\n\", step, testStr, dst.Bytes())\n\t\t}\n\t}\n}\n\nfunc TestIsFileExists(t *testing.T) {\n\terr := isFileExists(\"testdata\")\n\tif err == nil {\n\t\tt.Error(\"should return error is path is directory\")\n\t}\n\n\terr = isFileExists(\"testdata/none\")\n\tif err == nil {\n\t\tt.Error(\"Not existing file should return error\")\n\t}\n\n\terr = isFileExists(\"testdata/file\")\n\tif err != nil {\n\t\tt.Error(\"Why error for existing file?\")\n\t}\n}\n\nfunc TestNewNbitIPv4Mask(t *testing.T) {\n\tmask := []byte(NewNbitIPv4Mask(32))\n\tfor i := 0; i < 4; i++ {\n\t\tif mask[i] != 0xff {\n\t\t\tt.Error(\"NewNbitIPv4Mask with 32 error\")\n\t\t}\n\t}\n\tmask = []byte(NewNbitIPv4Mask(5))\n\tif mask[0] != 0xf8 || mask[1] != 0 || mask[2] != 0 {\n\t\tt.Error(\"NewNbitIPv4Mask with 5 error:\", mask)\n\t}\n\tmask = []byte(NewNbitIPv4Mask(9))\n\tif mask[0] != 0xff || mask[1] != 0x80 || mask[2] != 0 {\n\t\tt.Error(\"NewNbitIPv4Mask with 9 error:\", mask)\n\t}\n\tmask = []byte(NewNbitIPv4Mask(23))\n\tif mask[0] != 0xff || mask[1] != 0xff || mask[2] != 0xfe || mask[3] != 0 {\n\t\tt.Error(\"NewNbitIPv4Mask with 23 error:\", mask)\n\t}\n\tmask = []byte(NewNbitIPv4Mask(28))\n\tif mask[0] != 0xff || mask[1] != 0xff || mask[2] != 0xff || mask[3] != 0xf0 {\n\t\tt.Error(\"NewNbitIPv4Mask with 28 error:\", mask)\n\t}\n}\n\nfunc TestHost2Domain(t *testing.T) {\n\tvar testData = []struct {\n\t\thost   string\n\t\tdomain string\n\t}{\n\t\t{\"www.google.com\", \"google.com\"},\n\t\t{\"google.com\", \"google.com\"},\n\t\t{\"com.cn\", \"com.cn\"},\n\t\t{\"sina.com.cn\", \"sina.com.cn\"},\n\t\t{\"www.bbc.co.uk\", \"bbc.co.uk\"},\n\t\t{\"apple.com.cn\", \"apple.com.cn\"},\n\t\t{\"simplehost\", \"\"},\n\t\t{\"192.168.1.1\", \"\"},\n\t\t{\"10.2.1.1\", \"\"},\n\t\t{\"123.45.67.89\", \"123.45.67.89\"},\n\t\t{\"172.65.43.21\", \"172.65.43.21\"},\n\t}\n\n\tfor _, td := range testData {\n\t\tdm := host2Domain(td.host)\n\t\tif dm != td.domain {\n\t\t\tt.Errorf(\"%s got domain %v should be %v\", td.host, dm, td.domain)\n\t\t}\n\t}\n}\n\nfunc TestHostIsIP(t *testing.T) {\n\tvar testData = []struct {\n\t\thost  string\n\t\tisIP  bool\n\t\tisPri bool\n\t}{\n\t\t{\"127.0.0.1\", true, true},\n\t\t{\"127.2.1.1\", true, true},\n\t\t{\"192.168.1.1\", true, true},\n\t\t{\"10.2.3.4\", true, true},\n\t\t{\"172.16.5.3\", true, true},\n\t\t{\"172.20.5.3\", true, true},\n\t\t{\"172.31.5.3\", true, true},\n\t\t{\"172.15.1.1\", true, false},\n\t\t{\"123.45.67.89\", true, false},\n\t\t{\"foo.com\", false, false},\n\t\t{\"www.foo.com\", false, false},\n\t\t{\"www.bar.foo.com\", false, false},\n\t}\n\n\tfor _, td := range testData {\n\t\tisIP, isPri := hostIsIP(td.host)\n\t\tif isIP != td.isIP {\n\t\t\tif td.isIP {\n\t\t\t\tt.Error(td.host, \"is IP address\")\n\t\t\t} else {\n\t\t\t\tt.Error(td.host, \"is NOT IP address\")\n\t\t\t}\n\t\t}\n\t\tif isPri != td.isPri {\n\t\t\tif td.isPri {\n\t\t\t\tt.Error(td.host, \"is private IP address\")\n\t\t\t} else {\n\t\t\t\tt.Error(td.host, \"is NOT private IP address\")\n\t\t\t}\n\t\t}\n\t}\n}\n"
  }
]