Repository: hidu/pproxy Branch: master Commit: cc3978cfd1b7 Files: 59 Total size: 466.2 KB Directory structure: gitextract_gqzuybd4/ ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README-en.md ├── README.md ├── assest.json ├── build.sh ├── fmt.sh ├── go.mod ├── go.sum ├── main.go ├── res/ │ ├── conf/ │ │ └── demo.conf │ ├── css/ │ │ ├── flat.css │ │ └── style.css │ ├── js/ │ │ ├── base64.js │ │ ├── default.js │ │ ├── jquery.js │ │ ├── session.js │ │ └── socket.io.js │ ├── private/ │ │ ├── client_cert.pem │ │ └── server_key.pem │ ├── sjs/ │ │ └── req_rewrite.js │ ├── tpl/ │ │ ├── about.html │ │ ├── config/ │ │ │ ├── req_demo.html │ │ │ └── req_form.html │ │ ├── config.html │ │ ├── error.html │ │ ├── file.html │ │ ├── file_edit.html │ │ ├── file_new.html │ │ ├── layout.html │ │ ├── login.html │ │ ├── network.html │ │ ├── replay.html │ │ ├── replay_direct.html │ │ └── useage.html │ └── version ├── script/ │ ├── create_dest_zip.sh │ ├── pproxy_control.sh │ └── windows_run.bat └── serve/ ├── assest.go ├── auth.go ├── broadcast.go ├── certs.go ├── config.go ├── init.go ├── kvStore.go ├── proxy.go ├── reqCtx.go ├── req_modifer.go ├── req_replay.go ├── req_rewrite.go ├── serve.go ├── sessions.go ├── util.go ├── web.go ├── web_file.go ├── wsClient.go └── wsServer.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go test/ *.exe *.test data/ dest/ hosts_* conf/* file/ .*.html .DS_Store ================================================ FILE: CHANGELOG.md ================================================ 2016-07-27 1. 升级依赖 2016-07-27 1. version 0.5.2 2. 修复文件上传页面错误 3. 更新lib otto ,boltdb 为最新版本 2016-03-25 1.版本号升级为0.5.1 2.改进request动态修改引擎并发异常问题 2014-12-27 1.版本号升级为0.4.7 2.添加adminPort配置项,以独立的端口提供给管理界面 2014-12-17 1.静态资源使用goassest方式而不使用之前的读取zip的方式 2014-11-08 1.重构代理处理逻辑,改进Upgrade代理协议 2.会话详情页面展现请求时间 3.upgrade结束的时候也记录一条response 以方便查看何时断开连接 2014-09-27 1.http session list support local filter 2014-09-14 1.downgrade the socket.io lib 2014-08-14 1.emit data with base64encode 2.fix some url has no schema 2014-08-10 1.websocket proxy support 2014-08-06 1.update socket.io 2014-07-19 1.修复监听端口为80时不能查看会话列表的问题 2.完善帮助说明 2014-07-15 1.get和post参数支持重写 2.重写请求出现错误自己返回502错误 2014-07-12 1.认证机制升级,新认证机制:一个ip第一次访问的时候会要求登录,若没有输入登录信息也跳过。 2.管理员用户(登录后)在session filter 输入user:any 可以查看到所有的会话信息 ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2014 du Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README-en.md ================================================ pproxy 0.5.2 ====== HTTP protocol analysis tool. features:
1.url redirect
   redirect *http://www.baidu.com/s?wd=pproxy* to  *http://m.baidu.com/s?wd=pproxy*
   redirect  *ws://www.test.com/a* to  *ws://www.example.com/b*
   
2.form dynamic modification  
   get、post and header all can modify  
   
3.hosts
  www.baidu.com to 127.0.0.1  
  or www.baidu.com:81 to 192.168.1.2:8080 ,and only  takes effect on port 81  
  
4.view request and response detail
   form params,header and all response and easy to share
   
5.auth sup
   http Basic or only try basic auth at first request
   
6.replay
   can modify the get、post、header params and replay the request

7.parent proxy
  
use javascript code as config to modify the request params: ``` if(req.host=="www.baidu.com"){ req.host="www.163.com" req.host_addr="127.0.0.0:81" // send req to 127.0.0.1:81 } ``` or: ``` if(req.host.indexOf("baidu.com")>-1){ req.host_addr="127.0.0.0:81" } ``` request params dump: ``` #url : http://www.example.com/album/list?cid=126 #request has these attrs: schema : http host : www.example.com port : 80 path : /album/list get: {cid:[123]} post: {} username : password : method: GET form_get : {add:function(k,v){},set:function(k,v){},get:function(k){},len:function(){}} form_post : {add:function(k,v){},set:function(k,v){},get:function(k){},len:function(){}} host_addr: #modify hosts eg:127.0.0.1:3218 #note get and post value is array #form_get: helper function for get params #form_post: helper function for post params ``` hosts config demo: ``` www.baidu.com 127.0.0.1 www.baidu.com:81 10.0.2.2:8080 ``` disable req_rewrite.js first line ```//ignore``` req_rewrite.js支持不同用户设置不同的规则。默认使用当前验证使用用户名的规则,若无则使用默认的。 configs: ``` conf/ ├── pproxy.conf #server config ├── hosts_8080 #hosts for 8080 ├── req_rewrite_8080.js #8080端口server的url重写规则 ├── hosts_8081 ├── req_rewrite_8081.js └── users #全局帐号配置文件 ``` users配置: ``` #帐号 admin,密码 是 psw,是管理员帐号 name:admin psw:psw is_admin:admin #密码也可以存储为md5值,使用 psw_md5:32位md密码 name:admin_sec psw_md5:7bb483729b5a8e26f73e1831cde5b842 is_admin:admin ``` 可以在线修改配置时必须使用管理员帐号登录 配置文件示例: ``` port : 8080 title : demo notice :notice notice #数据存放目录,相对于当前配置的路径 dataDir : ../data/ #数据存放天数,0为永久存储(目前只在重启的时候会进行数据清理) dataStoreDay : 15 #代理服务认证方式 #options:{none : 无认证,basic:http basic ,basic_try:尝试httpBasic认证 ,basic_any:任意帐号} authType : none #那些request和response数据进行存储 #options:{ all : 所有 only_broadcast : 发送到session list的才存储} responseSave : only_broadcast #session列表查看数据 # options :{ all:所有人可见 ip_or_user : 输入正确的ip或者user后可见} sessionView : all #父级代理 #eg http://10.10.2.2:3128 or http://name:psw@10.10.2.2:3128 # http://pass:pass@10.10.2.2:3128 the user and psw will pass through to the parent proxy parentProxy: ``` ================================================ FILE: README.md ================================================ pproxy 0.5.2 ====== ## intro HTTP 协议抓包代理程序, HTTP 协议调试工具。 采用 Go 编写,采用 BS 模式(s-代理程序,b-会话查看、配置管理等功能) 0.4.2版本已经支持websocket代理,以及重定向(和普通http请求一样使用) 0.5 版本是对底层存储进行了替换,并且尝试支持https抓包 ## install 已经安装go的用户直接安装: >go install github.com/hidu/pproxy@master ## 功能特性
1.url重定向
   如把 http://www.baidu.com/s?wd=pproxy 修改为 http://m.baidu.com/s?wd=pproxy
   或者把 ws://www.test.com/a 重定向到 ws://www.example.com/b
   
2.form表单动态修改  
   get、post可以动态修改(增删改)  
   
3.hosts文件支持
  相当于 修改host或者dns 如  
  将www.baidu.com 请求全部发往127.0.0.1  
  将www.baidu.com:81 请求全部发往192.168.1.2:8080  
  
4.可查看request 和response详情
   form表单参数,header等都可以很方便的看到
   
5.登录认证支持
   支持httpBasic认证
   
6.replay功能
   可以修改request的参数(get、post、header)

7.父级代理
  
## 配置 ### rewrite req 使用javascript来配置重定向功能,如 ``` if(req.host=="www.baidu.com"){ req.host="www.163.com" req.host_addr="127.0.0.0:81" // send req to 127.0.0.1:81 } ``` 当然也可以这样: ``` if(req.host.indexOf("baidu.com")>-1){ req.host_addr="127.0.0.0:81" } ``` ### req变量示例 ``` #url : http://www.example.com/album/list?cid=126 #req对象有如下一下属性: schema : http host : www.example.com port : 80 path : /album/list get: {cid:[123]} post: {} username : password : method: GET form_get : {add:function(k,v){},set:function(k,v){},get:function(k){},len:function(){}} form_post : {add:function(k,v){},set:function(k,v){},get:function(k){},len:function(){}} host_addr: #修改该请求的host是使用,如 127.0.0.1:3218 #注意 get 和post的值是数组,如上cid参数 #form_get 用于更方便的操作 get参数对象 #form_post 用于更方便的操作 post参数对象 ``` ### hosts 增强的hosts文件使用: ``` www.baidu.com 127.0.0.1 www.baidu.com:81 10.0.2.2:8080 ``` ### other 忽略禁用req_rewrite.js 在js文件的第一行内容写入 ```//ignore``` req_rewrite.js支持不同用户设置不同的规则。默认使用当前验证使用用户名的规则,若无则使用默认的。 ### 配置文件结构: ``` conf/ ├── pproxy.conf #server的配置 ├── hosts_8080 #8080端口server的hosts规则 ├── req_rewrite_8080.js #8080端口server的url重写规则 ├── hosts_8081 ├── req_rewrite_8081.js └── users #全局帐号配置文件 ``` ### users配置: ``` #帐号 admin,密码 是 psw,是管理员帐号 name:admin psw:psw is_admin:admin #密码也可以存储为md5值,使用 psw_md5:32位md密码 name:admin_sec psw_md5:7bb483729b5a8e26f73e1831cde5b842 is_admin:admin ``` 可以在线修改配置时必须使用管理员帐号登录 ### 配置文件示例(pproxy.conf): ``` #提供代理服务的端口 port : 8080 #管理界面的端口,为0表示和代理服务使用相同的端口,eg:8081 adminPort : 0 title : demo notice :notice notice #数据存放目录,相对于当前配置的路径 dataDir : ../data/ #数据存放天数,0为永久存储(目前只在重启的时候会进行数据清理) dataStoreDay : 15 #代理服务认证方式 #options:{none : 无认证,basic:http basic ,basic_try:尝试httpBasic认证 ,basic_any:任意帐号} authType : none #那些request和response数据进行存储 #options:{ all : 所有 only_broadcast : 发送到session list的才存储} responseSave : only_broadcast #session列表查看数据 # options :{ all:所有人可见 ip_or_user : 输入正确的ip或者user后可见} sessionView : all #父级代理 #eg http://10.10.2.2:3128 or http://name:psw@10.10.2.2:3128 # http://pass:pass@10.10.2.2:3128 the user and psw will pass through to the parent proxy parentProxy: #是否使用中间人方式对https进行抓包,若启用的话 需要客户端按照证书-/res/private/client_cert.pem #pproxy内置默认证书存放在/res/private目录中 #options:{on:启用 off:禁用} ssl : on #ssl 服务端秘钥文件地址,为空则使用默认内置的 /res/private/server_key.pem ssl_server_key: #ssl 公钥地址 ,为空则使用默认内置的 /res/private/client_cert.pem ssl_client_cert : ``` ## (管理)web查看界面 方式1: 直接访问 http://serverHost:port 方式2: 直接访问 http://serverHost:adminPort 方式3: 浏览器设置http代理 serverHost:port,访问 http://pproxy.man 或者 http://pproxy.com # 其他 ## 如何自己修改源码中的静态资源? 该项目的静态资源(res目录中的所有内容)都编译到go文件中去了,可以处理即可: 1. 安装goassest工具: ``` go get -u github.com/hidu/goassest ``` 2.到pproxy代码根目录下运行命令: ``` goassest ``` 调试过程中可以添加参数 `-assest_direct` 可以让静态资源实时生效而不需要重新编译静态资源: ``` go run proxy_main.go -assest_direct ``` ================================================ FILE: assest.json ================================================ { "src":"res", "dest":"serve/assest.go", "package":"serve" } ================================================ FILE: build.sh ================================================ #!/bin/bash # build for window: build.sh windows # default linux #./gox -build-toolchain set -e cd $(dirname $0) #export GOPATH=`readlink -f Godeps/_workspace`:$GOPATH export GO15VENDOREXPERIMENT=1 go install ================================================ FILE: fmt.sh ================================================ #/bin/bash cd $(dirname $0) cd serve #gofmt -tabs=false -w=true -tabwidth=4 . gofmt -w=true -s=true . ================================================ FILE: go.mod ================================================ module github.com/hidu/pproxy go 1.23 require ( github.com/Unknwon/goconfig v1.0.0 github.com/boltdb/bolt v1.3.1 github.com/elazarl/goproxy v0.0.0-20241219141958-0cbc93263399 github.com/googollee/go-socket.io v0.9.1 github.com/hidu/goutils v0.0.2 github.com/robertkrimen/otto v0.5.1 ) require ( github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac // indirect github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect gopkg.in/sourcemap.v1 v1.0.5 // indirect ) ================================================ FILE: go.sum ================================================ github.com/Unknwon/goconfig v1.0.0 h1:9IAu/BYbSLQi8puFjUQApZTxIHqSwrj5d8vpP8vTq4A= github.com/Unknwon/goconfig v1.0.0/go.mod h1:wngxua9XCNjvHjDiTiV26DaKDT+0c63QR6H5hjVUUxw= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/elazarl/goproxy v0.0.0-20241219141958-0cbc93263399 h1:WBf0ImRm78m/cgOOob9PUyQdYYod2luQA6fs+OqAjbw= github.com/elazarl/goproxy v0.0.0-20241219141958-0cbc93263399/go.mod h1:3tPvP6c6GrQS4u4TJEbOoqGC2wDNMLra3t6AXWwtL0M= github.com/elazarl/goproxy/ext v0.0.0-20241217120900-7711dfa3811c h1:R+i10jtNSzKJKqEZAYJnR9M8y14k0zrNHqD1xkv/A2M= github.com/elazarl/goproxy/ext v0.0.0-20241217120900-7711dfa3811c/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/googollee/go-socket.io v0.9.1 h1:KYsu63c3H5SaeQ3MDlHSTE/LJnwok2SH1M5wy4ZaYD0= github.com/googollee/go-socket.io v0.9.1/go.mod h1:Q0CvnKmaZNgDXIi85at4eLadAOS1hWDLaDATQpuH3i4= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/hidu/goutils v0.0.2 h1:ZjwXZhuWXZjzQ4dHoFo2iN6oDXH1TCudQZ0V2vdAUfQ= github.com/hidu/goutils v0.0.2/go.mod h1:m13DejGt6FVHM+taWpMHpavxBRZnnQBZeDJyB/YsyRI= github.com/howeyc/fsnotify v0.9.0/go.mod h1:41HzSPxBGeFRQKEEwgh49TRw/nKBsYZ2cF1OzPjSJsA= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/robertkrimen/otto v0.5.1 h1:avDI4ToRk8k1hppLdYFTuuzND41n37vPGJU7547dGf0= github.com/robertkrimen/otto v0.5.1/go.mod h1:bS433I4Q9p+E5pZLu7r17vP6FkE6/wLxBdmKjoqJXF8= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac h1:wbW+Bybf9pXxnCFAOWZTqkRjAc7rAIwo2e1ArUhiHxg= github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8= github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: main.go ================================================ package main import ( "flag" "fmt" "log" "os" "github.com/hidu/pproxy/serve" ) var configPath = flag.String("conf", "./conf/pproxy.conf", "pproxy's config file") var port = flag.Int("port", 0, "proxy port") var vv = flag.Bool("vv", false, "debug,log request with more detail") var showConf = flag.Bool("demo_conf", false, "show default conf") var version = flag.Bool("v", false, "show version") func init() { df := flag.Usage flag.Usage = func() { df() fmt.Fprintln(os.Stderr, "\n HTTP protocol analysis tool\n https://github.com/hidu/pproxy/\n") } } func main() { flag.Parse() if *showConf { demoConf := serve.GetDemoConf() fmt.Println(demoConf) os.Exit(0) } if *version { fmt.Println("pproxy version:", serve.GetVersion()) os.Exit(0) } log.SetFlags(log.Lshortfile | log.LstdFlags | log.Ldate) ser, err := serve.NewProxyServe(*configPath, *port) if err != nil { fmt.Println("start pproxy failed", err) os.Exit(2) } ser.Debug = *vv ser.Start() } ================================================ FILE: res/conf/demo.conf ================================================ ########################################################## # pproxy demo conf # ########################################################## #提供代理服务的端口 port : 8080 #管理界面的端口,为0表示和代理服务使用相同的端口 adminPort : 0 title : hello pproxy notice : notice notice #数据存放目录,相对于当前配置的路径 dataDir : ../data/ #静态文件存放目录 fileDir: ../file/ #数据存放天数,0为永久存储(目前只在重启的时候会进行数据清理) dataStoreDay : 15 #代理服务认证方式 #options:{none : 无认证,basic:http basic ,basic_try:尝试httpBasic认证 ,basic_any:任意帐号} authType : none #那些request和response数据进行存储 #options:{ all : 所有 only_broadcast : 发送到session list的才存储} responseSave : only_broadcast #session列表查看数据 # options :{ all:所有人可见 ip_or_user : 输入正确的ip或者user后可见} sessionView : all #eg http://10.10.2.2:3128 or http://name:psw@10.10.2.2:3128 # http://name:psw@10.10.2.2:3128 the user and psw will pass through to the parent proxy parentProxy: ================================================ FILE: res/css/flat.css ================================================ .dropdown-arrow-inverse { border-bottom-color: #34495e !important; border-top-color: #34495e !important; } a { color: #428bca; text-decoration: none; -webkit-transition: 0.25s; transition: 0.25s; } a:hover, a:focus { color: #428bca; text-decoration: none; } a:focus { outline: none; } .img-rounded { border-radius: 6px; } .img-thumbnail { padding: 4px; line-height: 1.72222; background-color: #ffffff; border: 2px solid #bdc3c7; border-radius: 6px; -webkit-transition: all 0.25s ease-in-out; transition: all 0.25s ease-in-out; display: inline-block; max-width: 100%; height: auto; } .img-comment { font-size: 15px; line-height: 1.2; font-style: italic; margin: 24px 0; } h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 { font-family: inherit; font-weight: 700; line-height: 1.1; color: inherit; } h1 small, h2 small, h3 small, h4 small, h5 small, h6 small, .h1 small, .h2 small, .h3 small, .h4 small, .h5 small, .h6 small { color: #e7e9ec; } h1, h2, h3 { margin-top: 30px; margin-bottom: 15px; } h4, h5, h6 { margin-top: 15px; margin-bottom: 15px; } h6 { font-weight: normal; } h1, .h1 { font-size: 61px; } h2, .h2 { font-size: 53px; } h3, .h3 { font-size: 40px; } h4, .h4 { font-size: 29px; } h5, .h5 { font-size: 28px; } h6, .h6 { font-size: 24px; } p { font-size: 18px; line-height: 1.72222; margin: 0 0 15px; } .lead { margin-bottom: 30px; font-size: 28px; line-height: 1.46428571; font-weight: 300; } @media (min-width: 768px) { .lead { font-size: 30.006px; } } small, .small { font-size: 83%; line-height: 2.067; } .text-muted { color: #bdc3c7; } .text-inverse { color: #ffffff; } .text-primary { color: #428bca; } a.text-primary:hover { color: #15967d; } .text-warning { color: #f1c40f; } a.text-warning:hover { color: #c19d0c; } .text-danger { color: #e74c3c; } a.text-danger:hover { color: #b93d30; } .text-success { color: #2ecc71; } a.text-success:hover { color: #25a35a; } .text-info { color: #3498db; } a.text-info:hover { color: #2a7aaf; } .bg-primary { color: #ffffff; background-color: #34495e; } a.bg-primary:hover { background-color: #222f3d; } .bg-success { background-color: #dff0d8; } a.bg-success:hover { background-color: #c1e2b3; } .bg-info { background-color: #d9edf7; } a.bg-info:hover { background-color: #afd9ee; } .bg-warning { background-color: #fcf8e3; } a.bg-warning:hover { background-color: #f7ecb5; } .bg-danger { background-color: #f2dede; } a.bg-danger:hover { background-color: #e4b9b9; } .page-header { padding-bottom: 14px; margin: 60px 0 30px; border-bottom: 1px solid #e7e9ec; } ul, ol { margin-bottom: 15px; } dl { margin-bottom: 30px; } dt, dd { line-height: 1.72222; } @media (min-width: 768px) { .dl-horizontal dt { width: 160px; } .dl-horizontal dd { margin-left: 180px; } } abbr[title], abbr[data-original-title] { border-bottom: 1px dotted #bdc3c7; } blockquote { border-left: 3px solid #e7e9ec; padding: 0 0 0 16px; margin: 0 0 30px; } blockquote p { font-size: 20px; line-height: 1.55; font-weight: normal; margin-bottom: .4em; } blockquote small, blockquote .small { font-size: 18px; line-height: 1.72222; font-style: italic; color: inherit; } blockquote small:before, blockquote .small:before { content: ""; } blockquote.pull-right { padding-right: 16px; padding-left: 0; border-right: 3px solid #e7e9ec; border-left: 0; } blockquote.pull-right small:after { content: ""; } address { margin-bottom: 30px; line-height: 1.72222; } code, kbd, pre, samp { font-family: Monaco, Menlo, Consolas, "Courier New", monospace; } code { padding: 2px 6px; font-size: 85%; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; } kbd { padding: 2px 6px; font-size: 85%; color: #ffffff; background-color: #34495e; border-radius: 4px; box-shadow: none; } pre { padding: 8px; margin: 0 0 15px; font-size: 13px; line-height: 1.72222; color: inherit; background-color: #ffffff; border: 2px solid #e7e9ec; border-radius: 6px; white-space: pre; } .pre-scrollable { max-height: 340px; } .btn { border: none; font-size: 14px; font-weight: normal; cursor: pointer; line-height: 1.4; border-radius: 4px; padding: 6px 12px; -webkit-font-smoothing: subpixel-antialiased; -webkit-transition: border .25s linear, color .25s linear, background-color .25s linear; transition: border .25s linear, color .25s linear, background-color .25s linear; } .btn:hover, .btn:focus { outline: none; color: #ffffff; } .btn:active, .btn.active { outline: none; -webkit-box-shadow: none; box-shadow: none; } .btn.disabled, .btn[disabled], fieldset[disabled] .btn { background-color: #bdc3c7; color: rgba(255, 255, 255, 0.75); opacity: 0.7; filter: alpha(opacity=70); } .btn > [class^="fui-"] { margin: 0 1px; position: relative; line-height: 1; top: 1px; } .btn-xs.btn > [class^="fui-"] { font-size: 11px; top: 0; } .btn-hg.btn > [class^="fui-"] { top: 2px; } .btn-default { color: #ffffff; background-color: #bdc3c7; } .btn-default:hover, .btn-default:focus, .btn-default:active, .btn-default.active, .open .dropdown-toggle.btn-default { color: #ffffff; background-color: #cacfd2; border-color: #cacfd2; } .btn-default:active, .btn-default.active, .open .dropdown-toggle.btn-default { background: #a1a6a9; border-color: #a1a6a9; } .btn-default.disabled, .btn-default[disabled], fieldset[disabled] .btn-default, .btn-default.disabled:hover, .btn-default[disabled]:hover, fieldset[disabled] .btn-default:hover, .btn-default.disabled:focus, .btn-default[disabled]:focus, fieldset[disabled] .btn-default:focus, .btn-default.disabled:active, .btn-default[disabled]:active, fieldset[disabled] .btn-default:active, .btn-default.disabled.active, .btn-default[disabled].active, fieldset[disabled] .btn-default.active { background-color: #bdc3c7; border-color: #bdc3c7; } .btn-primary { color: #ffffff; background-color: #428bca; } .btn-primary:hover, .btn-primary:focus, .btn-primary:active, .btn-primary.active, .open .dropdown-toggle.btn-primary { color: #ffffff; background-color: #3276b1; border-color: #3276b1; } .btn-primary:active, .btn-primary.active, .open .dropdown-toggle.btn-primary { background: #428bca; border-color: #428bca; } .btn-primary.disabled, .btn-primary[disabled], fieldset[disabled] .btn-primary, .btn-primary.disabled:hover, .btn-primary[disabled]:hover, fieldset[disabled] .btn-primary:hover, .btn-primary.disabled:focus, .btn-primary[disabled]:focus, fieldset[disabled] .btn-primary:focus, .btn-primary.disabled:active, .btn-primary[disabled]:active, fieldset[disabled] .btn-primary:active, .btn-primary.disabled.active, .btn-primary[disabled].active, fieldset[disabled] .btn-primary.active { background-color: #428bca; border-color: #428bca; } .btn-info { color: #ffffff; background-color: #3498db; } .btn-info:hover, .btn-info:focus, .btn-info:active, .btn-info.active, .open .dropdown-toggle.btn-info { color: #ffffff; background-color: #5dade2; border-color: #5dade2; } .btn-info:active, .btn-info.active, .open .dropdown-toggle.btn-info { background: #2c81ba; border-color: #2c81ba; } .btn-info.disabled, .btn-info[disabled], fieldset[disabled] .btn-info, .btn-info.disabled:hover, .btn-info[disabled]:hover, fieldset[disabled] .btn-info:hover, .btn-info.disabled:focus, .btn-info[disabled]:focus, fieldset[disabled] .btn-info:focus, .btn-info.disabled:active, .btn-info[disabled]:active, fieldset[disabled] .btn-info:active, .btn-info.disabled.active, .btn-info[disabled].active, fieldset[disabled] .btn-info.active { background-color: #3498db; border-color: #3498db; } .btn-danger { color: #ffffff; background-color: #e74c3c; } .btn-danger:hover, .btn-danger:focus, .btn-danger:active, .btn-danger.active, .open .dropdown-toggle.btn-danger { color: #ffffff; background-color: #ec7063; border-color: #ec7063; } .btn-danger:active, .btn-danger.active, .open .dropdown-toggle.btn-danger { background: #c44133; border-color: #c44133; } .btn-danger.disabled, .btn-danger[disabled], fieldset[disabled] .btn-danger, .btn-danger.disabled:hover, .btn-danger[disabled]:hover, fieldset[disabled] .btn-danger:hover, .btn-danger.disabled:focus, .btn-danger[disabled]:focus, fieldset[disabled] .btn-danger:focus, .btn-danger.disabled:active, .btn-danger[disabled]:active, fieldset[disabled] .btn-danger:active, .btn-danger.disabled.active, .btn-danger[disabled].active, fieldset[disabled] .btn-danger.active { background-color: #e74c3c; border-color: #e74c3c; } .btn-success { color: #ffffff; background-color: #2ecc71; } .btn-success:hover, .btn-success:focus, .btn-success:active, .btn-success.active, .open .dropdown-toggle.btn-success { color: #ffffff; background-color: #58d68d; border-color: #58d68d; } .btn-success:active, .btn-success.active, .open .dropdown-toggle.btn-success { background: #27ad60; border-color: #27ad60; } .btn-success.disabled, .btn-success[disabled], fieldset[disabled] .btn-success, .btn-success.disabled:hover, .btn-success[disabled]:hover, fieldset[disabled] .btn-success:hover, .btn-success.disabled:focus, .btn-success[disabled]:focus, fieldset[disabled] .btn-success:focus, .btn-success.disabled:active, .btn-success[disabled]:active, fieldset[disabled] .btn-success:active, .btn-success.disabled.active, .btn-success[disabled].active, fieldset[disabled] .btn-success.active { background-color: #2ecc71; border-color: #2ecc71; } .btn-warning { color: #ffffff; background-color: #f1c40f; } .btn-warning:hover, .btn-warning:focus, .btn-warning:active, .btn-warning.active, .open .dropdown-toggle.btn-warning { color: #ffffff; background-color: #f4d313; border-color: #f4d313; } .btn-warning:active, .btn-warning.active, .open .dropdown-toggle.btn-warning { background: #cda70d; border-color: #cda70d; } .btn-warning.disabled, .btn-warning[disabled], fieldset[disabled] .btn-warning, .btn-warning.disabled:hover, .btn-warning[disabled]:hover, fieldset[disabled] .btn-warning:hover, .btn-warning.disabled:focus, .btn-warning[disabled]:focus, fieldset[disabled] .btn-warning:focus, .btn-warning.disabled:active, .btn-warning[disabled]:active, fieldset[disabled] .btn-warning:active, .btn-warning.disabled.active, .btn-warning[disabled].active, fieldset[disabled] .btn-warning.active { background-color: #f1c40f; border-color: #f1c40f; } .btn-inverse { color: #ffffff; background-color: #34495e; } .btn-inverse:hover, .btn-inverse:focus, .btn-inverse:active, .btn-inverse.active, .open .dropdown-toggle.btn-inverse { color: #ffffff; background-color: #415b76; border-color: #415b76; } .btn-inverse:active, .btn-inverse.active, .open .dropdown-toggle.btn-inverse { background: #2c3e50; border-color: #2c3e50; } .btn-inverse.disabled, .btn-inverse[disabled], fieldset[disabled] .btn-inverse, .btn-inverse.disabled:hover, .btn-inverse[disabled]:hover, fieldset[disabled] .btn-inverse:hover, .btn-inverse.disabled:focus, .btn-inverse[disabled]:focus, fieldset[disabled] .btn-inverse:focus, .btn-inverse.disabled:active, .btn-inverse[disabled]:active, fieldset[disabled] .btn-inverse:active, .btn-inverse.disabled.active, .btn-inverse[disabled].active, fieldset[disabled] .btn-inverse.active { background-color: #34495e; border-color: #34495e; } .btn-embossed { -webkit-box-shadow: inset 0 -2px 0 rgba(0, 0, 0, 0.15); box-shadow: inset 0 -2px 0 rgba(0, 0, 0, 0.15); } .btn-embossed.active, .btn-embossed:active { -webkit-box-shadow: inset 0 2px 0 rgba(0, 0, 0, 0.15); box-shadow: inset 0 2px 0 rgba(0, 0, 0, 0.15); } .btn-wide { min-width: 140px; padding-left: 30px; padding-right: 30px; } .btn-link { color: #428bca; } .btn-link:hover, .btn-link:focus { color: #428bca; text-decoration: underline; background-color: transparent; } .btn-link[disabled]:hover, fieldset[disabled] .btn-link:hover, .btn-link[disabled]:focus, fieldset[disabled] .btn-link:focus { color: #bdc3c7; text-decoration: none; } .btn-hg { padding: 13px 20px; font-size: 22px; line-height: 1.227; border-radius: 6px; } .btn-lg { padding: 10px 19px; font-size: 17px; line-height: 1.471; border-radius: 6px; } .btn-sm { padding: 9px 13px; font-size: 13px; line-height: 1.385; border-radius: 4px; } .btn-xs { padding: 6px 9px; font-size: 12px; line-height: 1.083; border-radius: 3px; } .btn-tip { font-weight: 300; padding-left: 10px; font-size: 92%; } .btn-block { white-space: normal; } .caret { border-width: 8px 6px; border-bottom-color: #34495e; border-top-color: #34495e; border-style: solid; border-bottom-style: none; -webkit-transition: 0.25s; transition: 0.25s; -webkit-transform: scale(1.001, ); -ms-transform: scale(1.001, ); transform: scale(1.001, ); } .dropup .caret, .dropup .btn-lg .caret, .navbar-fixed-bottom .dropdown .caret { border-bottom-width: 8px; } .btn-lg .caret { border-top-width: 8px; border-right-width: 6px; border-left-width: 6px; } .select { display: inline-block; margin-bottom: 10px; } [class*="span"] > .select[class*="span"] { margin-left: 0; } .select[class*="span"] .btn { width: 100%; } .select.select-block { display: block; float: none; margin-left: 0; width: auto; } .select.select-block:before, .select.select-block:after { content: " "; /* 1 */ display: table; /* 2 */ } .select.select-block:after { clear: both; } .select.select-block .btn { width: 100%; } .select.select-block .dropdown-menu { width: 100%; } .select .btn { width: 220px; } .select .btn.btn-hg .filter-option { left: 20px; right: 40px; top: 13px; } .select .btn.btn-hg .caret { right: 20px; } .select .btn.btn-lg .filter-option { left: 18px; right: 38px; } .select .btn.btn-sm .filter-option { left: 13px; right: 33px; } .select .btn.btn-sm .caret { right: 13px; } .select .btn.btn-xs .filter-option { left: 13px; right: 33px; top: 5px; } .select .btn.btn-xs .caret { right: 13px; } .select .btn .filter-option { height: 26px; left: 13px; overflow: hidden; position: absolute; right: 33px; text-align: left; top: 10px; } .select .btn .caret { position: absolute; right: 16px; top: 50%; margin-top: -3px; } .select .btn .dropdown-toggle { border-radius: 6px; } .select .btn .dropdown-menu { min-width: 100%; } .select .btn .dropdown-menu dt { cursor: default; display: block; padding: 3px 20px; } .select .btn .dropdown-menu li:not(.disabled) > a:hover small { color: rgba(255, 255, 255, 0.004); } .select .btn .dropdown-menu li > a { min-height: 20px; } .select .btn .dropdown-menu li > a.opt { padding-left: 35px; } .select .btn .dropdown-menu li small { padding-left: .5em; } .select .btn .dropdown-menu li > dt small { font-weight: normal; } .select .btn > .disabled, .select .btn .dropdown-menu li.disabled > a { cursor: default; } .select .caret { border-bottom-color: #ffffff; border-top-color: #ffffff; } textarea { font-size: 20px; line-height: 24px; padding: 5px 11px; } input[type="search"] { -webkit-appearance: none !important; } label { font-weight: normal; font-size: 15px; line-height: 2.4; } .form-control:-moz-placeholder { color: #b2bcc5; } .form-control::-moz-placeholder { color: #b2bcc5; opacity: 1; } .form-control:-ms-input-placeholder { color: #b2bcc5; } .form-control::-webkit-input-placeholder { color: #b2bcc5; } .form-control.placeholder { color: #b2bcc5; } .form-control { border: 2px solid #bdc3c7; color: #34495e; font-family: "Lato", Helvetica, Arial, sans-serif; font-size: 15px; line-height: 1.467; padding: 8px 12px; height: 42px; -webkit-appearance: none; border-radius: 6px; -webkit-box-shadow: none; box-shadow: none; -webkit-transition: border .25s linear, color .25s linear, background-color .25s linear; transition: border .25s linear, color .25s linear, background-color .25s linear; } .form-group.focus .form-control, .form-control:focus { border-color: #428bca; outline: 0; -webkit-box-shadow: none; box-shadow: none; } .form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control { background-color: #f4f6f6; border-color: #d5dbdb; color: #d5dbdb; cursor: default; opacity: 0.7; filter: alpha(opacity=70); } .form-control.flat { border-color: transparent; } .form-control.flat:hover { border-color: #bdc3c7; } .form-control.flat:focus { border-color: #428bca; } .input-sm { height: 35px; padding: 6px 10px; font-size: 13px; line-height: 1.462; border-radius: 6px; } select.input-sm { height: 35px; line-height: 35px; } textarea.input-sm, select[multiple].input-sm { height: auto; } .input-lg { height: 45px; padding: 10px 15px; font-size: 17px; line-height: 1.235; border-radius: 6px; } select.input-lg { height: 45px; line-height: 45px; } textarea.input-lg, select[multiple].input-lg { height: auto; } .input-hg { height: 53px; padding: 10px 16px; font-size: 22px; line-height: 1.318; border-radius: 6px; } select.input-hg { height: 53px; line-height: 53px; } textarea.input-hg, select[multiple].input-hg { height: auto; } .has-warning .help-block, .has-warning .control-label, .has-warning .radio, .has-warning .checkbox, .has-warning .radio-inline, .has-warning .checkbox-inline { color: #f1c40f; } .has-warning .form-control { color: #f1c40f; border-color: #f1c40f; -webkit-box-shadow: none; box-shadow: none; } .has-warning .form-control:-moz-placeholder { color: #f1c40f; } .has-warning .form-control::-moz-placeholder { color: #f1c40f; opacity: 1; } .has-warning .form-control:-ms-input-placeholder { color: #f1c40f; } .has-warning .form-control::-webkit-input-placeholder { color: #f1c40f; } .has-warning .form-control.placeholder { color: #f1c40f; } .has-warning .form-control:focus { border-color: #f1c40f; -webkit-box-shadow: none; box-shadow: none; } .has-warning .input-group-addon { color: #f1c40f; border-color: #f1c40f; background-color: #ffffff; } .has-error .help-block, .has-error .control-label, .has-error .radio, .has-error .checkbox, .has-error .radio-inline, .has-error .checkbox-inline { color: #e74c3c; } .has-error .form-control { color: #e74c3c; border-color: #e74c3c; -webkit-box-shadow: none; box-shadow: none; } .has-error .form-control:-moz-placeholder { color: #e74c3c; } .has-error .form-control::-moz-placeholder { color: #e74c3c; opacity: 1; } .has-error .form-control:-ms-input-placeholder { color: #e74c3c; } .has-error .form-control::-webkit-input-placeholder { color: #e74c3c; } .has-error .form-control.placeholder { color: #e74c3c; } .has-error .form-control:focus { border-color: #e74c3c; -webkit-box-shadow: none; box-shadow: none; } .has-error .input-group-addon { color: #e74c3c; border-color: #e74c3c; background-color: #ffffff; } .has-success .help-block, .has-success .control-label, .has-success .radio, .has-success .checkbox, .has-success .radio-inline, .has-success .checkbox-inline { color: #2ecc71; } .has-success .form-control { color: #2ecc71; border-color: #2ecc71; -webkit-box-shadow: none; box-shadow: none; } .has-success .form-control:-moz-placeholder { color: #2ecc71; } .has-success .form-control::-moz-placeholder { color: #2ecc71; opacity: 1; } .has-success .form-control:-ms-input-placeholder { color: #2ecc71; } .has-success .form-control::-webkit-input-placeholder { color: #2ecc71; } .has-success .form-control.placeholder { color: #2ecc71; } .has-success .form-control:focus { border-color: #2ecc71; -webkit-box-shadow: none; box-shadow: none; } .has-success .input-group-addon { color: #2ecc71; border-color: #2ecc71; background-color: #ffffff; } .help-block { font-size: 15px; margin-bottom: 5px; color: inherit; } .form-group { position: relative; margin-bottom: 20px; } .form-horizontal .control-label, .form-horizontal .radio, .form-horizontal .checkbox, .form-horizontal .radio-inline, .form-horizontal .checkbox-inline { margin-top: 0; margin-bottom: 0; padding-top: 6px; } .form-horizontal .form-group { margin-left: -15px; margin-right: -15px; } .form-horizontal .form-group:before, .form-horizontal .form-group:after { content: " "; /* 1 */ display: table; /* 2 */ } .form-horizontal .form-group:after { clear: both; } .form-horizontal .form-control-static { padding-top: 6px; } .form-group { position: relative; } .form-control + .input-icon { position: absolute; top: 2px; right: 2px; line-height: 37px; vertical-align: middle; font-size: 20px; color: #b2bcc5; background-color: #ffffff; padding: 0 12px 0 0; border-radius: 6px; } .input-hg + .input-icon { line-height: 49px; padding: 0 16px 0 0; } .input-lg + .input-icon { line-height: 41px; padding: 0 15px 0 0; } .input-sm + .input-icon { font-size: 18px; line-height: 30px; padding: 0 10px 0 0; } .has-success .input-icon { color: #2ecc71; } .has-warning .input-icon { color: #f1c40f; } .has-error .input-icon { color: #e74c3c; } .form-control[disabled] + .input-icon, .form-control[readonly] + .input-icon, fieldset[disabled] .form-control + .input-icon, .form-control.disabled + .input-icon { color: #d5dbdb; background-color: transparent; opacity: 0.7; filter: alpha(opacity=70); } .input-group-hg > .form-control, .input-group-hg > .input-group-addon, .input-group-hg > .input-group-btn > .btn { height: 53px; padding: 10px 16px; font-size: 22px; line-height: 1.318; border-radius: 6px; } select.input-group-hg > .form-control, select.input-group-hg > .input-group-addon, select.input-group-hg > .input-group-btn > .btn { height: 53px; line-height: 53px; } textarea.input-group-hg > .form-control, textarea.input-group-hg > .input-group-addon, textarea.input-group-hg > .input-group-btn > .btn, select[multiple].input-group-hg > .form-control, select[multiple].input-group-hg > .input-group-addon, select[multiple].input-group-hg > .input-group-btn > .btn { height: auto; } .input-group-lg > .form-control, .input-group-lg > .input-group-addon, .input-group-lg > .input-group-btn > .btn { height: 45px; padding: 10px 15px; font-size: 17px; line-height: 1.235; border-radius: 6px; } select.input-group-lg > .form-control, select.input-group-lg > .input-group-addon, select.input-group-lg > .input-group-btn > .btn { height: 45px; line-height: 45px; } textarea.input-group-lg > .form-control, textarea.input-group-lg > .input-group-addon, textarea.input-group-lg > .input-group-btn > .btn, select[multiple].input-group-lg > .form-control, select[multiple].input-group-lg > .input-group-addon, select[multiple].input-group-lg > .input-group-btn > .btn { height: auto; } .input-group-sm > .form-control, .input-group-sm > .input-group-addon, .input-group-sm > .input-group-btn > .btn { height: 35px; padding: 6px 10px; font-size: 13px; line-height: 1.462; border-radius: 6px; } select.input-group-sm > .form-control, select.input-group-sm > .input-group-addon, select.input-group-sm > .input-group-btn > .btn { height: 35px; line-height: 35px; } textarea.input-group-sm > .form-control, textarea.input-group-sm > .input-group-addon, textarea.input-group-sm > .input-group-btn > .btn, select[multiple].input-group-sm > .form-control, select[multiple].input-group-sm > .input-group-addon, select[multiple].input-group-sm > .input-group-btn > .btn { height: auto; } .input-group-addon { padding: 10px 12px; font-size: 15px; color: #ffffff; text-align: center; background-color: #bdc3c7; border: 1px solid #bdc3c7; border-radius: 6px; -webkit-transition: border .25s linear, color .25s linear, background-color .25s linear; transition: border .25s linear, color .25s linear, background-color .25s linear; } .input-group-hg .input-group-addon, .input-group-lg .input-group-addon, .input-group-sm .input-group-addon { line-height: 1; } .input-group .form-control:first-child, .input-group-addon:first-child, .input-group-btn:first-child > .btn, .input-group-btn:first-child > .dropdown-toggle, .input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) { border-bottom-right-radius: 0; border-top-right-radius: 0; } .input-group .form-control:last-child, .input-group-addon:last-child, .input-group-btn:last-child > .btn, .input-group-btn:last-child > .dropdown-toggle, .input-group-btn:first-child > .btn:not(:first-child) { border-bottom-left-radius: 0; border-top-left-radius: 0; } .form-group.focus .input-group-addon, .input-group.focus .input-group-addon { background-color: #428bca; border-color: #428bca; } .form-group.focus .input-group-btn > .btn-default + .btn-default, .input-group.focus .input-group-btn > .btn-default + .btn-default { border-left-color: #428bca; } .form-group.focus .input-group-btn .btn, .input-group.focus .input-group-btn .btn { border-color: #428bca; background-color: #ffffff; color: #428bca; } .form-group.focus .input-group-btn .btn-default, .input-group.focus .input-group-btn .btn-default { color: #ffffff; background-color: #428bca; } .form-group.focus .input-group-btn .btn-default:hover, .input-group.focus .input-group-btn .btn-default:hover, .form-group.focus .input-group-btn .btn-default:focus, .input-group.focus .input-group-btn .btn-default:focus, .form-group.focus .input-group-btn .btn-default:active, .input-group.focus .input-group-btn .btn-default:active, .form-group.focus .input-group-btn .btn-default.active, .input-group.focus .input-group-btn .btn-default.active, .open .dropdown-toggle.form-group.focus .input-group-btn .btn-default, .open .dropdown-toggle.input-group.focus .input-group-btn .btn-default { color: #ffffff; background-color: #3276b1; border-color: #3276b1; } .form-group.focus .input-group-btn .btn-default:active, .input-group.focus .input-group-btn .btn-default:active, .form-group.focus .input-group-btn .btn-default.active, .input-group.focus .input-group-btn .btn-default.active, .open .dropdown-toggle.form-group.focus .input-group-btn .btn-default, .open .dropdown-toggle.input-group.focus .input-group-btn .btn-default { background: #428bca; border-color: #428bca; } .form-group.focus .input-group-btn .btn-default.disabled, .input-group.focus .input-group-btn .btn-default.disabled, .form-group.focus .input-group-btn .btn-default[disabled], .input-group.focus .input-group-btn .btn-default[disabled], fieldset[disabled] .form-group.focus .input-group-btn .btn-default, fieldset[disabled] .input-group.focus .input-group-btn .btn-default, .form-group.focus .input-group-btn .btn-default.disabled:hover, .input-group.focus .input-group-btn .btn-default.disabled:hover, .form-group.focus .input-group-btn .btn-default[disabled]:hover, .input-group.focus .input-group-btn .btn-default[disabled]:hover, fieldset[disabled] .form-group.focus .input-group-btn .btn-default:hover, fieldset[disabled] .input-group.focus .input-group-btn .btn-default:hover, .form-group.focus .input-group-btn .btn-default.disabled:focus, .input-group.focus .input-group-btn .btn-default.disabled:focus, .form-group.focus .input-group-btn .btn-default[disabled]:focus, .input-group.focus .input-group-btn .btn-default[disabled]:focus, fieldset[disabled] .form-group.focus .input-group-btn .btn-default:focus, fieldset[disabled] .input-group.focus .input-group-btn .btn-default:focus, .form-group.focus .input-group-btn .btn-default.disabled:active, .input-group.focus .input-group-btn .btn-default.disabled:active, .form-group.focus .input-group-btn .btn-default[disabled]:active, .input-group.focus .input-group-btn .btn-default[disabled]:active, fieldset[disabled] .form-group.focus .input-group-btn .btn-default:active, fieldset[disabled] .input-group.focus .input-group-btn .btn-default:active, .form-group.focus .input-group-btn .btn-default.disabled.active, .input-group.focus .input-group-btn .btn-default.disabled.active, .form-group.focus .input-group-btn .btn-default[disabled].active, .input-group.focus .input-group-btn .btn-default[disabled].active, fieldset[disabled] .form-group.focus .input-group-btn .btn-default.active, fieldset[disabled] .input-group.focus .input-group-btn .btn-default.active { background-color: #428bca; border-color: #428bca; } .input-group-btn .btn { background-color: #ffffff; border: 2px solid #bdc3c7; color: #bdc3c7; line-height: 18px; } .input-group-btn .btn-default { color: #ffffff; background-color: #bdc3c7; } .input-group-btn .btn-default:hover, .input-group-btn .btn-default:focus, .input-group-btn .btn-default:active, .input-group-btn .btn-default.active, .open .dropdown-toggle.input-group-btn .btn-default { color: #ffffff; background-color: #cacfd2; border-color: #cacfd2; } .input-group-btn .btn-default:active, .input-group-btn .btn-default.active, .open .dropdown-toggle.input-group-btn .btn-default { background: #a1a6a9; border-color: #a1a6a9; } .input-group-btn .btn-default.disabled, .input-group-btn .btn-default[disabled], fieldset[disabled] .input-group-btn .btn-default, .input-group-btn .btn-default.disabled:hover, .input-group-btn .btn-default[disabled]:hover, fieldset[disabled] .input-group-btn .btn-default:hover, .input-group-btn .btn-default.disabled:focus, .input-group-btn .btn-default[disabled]:focus, fieldset[disabled] .input-group-btn .btn-default:focus, .input-group-btn .btn-default.disabled:active, .input-group-btn .btn-default[disabled]:active, fieldset[disabled] .input-group-btn .btn-default:active, .input-group-btn .btn-default.disabled.active, .input-group-btn .btn-default[disabled].active, fieldset[disabled] .input-group-btn .btn-default.active { background-color: #bdc3c7; border-color: #bdc3c7; } .input-group-hg .input-group-btn .btn { line-height: 31px; } .input-group-lg .input-group-btn .btn { line-height: 21px; } .input-group-sm .input-group-btn .btn { line-height: 19px; } .input-group-btn:first-child > .btn { border-right-width: 0; margin-right: -2px; } .input-group-btn:last-child > .btn { border-left-width: 0; margin-left: -2px; } .input-group-btn > .btn-default + .btn-default { border-left: 2px solid #bdc3c7; } .input-group-btn > .btn:first-child + .btn .caret { margin-left: 0; } .input-group-rounded .input-group-btn + .form-control, .input-group-rounded .input-group-btn:last-child .btn { border-bottom-right-radius: 20px; border-top-right-radius: 20px; } .input-group-hg.input-group-rounded .input-group-btn + .form-control, .input-group-hg.input-group-rounded .input-group-btn:last-child .btn { border-bottom-right-radius: 27px; border-top-right-radius: 27px; } .input-group-lg.input-group-rounded .input-group-btn + .form-control, .input-group-lg.input-group-rounded .input-group-btn:last-child .btn { border-bottom-right-radius: 25px; border-top-right-radius: 25px; } .input-group-rounded .form-control:first-child, .input-group-rounded .input-group-btn:first-child .btn { border-bottom-left-radius: 20px; border-top-left-radius: 20px; } .input-group-hg.input-group-rounded .form-control:first-child, .input-group-hg.input-group-rounded .input-group-btn:first-child .btn { border-bottom-left-radius: 27px; border-top-left-radius: 27px; } .input-group-lg.input-group-rounded .form-control:first-child, .input-group-lg.input-group-rounded .input-group-btn:first-child .btn { border-bottom-left-radius: 25px; border-top-left-radius: 25px; } .input-group-rounded .input-group-btn + .form-control { padding-left: 0; } .checkbox, .radio { margin-bottom: 12px; padding-left: 32px; position: relative; -webkit-transition: color 0.25s linear; transition: color 0.25s linear; font-size: 14px; line-height: 1.5; } .checkbox input, .radio input { outline: none !important; display: none; } .checkbox .icons, .radio .icons { color: #bdc3c7; display: block; height: 20px; left: 0; position: absolute; top: 0; width: 20px; text-align: center; line-height: 21px; font-size: 20px; cursor: pointer; -webkit-transition: color 0.25s linear; transition: color 0.25s linear; } .checkbox .icons .first-icon, .radio .icons .first-icon, .checkbox .icons .second-icon, .radio .icons .second-icon { display: inline-table; position: absolute; left: 0; top: 0; background-color: transparent; margin: 0; opacity: 1; filter: alpha(opacity=100); } .checkbox .icons .second-icon, .radio .icons .second-icon { opacity: 0; filter: alpha(opacity=0); } .checkbox:hover, .radio:hover { -webkit-transition: color 0.25s linear; transition: color 0.25s linear; } .checkbox:hover .first-icon, .radio:hover .first-icon { opacity: 0; filter: alpha(opacity=0); } .checkbox:hover .second-icon, .radio:hover .second-icon { opacity: 1; filter: alpha(opacity=100); } .checkbox.checked, .radio.checked { color: #428bca; } .checkbox.checked .first-icon, .radio.checked .first-icon { opacity: 0; filter: alpha(opacity=0); } .checkbox.checked .second-icon, .radio.checked .second-icon { opacity: 1; filter: alpha(opacity=100); color: #428bca; -webkit-transition: color 0.25s linear; transition: color 0.25s linear; } .checkbox.disabled, .radio.disabled { cursor: default; color: #e6e8ea; } .checkbox.disabled .icons, .radio.disabled .icons { color: #e6e8ea; } .checkbox.disabled .first-icon, .radio.disabled .first-icon { opacity: 1; filter: alpha(opacity=100); } .checkbox.disabled .second-icon, .radio.disabled .second-icon { opacity: 0; filter: alpha(opacity=0); } .checkbox.disabled.checked .icons, .radio.disabled.checked .icons { color: #e6e8ea; } .checkbox.disabled.checked .first-icon, .radio.disabled.checked .first-icon { opacity: 0; filter: alpha(opacity=0); } .checkbox.disabled.checked .second-icon, .radio.disabled.checked .second-icon { opacity: 1; filter: alpha(opacity=100); color: #e6e8ea; } .checkbox.primary .icons, .radio.primary .icons { color: #34495e; } .checkbox.primary.checked, .radio.primary.checked { color: #428bca; } .checkbox.primary.checked .icons, .radio.primary.checked .icons { color: #428bca; } .checkbox.primary.disabled, .radio.primary.disabled { cursor: default; color: #bdc3c7; } .checkbox.primary.disabled .icons, .radio.primary.disabled .icons { color: #bdc3c7; } .checkbox.primary.disabled.checked .icons, .radio.primary.disabled.checked .icons { color: #bdc3c7; } .radio + .radio, .checkbox + .checkbox { margin-top: 10px; } .tooltip { font-size: 14px; line-height: 1.286; } .tooltip.in { opacity: 1; } .tooltip.top { padding-bottom: 9px; } .tooltip.top .tooltip-arrow { border-top-color: #34495e; border-width: 9px 9px 0; bottom: 0; margin-left: -9px; } .tooltip.right .tooltip-arrow { border-right-color: #34495e; border-width: 9px 9px 9px 0; margin-top: -9px; left: -3px; } .tooltip.bottom { padding-top: 8px; } .tooltip.bottom .tooltip-arrow { border-bottom-color: #34495e; border-width: 0 9px 9px; margin-left: -9px; top: -1px; } .tooltip.left .tooltip-arrow { border-left-color: #34495e; border-width: 9px 0 9px 9px; margin-top: -9px; right: -3px; } .tooltip-inner { background-color: #34495e; line-height: 1.286; padding: 12px 12px; text-align: center; width: 183px; border-radius: 6px; } @media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (-moz-min-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 3/2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (-moz-min-device-pixel-ratio: 1.5), only screen and (min-device-pixel-ratio: 1.5), only screen and (min-device-pixel-ratio: 2) { .login { background-image: url(../images/login/imac-2x.png); } } ================================================ FILE: res/css/style.css ================================================ @CHARSET "UTF-8"; .clear { clear: both; } html,body{margin: 0;padding: 0} #nav { filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=90); opacity: 0.9; background: #FFF; padding: 0; margin:0; background: #446CB3; color:white; } #menu{ width: 80%; margin: 0 auto } #nav ul { margin: 0px auto; padding: 0px; list-style-type: none; } #nav ul li { float: left; padding: 10px 10px; font-size: 14px; border-right: 1px solid rgba(0, 0, 0, 0.1); box-shadow: 1px 0 0 rgba(255, 255, 255, 0.11); font-weight: bold; text-align: center; } .border_first{ border-left: 1px solid rgba(0, 0, 0, 0.1); box-shadow: 1px 0 0 rgba(255, 255, 255, 0.11); } #nav ul#left_submenu li:HOVER{ background: white; } #nav ul#left_submenu li:HOVER a{ color:#446CB3 } #nav ul li a{display: inline-block;width:50px; color:white;} #network-tb td{ vertical-align: top } #aside { width:33%; min-width: 450px; } .w120{width: 120px} .w140{width: 140px} .w150{width: 150px} #connect_status{padding:6px;color: white} table {border-collapse: collapse;border-spacing: 0;} .tb_1{border:1px solid #cccccc;table-layout:fixed;word-break:break-all;width: 100%;background:#ffffff;margin-bottom:5px} .tb_1 caption{text-align: center;background: #F0F4F6;font-weight: bold;padding-top: 5px;height: 25px;border:1px solid #cccccc;border-bottom:none} .tb_1 a{margin:0 3px 0 3px} .tb_1 tr th,.tb_1 tr td{padding: 3px;border:1px solid #cccccc;line-height:20px} .tb_1 thead tr th{font-weight:bold;text-align: center;background:#e3eaee} .tb_1 tbody tr th{text-align: right;background:#f0f4f6;padding-right:5px} .tb_1 tfoot{color:#cccccc} .td_c td{text-align: center} .td_r td{text-align: right} .t_c{text-align: center !important;} .t_r{text-align: right !important;} .t_l{text-align: left !important;} .panel_1{background: #e5e2eb;padding:4px} #network_filter_form fieldset{padding-right:3px} #tb_network{font-size: 11px;margin-top: -8px;} #tb_network td{overflow: auto;cursor: pointer;} #right_content{margin-top:5px} #right_content table th,#right_content table td{font-size:11px;vertical-align: top} #right_content pre{border:none} #div_tb_network_list {overflow:auto;max-height: 1000px;min-height:450px} tr.selected{background: #ccffee;} .right{float: right} .left{float:left} .c{clear: both} .hide{display:none} #network_list_div tbody th{text-align: right;} .td_ul{list-style: none inside;padding: 0;margin: 0} .td_ul li{list-style: none inside;} td.td_has_sub{padding: 0 !important;} td.td_has_sub .tb_1{margin: 0;border: 0;} #bd0{margin:0 auto;width:80%} .form-control{height: 26px;padding:0} fieldset { border-radius:10px; border: 1px solid gray } tr.replay{ color:blue; } .res_td_body{ max-height: 120px; overflow: scroll; } .div_b_i{ display: inline-block; } .login-screen { min-height: 400px; width:300px; margin: 0 auto; text-align: center; } #bd{ margin: 0 auto; width:80%; padding-top: 30px; } .form_0 table{width: 100%} .form_0 th{text-align: right;width: 180px;vertical-align:top;} .form_0 input[type=text]{width: 100%;margin-bottom:2px} .form_0 td.last{width: 90px;padding-left: 10px} .oneline{height: 20px;overflow: hidden;} img {margin-bottom: -3px} ================================================ FILE: res/js/base64.js ================================================ /** * * Base64 encode / decode * http://www.webtoolkit.info/ * **/ var Base64 = { // private property _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", // public method for encoding encode : function (input) { var output = ""; var chr1, chr2, chr3, enc1, enc2, enc3, enc4; var i = 0; input = Base64._utf8_encode(input); while (i < input.length) { chr1 = input.charCodeAt(i++); chr2 = input.charCodeAt(i++); chr3 = input.charCodeAt(i++); enc1 = chr1 >> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63; if (isNaN(chr2)) { enc3 = enc4 = 64; } else if (isNaN(chr3)) { enc4 = 64; } output = output + this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4); } return output; }, // public method for decoding decode : function (input) { var output = ""; var chr1, chr2, chr3; var enc1, enc2, enc3, enc4; var i = 0; input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); while (i < input.length) { enc1 = this._keyStr.indexOf(input.charAt(i++)); enc2 = this._keyStr.indexOf(input.charAt(i++)); enc3 = this._keyStr.indexOf(input.charAt(i++)); enc4 = this._keyStr.indexOf(input.charAt(i++)); chr1 = (enc1 << 2) | (enc2 >> 4); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); chr3 = ((enc3 & 3) << 6) | enc4; output = output + String.fromCharCode(chr1); if (enc3 != 64) { output = output + String.fromCharCode(chr2); } if (enc4 != 64) { output = output + String.fromCharCode(chr3); } } output = Base64._utf8_decode(output); return output; }, // private method for UTF-8 encoding _utf8_encode : function (string) { string = string.replace(/\r\n/g,"\n"); var utftext = ""; for (var n = 0; n < string.length; n++) { var c = string.charCodeAt(n); if (c < 128) { utftext += String.fromCharCode(c); } else if((c > 127) && (c < 2048)) { utftext += String.fromCharCode((c >> 6) | 192); utftext += String.fromCharCode((c & 63) | 128); } else { utftext += String.fromCharCode((c >> 12) | 224); utftext += String.fromCharCode(((c >> 6) & 63) | 128); utftext += String.fromCharCode((c & 63) | 128); } } return utftext; }, // private method for UTF-8 decoding _utf8_decode : function (utftext) { var string = ""; var i = 0; var c = c1 = c2 = 0; while ( i < utftext.length ) { c = utftext.charCodeAt(i); if (c < 128) { string += String.fromCharCode(c); i++; } else if((c > 191) && (c < 224)) { c2 = utftext.charCodeAt(i+1); string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); i += 2; } else { c2 = utftext.charCodeAt(i+1); c3 = utftext.charCodeAt(i+2); string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); i += 3; } } return string; } } ================================================ FILE: res/js/default.js ================================================ function pproxy_tab_sup(target){ $(target).find("textarea").bind('keydown', function(e) { if (e.keyCode == 9 ) { e.preventDefault(); this.setRangeText('\t'); this.selectionEnd = ++this.selectionStart; } }); } ================================================ FILE: res/js/jquery.js ================================================ /*! jQuery v1.6.4 http://jquery.com/ | http://jquery.org/license */ (function(a,b){function cu(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cr(a){if(!cg[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"":"")+""),ci.close();d=ci.createElement(a),ci.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ch)}cg[a]=e}return cg[a]}function cq(a,b){var c={};f.each(cm.concat.apply([],cm.slice(0,b)),function(){c[this]=a});return c}function cp(){cn=b}function co(){setTimeout(cp,0);return cn=f.now()}function cf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ce(){try{return new a.XMLHttpRequest}catch(b){}}function b$(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bv(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function bl(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bd,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bk(a){f.nodeName(a,"input")?bj(a):"getElementsByTagName"in a&&f.grep(a.getElementsByTagName("input"),bj)}function bj(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bi(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bh(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bg(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i=0===c})}function U(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function M(a,b){return(a&&a!=="*"?a+".":"")+b.replace(y,"`").replace(z,"&")}function L(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;ic)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function J(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function D(){return!0}function C(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function K(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(K,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z]|[0-9])/ig,x=/^-ms-/,y=function(a,b){return(b+"").toUpperCase()},z=d.userAgent,A,B,C,D=Object.prototype.toString,E=Object.prototype.hasOwnProperty,F=Array.prototype.push,G=Array.prototype.slice,H=String.prototype.trim,I=Array.prototype.indexOf,J={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.4",length:0,size:function(){return this.length},toArray:function(){return G.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?F.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),B.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(G.apply(this,arguments),"slice",G.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:F,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;B.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!B){B=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",C,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",C),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&K()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):J[D.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!E.call(a,"constructor")&&!E.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||E.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(x,"ms-").replace(w,y)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c
a",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.firstChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},m&&f.extend(p,{position:"absolute",left:"-1000px",top:"-1000px"});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="
",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="
t
",q=a.getElementsByTagName("td"),u=q[0].offsetHeight===0,q[0].style.display="",q[1].style.display="none",k.reliableHiddenOffsets=u&&q[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",a.appendChild(j),k.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0),o.innerHTML="",n.removeChild(o);if(a.attachEvent)for(t in{submit:1,change:1,focusin:1})s="on"+t,u=s in a,u||(a.setAttribute(s,"return;"),u=typeof a[s]=="function"),k[t+"Bubbles"]=u;o=l=g=h=m=j=a=i=null;return k}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i=f.expando,j=typeof c=="string",k=a.nodeType,l=k?f.cache:a,m=k?a[f.expando]:a[f.expando]&&f.expando;if((!m||e&&m&&l[m]&&!l[m][i])&&j&&d===b)return;m||(k?a[f.expando]=m=++f.uuid:m=f.expando),l[m]||(l[m]={},k||(l[m].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?l[m][i]=f.extend(l[m][i],c):l[m]=f.extend(l[m],c);g=l[m],e&&(g[i]||(g[i]={}),g=g[i]),d!==b&&(g[f.camelCase(c)]=d);if(c==="events"&&!g[c])return g[i]&&g[i].events;j?(h=g[c],h==null&&(h=g[f.camelCase(c)])):h=g;return h}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e=f.expando,g=a.nodeType,h=g?f.cache:a,i=g?a[f.expando]:f.expando;if(!h[i])return;if(b){d=c?h[i][e]:h[i];if(d){d[b]||(b=f.camelCase(b)),delete d[b];if(!l(d))return}}if(c){delete h[i][e];if(!l(h[i]))return}var j=h[i][e];f.support.deleteExpando||!h.setInterval?delete h[i]:h[i]=null,j?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=j):g&&(f.support.deleteExpando?delete a[f.expando]:a.removeAttribute?a.removeAttribute(f.expando):a[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;d=e.value;return typeof d=="string"?d.replace(p,""):d==null?"":d}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);j&&(c=f.attrFix[c]||c,i=f.attrHooks[c],i||(t.test(c)?i=v:u&&(i=u)));if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j&&(h=i.get(a,c))!==null)return h;h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.attr(a,b,""),a.removeAttribute(b),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(u&&f.nodeName(a,"button"))return u.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(u&&f.nodeName(a,"button"))return u.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);i&&(c=f.propFix[c]||c,h=f.propHooks[c]);return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==null?g:a[c]},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabIndex=f.propHooks.tabIndex,v={get:function(a,c){var d;return f.prop(a,c)===!0||(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},f.support.getSetAttribute||(u=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var w=/\.(.*)$/,x=/^(?:textarea|input|select)$/i,y=/\./g,z=/ /g,A=/[^\w\s.|`]/g,B=function(a){return a.replace(A,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=C;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=C);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),B).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d!=null?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},I=function(c){var d=c.target,e,g;if(!!x.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=H(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:I,beforedeactivate:I,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&I.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&I.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",H(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in G)f.event.add(this,c+".specialChange",G[c]);return x.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return x.test(this.nodeName)}},G=f.event.special.change.filters,G.focus=G.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g0)for(h=g;h0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=S.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(U(c[0])||U(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=R.call(arguments);N.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!T[a]?f.unique(e):e,(this.length>1||P.test(d))&&O.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};be.optgroup=be.option,be.tbody=be.tfoot=be.colgroup=be.caption=be.thead,be.th=be.td,f.support.htmlSerialize||(be._default=[1,"div
","
"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!be[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(var c=0,d=this.length;c1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bh(a,d),e=bi(a),g=bi(d);for(h=0;e[h];++h)g[h]&&bh(e[h],g[h])}if(b){bg(a,d);if(c){e=bi(a),g=bi(d);for(h=0;e[h];++h)bg(e[h],g[h])}}e=g=null;return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=be[l]||be._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bn.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bm,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bm.test(g)?g.replace(bm,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bv(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bw=function(a,c){var d,e,g;c=c.replace(bo,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bx=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bp.test(d)&&bq.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bv=bw||bx,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bz=/%20/g,bA=/\[\]$/,bB=/\r?\n/g,bC=/#.*$/,bD=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bE=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bF=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bG=/^(?:GET|HEAD)$/,bH=/^\/\//,bI=/\?/,bJ=/)<[^<]*)*<\/script>/gi,bK=/^(?:select|textarea)/i,bL=/\s+/,bM=/([?&])_=[^&]*/,bN=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bO=f.fn.load,bP={},bQ={},bR,bS,bT=["*/"]+["*"];try{bR=e.href}catch(bU){bR=c.createElement("a"),bR.href="",bR=bR.href}bS=bN.exec(bR.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bO)return bO.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
").append(c.replace(bJ,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bK.test(this.nodeName)||bE.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bB,"\r\n")}}):{name:b.name,value:c.replace(bB,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?bX(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),bX(a,b);return a},ajaxSettings:{url:bR,isLocal:bF.test(bS[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bT},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bV(bP),ajaxTransport:bV(bQ),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?bZ(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=b$(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bD.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bC,"").replace(bH,bS[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bL),d.crossDomain==null&&(r=bN.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bS[1]&&r[2]==bS[2]&&(r[3]||(r[1]==="http:"?80:443))==(bS[3]||(bS[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bW(bP,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bG.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bI.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bM,"$1_="+x);d.url=y+(y===d.url?(bI.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bT+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bW(bQ,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){s<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)bY(g,a[g],c,e);return d.join("&").replace(bz,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var b_=f.now(),ca=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+b_++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ca.test(b.url)||e&&ca.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ca,l),b.url===j&&(e&&(k=k.replace(ca,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cb=a.ActiveXObject?function(){for(var a in cd)cd[a](0,1)}:!1,cc=0,cd;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ce()||cf()}:ce,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cb&&delete cd[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cc,cb&&(cd||(cd={},f(a).unload(cb)),cd[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cg={},ch,ci,cj=/^(?:toggle|show|hide)$/,ck=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cl,cm=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cn;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cq("show",3),a,b,c);for(var g=0,h=this.length;g=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b
";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=ct.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!ct.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cu(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cu(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a&&a.style?parseFloat(f.css(a,d,"padding")):null},f.fn["outer"+c]=function(a){var b=this[0];return b&&b.style?parseFloat(f.css(b,d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNaN(j)?i:j}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window); ================================================ FILE: res/js/session.js ================================================ var socket = io.connect(); var connectNum=0; var pproxy_colors=["#FFFFFF","#CCFFFF","#FFCCCC","#99CCCC","996699","#CC9999","#0099CC","#FFFF66","#336633","#99CC00"] var ip_colors={} var pproxy_session_max_len=0; var pproxy_table_max_row=2000; var pproxy_req_list=[]; if(window.localStorage){ pproxy_req_list=$.parseJSON(window.localStorage["reqs"]||"[]") pproxy_session_max_len=1000; } function pproxy_show_reqs_from_local(){ for(var i=0;i0){ window.localStorage["reqs"]=JSON.stringify(pproxy_req_list); } } function pproxy_save_req_local(dataStr64){ if(pproxy_session_max_len<1){ return; } var n=pproxy_req_list.push(dataStr64) if(n>pproxy_session_max_len){ pproxy_req_list.shift(); } } function pporxy_session_clean(){ pproxy_req_list=[]; $('#tb_network tbody').empty(); } function pproxy_log(msg){ $("#log_div").append("
"+(new Date().toString())+":"+msg+"
"); } function pproxy_net_local_filter(host,path){ if(!_pproxy_filter(host,$("#net_local_host").val())){ return false; } if(!_pproxy_filter(path,$("#net_local_path").val())){ return false; } return true; } function _pproxy_filter(kw,kwstr){ if(kwstr==""){ return true; } var kws=(kwstr+"").split("|"); kw+=""; var tmp=[] for(var i in kws){ var _kw=$.trim(kws[i]+""); if(_kw!=""){ tmp.push(_kw); } } if(tmp.length==0){ return true; } for (var i in tmp){ if(kw.indexOf(tmp[i])!=-1){ return true; } } return false; } socket.on('connect', function() { connectNum++ if(connectNum>1){ pproxy_log("ws error.connectNum="+connectNum); socket.emit("disconnect"); return; } $("#connect_status").html("online") $("#network_filter_form").change(); }); socket.on("disconnect", function() { connectNum--; $("#connect_status").html("offline"); }); socket.on("hello",function(data){ console && console.log(new Date().toString(),data); }); function pproxy_getColor(addr){ var info=addr.split(":"); if(info.length!=2){ return "#FFFFFF"; } var ip=info[0]; var color=ip_colors[ip]; if(!color){ var l=0; for(var _t in ip_colors){ l++; } color=pproxy_colors[l% pproxy_colors.length]; ip_colors[ip]=color; } return color } function pproxy_show_req(data) { console && console.log("pproxy_show_req:",data) // var dataStr=Base64.decode(dataStr64+""); // var data={}; // try{ // data=$.parseJSON(dataStr) // }catch(e){ // console && console.log("parseJSON_error-pproxy_show_req",e,"datastr:",dataStr) // return // } // console && console.log("req", data) var html="0){ html+="class='"+cls.join(" ")+"' "; } html+=" data-ip='"+data["client_ip"]+"' data-host='"+h(data["host"])+"' data-path='"+h(data["path"])+"'>" + "" + data["sid"] + "" + "
" + data["host"] + "
" + "
" +data["method"]+" "+ h(data["path"])+ "
" + ""; var tb=$("#tb_network tbody"); tb.prepend(html); if(Math.random()*100>95 && tb.find("tr").size()>pproxy_table_max_row*1.5){ tb.find("tr:gt("+pproxy_table_max_row+")").each(function(){ $(this).remove(); }); } } socket.on("req",function(data){ console && console.log("on.req","data:",data); pproxy_save_req_local(data); pproxy_show_req(data); }); socket.on("user_num", function(data) { $("#user_num_online").html(data); }); socket.on("res", function(data) { console && console.log("on.res","data:",data) // var dataStr=Base64.decode(dataStr64+""); // try{ // var data=$.parseJSON(dataStr) // }catch(e){ // console && console.log("parseJSON_error on.res:",e) // return // } // console && console.log(data) var req = data["req"]; var res = data["res"]; var html=""; if(req){ var re_do_str=req["schema"]=="http"?(" replay"):""; html += "
"; html += "" if (req["url_origin"]!=req["url"]) { html += ""; } if (req["msg"]) { html += ""; } html += "" + ""; html += pproxy_tr_sub_table(req["form_get"], "get_params"); html += pproxy_tr_sub_table(req["form_post"], "post_params"); if (req["dump"]) { html += ""; } html += "
Request"+re_do_str+pproxy_timeformat(req["now"])+"
url" + h(req["url"]) + "  view
origin" + h(req["url_origin"]) + "
msg" + h(req["msg"])+"
proxy_urerremote_addr :  " +req["client_ip"] + "   docid :  "+ req["id"] + "
req_dump" + h(Base64.decode(req["dump"])).replace(/\n/g, "
") + "
"; }else{ html="



request not exists!
"; } var res_link = ""; var hideBigBody=false; if (res) { res_link = "view"; html += "
" if (res["msg"]) { html += ""; } if (res["dump"]) { html += ""; } var body_str = Base64.decode(res["body"]) var isImg = res["header"]["Content-Type"] != undefined && res["header"]["Content-Type"][0].indexOf("image") > -1; var isStatusOk = res["status"] == 200; var bd_json = pproxy_parseAsjson(body_str); if (bd_json) { hideBigBody=true; html += ""; } if (isImg) { hideBigBody=true; html += ""; } if (!isImg || res["body"].length < 1000 || !isStatusOk) { html += "" + ""; } } html += "
Response " + res_link +pproxy_timeformat(res["now"])+ "
msg" + h(res["msg"])+"
res_dump" + h(Base64.decode(res["dump"])).replace(/\n/g, "
") + "
body_json" + bd_json + "
body_img
body"; if(res["body"].length>400){ html+=""; }else{ hideBigBody=false; } html+= "" + "
" + h(body_str).replace(/\n/g, "
") + "
"; $("#right_content").empty().html(html).hide().slideDown("fast"); }) function pproxy_res_td_body_toggle(){ $("#res_td_body").toggleClass("res_td_body"); return false; } function pproxy_timeformat(sec){ if(!sec ||sec<1000){ return ""; } var numFill=function(num,len){ num=num+""; var l=len-num.length; for(;l>0;l--){ num="0"+num; } return num; } var d=new Date() d.setTime(sec*1000) return " "+d.getFullYear()+"-"+numFill(d.getMonth()+1,2)+"-"+numFill(d.getDate(),2)+" " +numFill(d.getHours(),2)+":"+numFill(d.getMinutes(),2)+":"+numFill(d.getSeconds(),2)+""; } function pproxy_parseAsjson(str) { try { str=str+""; if(str[0]!="{" && str[0]!="["){ return false; } var jsonObj = JSON.parse(str); if (jsonObj) { var json_str = JSON.stringify(jsonObj, null, 4); return "
" + json_str + "
"; } } catch (e) { console.log("pproxy_parseAsjson_error",e); } return false; } function pproxy_tr_sub_table(obj, name) { if (!obj) { return ""; } var html = "" + name + ""; var i = 0; var max_key_len=0; for ( var k in obj) { max_key_len=Math.max(max_key_len,(k+"").length); } for ( var k in obj) { html += ""; i++; } if (i < 1) { return ""; } html += "
" + k + "
    "; for ( var i in obj[k]) { html += "
  • "; var json_str = pproxy_parseAsjson(obj[k][i]); if (json_str) { html += json_str; } else { html += h(obj[k][i]); } html += "
  • "; } html += "
" return html; } function pproxy_show_response(docid){ console && console.log("get_response start,docid=", docid); var loading_msg="loading...docid=" + docid; var isValidId=(docid+"").length>2; if(!isValidId){ loading_msg="https request:no data"; }else{ loading_msg+=" reload"; } $("#right_content").empty().html("
"+loading_msg+"
"); if(!isValidId){ return; } socket.emit("get_response", docid); console.log && console.log("emit get_response,docid=",docid); } function get_response(tr, docid) { pproxy_show_response(docid); $(tr).parent("tbody").find("tr").removeClass("selected"); $(tr).addClass("selected"); location.hash="req_"+docid; } function bytesToString(bytes) { var result = ""; for (var i = 0; i < bytes.length; i++) { result += String.fromCharCode(parseInt(bytes[i], 2)); } return result; } function h(html) { if(html==""){ return " "; } html = (html+"").replace(/&/g, '&') .replace(//g, '>') .replace(/'/g, '´') .replace(/"/g, '"') .replace(/\|/g, '¦'); return html; } $().ready(function() { $("#network_filter_form input:text").each(function(){ pproxy_local_save(this,$(this).attr("name")); }); var filter_form=$("#network_filter_form"); filter_form.change(function() { var form_data = $(this).serialize(); socket.emit("client_filter", form_data); }); setTimeout(function(){filter_form.change();},600); setTimeout(function(){filter_form.change();},3000); filter_form.find("input:text").keyup(function(){ filter_form.change(); }); if(location.hash.match(/req_\d+/)){ var docid=location.hash.substr(5); setTimeout((function(id){ return function(){ pproxy_show_response(id); } })(docid),500); } setTimeout(pproxy_show_reqs_from_local,0); $("#net_local_host,#net_local_path").bind("keyup change",function(){ $('#tb_network tbody tr').each(function(){ if(pproxy_net_local_filter($(this).data("host"),$(this).data("path"))){ $(this).removeClass("hide"); }else{ $(this).addClass("hide"); } }); }); }); function pproxy_local_save(target,id){ if(!window.localStorage){ return; } $(target).val(window.localStorage[id]||"").change(function(){ window.localStorage[id]=$(this).val(); }); } ================================================ FILE: res/js/socket.io.js ================================================ /*! Socket.IO.js build:0.9.10, development. Copyright(c) 2011 LearnBoost MIT Licensed */ var io = ('undefined' === typeof module ? {} : module.exports); (function() { /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, global) { /** * IO namespace. * * @namespace */ var io = exports; /** * Socket.IO version * * @api public */ io.version = '0.9.10'; /** * Protocol implemented. * * @api public */ io.protocol = 1; /** * Available transports, these will be populated with the available transports * * @api public */ io.transports = []; /** * Keep track of jsonp callbacks. * * @api private */ io.j = []; /** * Keep track of our io.Sockets * * @api private */ io.sockets = {}; /** * Manages connections to hosts. * * @param {String} uri * @Param {Boolean} force creation of new socket (defaults to false) * @api public */ io.connect = function (host, details) { var uri = io.util.parseUri(host) , uuri , socket; if (global && global.location) { uri.protocol = uri.protocol || global.location.protocol.slice(0, -1); uri.host = uri.host || (global.document ? global.document.domain : global.location.hostname); uri.port = uri.port || global.location.port; } uuri = io.util.uniqueUri(uri); var options = { host: uri.host , secure: 'https' == uri.protocol , port: uri.port || ('https' == uri.protocol ? 443 : 80) , query: uri.query || '' }; io.util.merge(options, details); if (options['force new connection'] || !io.sockets[uuri]) { socket = new io.Socket(options); } if (!options['force new connection'] && socket) { io.sockets[uuri] = socket; } socket = socket || io.sockets[uuri]; // if path is different from '' or / return socket.of(uri.path.length > 1 ? uri.path : ''); }; })('object' === typeof module ? module.exports : (this.io = {}), this); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, global) { /** * Utilities namespace. * * @namespace */ var util = exports.util = {}; /** * Parses an URI * * @author Steven Levithan (MIT license) * @api public */ var re = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; var parts = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor']; util.parseUri = function (str) { var m = re.exec(str || '') , uri = {} , i = 14; while (i--) { uri[parts[i]] = m[i] || ''; } return uri; }; /** * Produces a unique url that identifies a Socket.IO connection. * * @param {Object} uri * @api public */ util.uniqueUri = function (uri) { var protocol = uri.protocol , host = uri.host , port = uri.port; if ('document' in global) { host = host || document.domain; port = port || (protocol == 'https' && document.location.protocol !== 'https:' ? 443 : document.location.port); } else { host = host || 'localhost'; if (!port && protocol == 'https') { port = 443; } } return (protocol || 'http') + '://' + host + ':' + (port || 80); }; /** * Mergest 2 query strings in to once unique query string * * @param {String} base * @param {String} addition * @api public */ util.query = function (base, addition) { var query = util.chunkQuery(base || '') , components = []; util.merge(query, util.chunkQuery(addition || '')); for (var part in query) { if (query.hasOwnProperty(part)) { components.push(part + '=' + query[part]); } } return components.length ? '?' + components.join('&') : ''; }; /** * Transforms a querystring in to an object * * @param {String} qs * @api public */ util.chunkQuery = function (qs) { var query = {} , params = qs.split('&') , i = 0 , l = params.length , kv; for (; i < l; ++i) { kv = params[i].split('='); if (kv[0]) { query[kv[0]] = kv[1]; } } return query; }; /** * Executes the given function when the page is loaded. * * io.util.load(function () { console.log('page loaded'); }); * * @param {Function} fn * @api public */ var pageLoaded = false; util.load = function (fn) { if ('document' in global && document.readyState === 'complete' || pageLoaded) { return fn(); } util.on(global, 'load', fn, false); }; /** * Adds an event. * * @api private */ util.on = function (element, event, fn, capture) { if (element.attachEvent) { element.attachEvent('on' + event, fn); } else if (element.addEventListener) { element.addEventListener(event, fn, capture); } }; /** * Generates the correct `XMLHttpRequest` for regular and cross domain requests. * * @param {Boolean} [xdomain] Create a request that can be used cross domain. * @returns {XMLHttpRequest|false} If we can create a XMLHttpRequest. * @api private */ util.request = function (xdomain) { if (xdomain && 'undefined' != typeof XDomainRequest) { return new XDomainRequest(); } if ('undefined' != typeof XMLHttpRequest && (!xdomain || util.ua.hasCORS)) { return new XMLHttpRequest(); } if (!xdomain) { try { return new window[(['Active'].concat('Object').join('X'))]('Microsoft.XMLHTTP'); } catch(e) { } } return null; }; /** * XHR based transport constructor. * * @constructor * @api public */ /** * Change the internal pageLoaded value. */ if ('undefined' != typeof window) { util.load(function () { pageLoaded = true; }); } /** * Defers a function to ensure a spinner is not displayed by the browser * * @param {Function} fn * @api public */ util.defer = function (fn) { if (!util.ua.webkit || 'undefined' != typeof importScripts) { return fn(); } util.load(function () { setTimeout(fn, 100); }); }; /** * Merges two objects. * * @api public */ util.merge = function merge (target, additional, deep, lastseen) { var seen = lastseen || [] , depth = typeof deep == 'undefined' ? 2 : deep , prop; for (prop in additional) { if (additional.hasOwnProperty(prop) && util.indexOf(seen, prop) < 0) { if (typeof target[prop] !== 'object' || !depth) { target[prop] = additional[prop]; seen.push(additional[prop]); } else { util.merge(target[prop], additional[prop], depth - 1, seen); } } } return target; }; /** * Merges prototypes from objects * * @api public */ util.mixin = function (ctor, ctor2) { util.merge(ctor.prototype, ctor2.prototype); }; /** * Shortcut for prototypical and static inheritance. * * @api private */ util.inherit = function (ctor, ctor2) { function f() {}; f.prototype = ctor2.prototype; ctor.prototype = new f; }; /** * Checks if the given object is an Array. * * io.util.isArray([]); // true * io.util.isArray({}); // false * * @param Object obj * @api public */ util.isArray = Array.isArray || function (obj) { return Object.prototype.toString.call(obj) === '[object Array]'; }; /** * Intersects values of two arrays into a third * * @api public */ util.intersect = function (arr, arr2) { var ret = [] , longest = arr.length > arr2.length ? arr : arr2 , shortest = arr.length > arr2.length ? arr2 : arr; for (var i = 0, l = shortest.length; i < l; i++) { if (~util.indexOf(longest, shortest[i])) ret.push(shortest[i]); } return ret; } /** * Array indexOf compatibility. * * @see bit.ly/a5Dxa2 * @api public */ util.indexOf = function (arr, o, i) { for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0; i < j && arr[i] !== o; i++) {} return j <= i ? -1 : i; }; /** * Converts enumerables to array. * * @api public */ util.toArray = function (enu) { var arr = []; for (var i = 0, l = enu.length; i < l; i++) arr.push(enu[i]); return arr; }; /** * UA / engines detection namespace. * * @namespace */ util.ua = {}; /** * Whether the UA supports CORS for XHR. * * @api public */ util.ua.hasCORS = 'undefined' != typeof XMLHttpRequest && (function () { try { var a = new XMLHttpRequest(); } catch (e) { return false; } return a.withCredentials != undefined; })(); /** * Detect webkit. * * @api public */ util.ua.webkit = 'undefined' != typeof navigator && /webkit/i.test(navigator.userAgent); /** * Detect iPad/iPhone/iPod. * * @api public */ util.ua.iDevice = 'undefined' != typeof navigator && /iPad|iPhone|iPod/i.test(navigator.userAgent); })('undefined' != typeof io ? io : module.exports, this); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, io) { /** * Expose constructor. */ exports.EventEmitter = EventEmitter; /** * Event emitter constructor. * * @api public. */ function EventEmitter () {}; /** * Adds a listener * * @api public */ EventEmitter.prototype.on = function (name, fn) { if (!this.$events) { this.$events = {}; } if (!this.$events[name]) { this.$events[name] = fn; } else if (io.util.isArray(this.$events[name])) { this.$events[name].push(fn); } else { this.$events[name] = [this.$events[name], fn]; } return this; }; EventEmitter.prototype.addListener = EventEmitter.prototype.on; /** * Adds a volatile listener. * * @api public */ EventEmitter.prototype.once = function (name, fn) { var self = this; function on () { self.removeListener(name, on); fn.apply(this, arguments); }; on.listener = fn; this.on(name, on); return this; }; /** * Removes a listener. * * @api public */ EventEmitter.prototype.removeListener = function (name, fn) { if (this.$events && this.$events[name]) { var list = this.$events[name]; if (io.util.isArray(list)) { var pos = -1; for (var i = 0, l = list.length; i < l; i++) { if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { pos = i; break; } } if (pos < 0) { return this; } list.splice(pos, 1); if (!list.length) { delete this.$events[name]; } } else if (list === fn || (list.listener && list.listener === fn)) { delete this.$events[name]; } } return this; }; /** * Removes all listeners for an event. * * @api public */ EventEmitter.prototype.removeAllListeners = function (name) { if (name === undefined) { this.$events = {}; return this; } if (this.$events && this.$events[name]) { this.$events[name] = null; } return this; }; /** * Gets all listeners for a certain event. * * @api publci */ EventEmitter.prototype.listeners = function (name) { if (!this.$events) { this.$events = {}; } if (!this.$events[name]) { this.$events[name] = []; } if (!io.util.isArray(this.$events[name])) { this.$events[name] = [this.$events[name]]; } return this.$events[name]; }; /** * Emits an event. * * @api public */ EventEmitter.prototype.emit = function (name) { if (!this.$events) { return false; } var handler = this.$events[name]; if (!handler) { return false; } var args = Array.prototype.slice.call(arguments, 1); if ('function' == typeof handler) { handler.apply(this, args); } else if (io.util.isArray(handler)) { var listeners = handler.slice(); for (var i = 0, l = listeners.length; i < l; i++) { listeners[i].apply(this, args); } } else { return false; } return true; }; })( 'undefined' != typeof io ? io : module.exports , 'undefined' != typeof io ? io : module.parent.exports ); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ /** * Based on JSON2 (http://www.JSON.org/js.html). */ (function (exports, nativeJSON) { "use strict"; // use native JSON if it's available if (nativeJSON && nativeJSON.parse){ return exports.JSON = { parse: nativeJSON.parse , stringify: nativeJSON.stringify } } var JSON = exports.JSON = {}; function f(n) { // Format integers to have at least two digits. return n < 10 ? '0' + n : n; } function date(d, key) { return isFinite(d.valueOf()) ? d.getUTCFullYear() + '-' + f(d.getUTCMonth() + 1) + '-' + f(d.getUTCDate()) + 'T' + f(d.getUTCHours()) + ':' + f(d.getUTCMinutes()) + ':' + f(d.getUTCSeconds()) + 'Z' : null; }; var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, gap, indent, meta = { // table of character substitutions '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\' }, rep; function quote(string) { // If the string contains no control characters, no quote characters, and no // backslash characters, then we can safely slap some quotes around it. // Otherwise we must also replace the offending characters with safe escape // sequences. escapable.lastIndex = 0; return escapable.test(string) ? '"' + string.replace(escapable, function (a) { var c = meta[a]; return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }) + '"' : '"' + string + '"'; } function str(key, holder) { // Produce a string from holder[key]. var i, // The loop counter. k, // The member key. v, // The member value. length, mind = gap, partial, value = holder[key]; // If the value has a toJSON method, call it to obtain a replacement value. if (value instanceof Date) { value = date(key); } // If we were called with a replacer function, then call the replacer to // obtain a replacement value. if (typeof rep === 'function') { value = rep.call(holder, key, value); } // What happens next depends on the value's type. switch (typeof value) { case 'string': return quote(value); case 'number': // JSON numbers must be finite. Encode non-finite numbers as null. return isFinite(value) ? String(value) : 'null'; case 'boolean': case 'null': // If the value is a boolean or null, convert it to a string. Note: // typeof null does not produce 'null'. The case is included here in // the remote chance that this gets fixed someday. return String(value); // If the type is 'object', we might be dealing with an object or an array or // null. case 'object': // Due to a specification blunder in ECMAScript, typeof null is 'object', // so watch out for that case. if (!value) { return 'null'; } // Make an array to hold the partial results of stringifying this object value. gap += indent; partial = []; // Is the value an array? if (Object.prototype.toString.apply(value) === '[object Array]') { // The value is an array. Stringify every element. Use null as a placeholder // for non-JSON values. length = value.length; for (i = 0; i < length; i += 1) { partial[i] = str(i, value) || 'null'; } // Join all of the elements together, separated with commas, and wrap them in // brackets. v = partial.length === 0 ? '[]' : gap ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : '[' + partial.join(',') + ']'; gap = mind; return v; } // If the replacer is an array, use it to select the members to be stringified. if (rep && typeof rep === 'object') { length = rep.length; for (i = 0; i < length; i += 1) { if (typeof rep[i] === 'string') { k = rep[i]; v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } else { // Otherwise, iterate through all of the keys in the object. for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } // Join all of the member texts together, separated with commas, // and wrap them in braces. v = partial.length === 0 ? '{}' : gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : '{' + partial.join(',') + '}'; gap = mind; return v; } } // If the JSON object does not yet have a stringify method, give it one. JSON.stringify = function (value, replacer, space) { // The stringify method takes a value and an optional replacer, and an optional // space parameter, and returns a JSON text. The replacer can be a function // that can replace values, or an array of strings that will select the keys. // A default replacer method can be provided. Use of the space parameter can // produce text that is more easily readable. var i; gap = ''; indent = ''; // If the space parameter is a number, make an indent string containing that // many spaces. if (typeof space === 'number') { for (i = 0; i < space; i += 1) { indent += ' '; } // If the space parameter is a string, it will be used as the indent string. } else if (typeof space === 'string') { indent = space; } // If there is a replacer, it must be a function or an array. // Otherwise, throw an error. rep = replacer; if (replacer && typeof replacer !== 'function' && (typeof replacer !== 'object' || typeof replacer.length !== 'number')) { throw new Error('JSON.stringify'); } // Make a fake root object containing our value under the key of ''. // Return the result of stringifying the value. return str('', {'': value}); }; // If the JSON object does not yet have a parse method, give it one. JSON.parse = function (text, reviver) { // The parse method takes a text and an optional reviver function, and returns // a JavaScript value if the text is a valid JSON text. var j; function walk(holder, key) { // The walk method is used to recursively walk the resulting structure so // that modifications can be made. var k, v, value = holder[key]; if (value && typeof value === 'object') { for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = walk(value, k); if (v !== undefined) { value[k] = v; } else { delete value[k]; } } } } return reviver.call(holder, key, value); } // Parsing happens in four stages. In the first stage, we replace certain // Unicode characters with escape sequences. JavaScript handles many characters // incorrectly, either silently deleting them, or treating them as line endings. text = String(text); cx.lastIndex = 0; if (cx.test(text)) { text = text.replace(cx, function (a) { return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }); } // In the second stage, we run the text against regular expressions that look // for non-JSON patterns. We are especially concerned with '()' and 'new' // because they can cause invocation, and '=' because it can cause mutation. // But just to be safe, we want to reject all unexpected forms. // We split the second stage into 4 regexp operations in order to work around // crippling inefficiencies in IE's and Safari's regexp engines. First we // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we // replace all simple value tokens with ']' characters. Third, we delete all // open brackets that follow a colon or comma or that begin the text. Finally, // we look to see that the remaining characters are only whitespace or ']' or // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. if (/^[\],:{}\s]*$/ .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { // In the third stage we use the eval function to compile the text into a // JavaScript structure. The '{' operator is subject to a syntactic ambiguity // in JavaScript: it can begin a block or an object literal. We wrap the text // in parens to eliminate the ambiguity. j = eval('(' + text + ')'); // In the optional fourth stage, we recursively walk the new structure, passing // each name/value pair to a reviver function for possible transformation. return typeof reviver === 'function' ? walk({'': j}, '') : j; } // If the text is not JSON parseable, then a SyntaxError is thrown. throw new SyntaxError('JSON.parse'); }; })( 'undefined' != typeof io ? io : module.exports , typeof JSON !== 'undefined' ? JSON : undefined ); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, io) { /** * Parser namespace. * * @namespace */ var parser = exports.parser = {}; /** * Packet types. */ var packets = parser.packets = [ 'disconnect' , 'connect' , 'heartbeat' , 'message' , 'json' , 'event' , 'ack' , 'error' , 'noop' ]; /** * Errors reasons. */ var reasons = parser.reasons = [ 'transport not supported' , 'client not handshaken' , 'unauthorized' ]; /** * Errors advice. */ var advice = parser.advice = [ 'reconnect' ]; /** * Shortcuts. */ var JSON = io.JSON , indexOf = io.util.indexOf; /** * Encodes a packet. * * @api private */ parser.encodePacket = function (packet) { var type = indexOf(packets, packet.type) , id = packet.id || '' , endpoint = packet.endpoint || '' , ack = packet.ack , data = null; switch (packet.type) { case 'error': var reason = packet.reason ? indexOf(reasons, packet.reason) : '' , adv = packet.advice ? indexOf(advice, packet.advice) : ''; if (reason !== '' || adv !== '') data = reason + (adv !== '' ? ('+' + adv) : ''); break; case 'message': if (packet.data !== '') data = packet.data; break; case 'event': var ev = { name: packet.name }; if (packet.args && packet.args.length) { ev.args = packet.args; } data = JSON.stringify(ev); break; case 'json': data = JSON.stringify(packet.data); break; case 'connect': if (packet.qs) data = packet.qs; break; case 'ack': data = packet.ackId + (packet.args && packet.args.length ? '+' + JSON.stringify(packet.args) : ''); break; } // construct packet with required fragments var encoded = [ type , id + (ack == 'data' ? '+' : '') , endpoint ]; // data fragment is optional if (data !== null && data !== undefined) encoded.push(data); return encoded.join(':'); }; /** * Encodes multiple messages (payload). * * @param {Array} messages * @api private */ parser.encodePayload = function (packets) { var decoded = ''; if (packets.length == 1) return packets[0]; for (var i = 0, l = packets.length; i < l; i++) { var packet = packets[i]; decoded += '\ufffd' + packet.length + '\ufffd' + packets[i]; } return decoded; }; /** * Decodes a packet * * @api private */ var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/; parser.decodePacket = function (data) { var pieces = data.match(regexp); if (!pieces) return {}; var id = pieces[2] || '' , data = pieces[5] || '' , packet = { type: packets[pieces[1]] , endpoint: pieces[4] || '' }; // whether we need to acknowledge the packet if (id) { packet.id = id; if (pieces[3]) packet.ack = 'data'; else packet.ack = true; } // handle different packet types switch (packet.type) { case 'error': var pieces = data.split('+'); packet.reason = reasons[pieces[0]] || ''; packet.advice = advice[pieces[1]] || ''; break; case 'message': packet.data = data || ''; break; case 'event': try { var opts = JSON.parse(data); packet.name = opts.name; packet.args = opts.args; } catch (e) { } packet.args = packet.args || []; break; case 'json': try { packet.data = JSON.parse(data); } catch (e) { } break; case 'connect': packet.qs = data || ''; break; case 'ack': var pieces = data.match(/^([0-9]+)(\+)?(.*)/); if (pieces) { packet.ackId = pieces[1]; packet.args = []; if (pieces[3]) { try { packet.args = pieces[3] ? JSON.parse(pieces[3]) : []; } catch (e) { } } } break; case 'disconnect': case 'heartbeat': break; }; return packet; }; /** * Decodes data payload. Detects multiple messages * * @return {Array} messages * @api public */ parser.decodePayload = function (data) { // IE doesn't like data[i] for unicode chars, charAt works fine if (data.charAt(0) == '\ufffd') { var ret = []; for (var i = 1, length = ''; i < data.length; i++) { if (data.charAt(i) == '\ufffd') { ret.push(parser.decodePacket(data.substr(i + 1).substr(0, length))); i += Number(length) + 1; length = ''; } else { length += data.charAt(i); } } return ret; } else { return [parser.decodePacket(data)]; } }; })( 'undefined' != typeof io ? io : module.exports , 'undefined' != typeof io ? io : module.parent.exports ); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, io) { /** * Expose constructor. */ exports.Transport = Transport; /** * This is the transport template for all supported transport methods. * * @constructor * @api public */ function Transport (socket, sessid) { this.socket = socket; this.sessid = sessid; }; /** * Apply EventEmitter mixin. */ io.util.mixin(Transport, io.EventEmitter); /** * Indicates whether heartbeats is enabled for this transport * * @api private */ Transport.prototype.heartbeats = function () { return true; } /** * Handles the response from the server. When a new response is received * it will automatically update the timeout, decode the message and * forwards the response to the onMessage function for further processing. * * @param {String} data Response from the server. * @api private */ Transport.prototype.onData = function (data) { this.clearCloseTimeout(); // If the connection in currently open (or in a reopening state) reset the close // timeout since we have just received data. This check is necessary so // that we don't reset the timeout on an explicitly disconnected connection. if (this.socket.connected || this.socket.connecting || this.socket.reconnecting) { this.setCloseTimeout(); } if (data !== '') { // todo: we should only do decodePayload for xhr transports var msgs = io.parser.decodePayload(data); if (msgs && msgs.length) { for (var i = 0, l = msgs.length; i < l; i++) { this.onPacket(msgs[i]); } } } return this; }; /** * Handles packets. * * @api private */ Transport.prototype.onPacket = function (packet) { this.socket.setHeartbeatTimeout(); if (packet.type == 'heartbeat') { return this.onHeartbeat(); } if (packet.type == 'connect' && packet.endpoint == '') { this.onConnect(); } if (packet.type == 'error' && packet.advice == 'reconnect') { this.isOpen = false; } this.socket.onPacket(packet); return this; }; /** * Sets close timeout * * @api private */ Transport.prototype.setCloseTimeout = function () { if (!this.closeTimeout) { var self = this; this.closeTimeout = setTimeout(function () { self.onDisconnect(); }, this.socket.closeTimeout); } }; /** * Called when transport disconnects. * * @api private */ Transport.prototype.onDisconnect = function () { if (this.isOpen) this.close(); this.clearTimeouts(); this.socket.onDisconnect(); return this; }; /** * Called when transport connects * * @api private */ Transport.prototype.onConnect = function () { this.socket.onConnect(); return this; } /** * Clears close timeout * * @api private */ Transport.prototype.clearCloseTimeout = function () { if (this.closeTimeout) { clearTimeout(this.closeTimeout); this.closeTimeout = null; } }; /** * Clear timeouts * * @api private */ Transport.prototype.clearTimeouts = function () { this.clearCloseTimeout(); if (this.reopenTimeout) { clearTimeout(this.reopenTimeout); } }; /** * Sends a packet * * @param {Object} packet object. * @api private */ Transport.prototype.packet = function (packet) { this.send(io.parser.encodePacket(packet)); }; /** * Send the received heartbeat message back to server. So the server * knows we are still connected. * * @param {String} heartbeat Heartbeat response from the server. * @api private */ Transport.prototype.onHeartbeat = function (heartbeat) { this.packet({ type: 'heartbeat' }); }; /** * Called when the transport opens. * * @api private */ Transport.prototype.onOpen = function () { this.isOpen = true; this.clearCloseTimeout(); this.socket.onOpen(); }; /** * Notifies the base when the connection with the Socket.IO server * has been disconnected. * * @api private */ Transport.prototype.onClose = function () { var self = this; /* FIXME: reopen delay causing a infinit loop this.reopenTimeout = setTimeout(function () { self.open(); }, this.socket.options['reopen delay']);*/ this.isOpen = false; this.socket.onClose(); this.onDisconnect(); }; /** * Generates a connection url based on the Socket.IO URL Protocol. * See for more details. * * @returns {String} Connection url * @api private */ Transport.prototype.prepareUrl = function () { var options = this.socket.options; return this.scheme() + '://' + options.host + ':' + options.port + '/' + options.resource + '/' + io.protocol + '/' + this.name + '/' + this.sessid; }; /** * Checks if the transport is ready to start a connection. * * @param {Socket} socket The socket instance that needs a transport * @param {Function} fn The callback * @api private */ Transport.prototype.ready = function (socket, fn) { fn.call(this); }; })( 'undefined' != typeof io ? io : module.exports , 'undefined' != typeof io ? io : module.parent.exports ); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, io, global) { /** * Expose constructor. */ exports.Socket = Socket; /** * Create a new `Socket.IO client` which can establish a persistent * connection with a Socket.IO enabled server. * * @api public */ function Socket (options) { this.options = { port: 80 , secure: false , document: 'document' in global ? document : false , resource: 'socket.io' , transports: io.transports , 'connect timeout': 10000 , 'try multiple transports': true , 'reconnect': true , 'reconnection delay': 500 , 'reconnection limit': Infinity , 'reopen delay': 3000 , 'max reconnection attempts': 10 , 'sync disconnect on unload': false , 'auto connect': true , 'flash policy port': 10843 , 'manualFlush': false }; io.util.merge(this.options, options); this.connected = false; this.open = false; this.connecting = false; this.reconnecting = false; this.namespaces = {}; this.buffer = []; this.doBuffer = false; if (this.options['sync disconnect on unload'] && (!this.isXDomain() || io.util.ua.hasCORS)) { var self = this; io.util.on(global, 'beforeunload', function () { self.disconnectSync(); }, false); } if (this.options['auto connect']) { this.connect(); } }; /** * Apply EventEmitter mixin. */ io.util.mixin(Socket, io.EventEmitter); /** * Returns a namespace listener/emitter for this socket * * @api public */ Socket.prototype.of = function (name) { if (!this.namespaces[name]) { this.namespaces[name] = new io.SocketNamespace(this, name); if (name !== '') { this.namespaces[name].packet({ type: 'connect' }); } } return this.namespaces[name]; }; /** * Emits the given event to the Socket and all namespaces * * @api private */ Socket.prototype.publish = function () { this.emit.apply(this, arguments); var nsp; for (var i in this.namespaces) { if (this.namespaces.hasOwnProperty(i)) { nsp = this.of(i); nsp.$emit.apply(nsp, arguments); } } }; /** * Performs the handshake * * @api private */ function empty () { }; Socket.prototype.handshake = function (fn) { var self = this , options = this.options; function complete (data) { if (data instanceof Error) { self.connecting = false; self.onError(data.message); } else { fn.apply(null, data.split(':')); } }; var url = [ 'http' + (options.secure ? 's' : '') + ':/' , options.host + ':' + options.port , options.resource , io.protocol , io.util.query(this.options.query, 't=' + +new Date) ].join('/'); if (this.isXDomain() && !io.util.ua.hasCORS) { var insertAt = document.getElementsByTagName('script')[0] , script = document.createElement('script'); script.src = url + '&jsonp=' + io.j.length; insertAt.parentNode.insertBefore(script, insertAt); io.j.push(function (data) { complete(data); script.parentNode.removeChild(script); }); } else { var xhr = io.util.request(); xhr.open('GET', url, true); if (this.isXDomain()) { xhr.withCredentials = true; } xhr.onreadystatechange = function () { if (xhr.readyState == 4) { xhr.onreadystatechange = empty; if (xhr.status == 200) { complete(xhr.responseText); } else if (xhr.status == 403) { self.onError(xhr.responseText); } else { self.connecting = false; !self.reconnecting && self.onError(xhr.responseText); } } }; xhr.send(null); } }; /** * Find an available transport based on the options supplied in the constructor. * * @api private */ Socket.prototype.getTransport = function (override) { var transports = override || this.transports, match; for (var i = 0, transport; transport = transports[i]; i++) { if (io.Transport[transport] && io.Transport[transport].check(this) && (!this.isXDomain() || io.Transport[transport].xdomainCheck(this))) { return new io.Transport[transport](this, this.sessionid); } } return null; }; /** * Connects to the server. * * @param {Function} [fn] Callback. * @returns {io.Socket} * @api public */ Socket.prototype.connect = function (fn) { if (this.connecting) { return this; } var self = this; self.connecting = true; this.handshake(function (sid, heartbeat, close, transports) { self.sessionid = sid; self.closeTimeout = close * 1000; self.heartbeatTimeout = heartbeat * 1000; if(!self.transports) self.transports = self.origTransports = (transports ? io.util.intersect( transports.split(',') , self.options.transports ) : self.options.transports); self.setHeartbeatTimeout(); function connect (transports){ if (self.transport) self.transport.clearTimeouts(); self.transport = self.getTransport(transports); if (!self.transport) return self.publish('connect_failed'); // once the transport is ready self.transport.ready(self, function () { self.connecting = true; self.publish('connecting', self.transport.name); self.transport.open(); if (self.options['connect timeout']) { self.connectTimeoutTimer = setTimeout(function () { if (!self.connected) { self.connecting = false; if (self.options['try multiple transports']) { var remaining = self.transports; while (remaining.length > 0 && remaining.splice(0,1)[0] != self.transport.name) {} if (remaining.length){ connect(remaining); } else { self.publish('connect_failed'); } } } }, self.options['connect timeout']); } }); } connect(self.transports); self.once('connect', function (){ clearTimeout(self.connectTimeoutTimer); fn && typeof fn == 'function' && fn(); }); }); return this; }; /** * Clears and sets a new heartbeat timeout using the value given by the * server during the handshake. * * @api private */ Socket.prototype.setHeartbeatTimeout = function () { clearTimeout(this.heartbeatTimeoutTimer); if(this.transport && !this.transport.heartbeats()) return; var self = this; this.heartbeatTimeoutTimer = setTimeout(function () { self.transport.onClose(); }, this.heartbeatTimeout); }; /** * Sends a message. * * @param {Object} data packet. * @returns {io.Socket} * @api public */ Socket.prototype.packet = function (data) { if (this.connected && !this.doBuffer) { this.transport.packet(data); } else { this.buffer.push(data); } return this; }; /** * Sets buffer state * * @api private */ Socket.prototype.setBuffer = function (v) { this.doBuffer = v; if (!v && this.connected && this.buffer.length) { if (!this.options['manualFlush']) { this.flushBuffer(); } } }; /** * Flushes the buffer data over the wire. * To be invoked manually when 'manualFlush' is set to true. * * @api public */ Socket.prototype.flushBuffer = function() { this.transport.payload(this.buffer); this.buffer = []; }; /** * Disconnect the established connect. * * @returns {io.Socket} * @api public */ Socket.prototype.disconnect = function () { if (this.connected || this.connecting) { if (this.open) { this.of('').packet({ type: 'disconnect' }); } // handle disconnection immediately this.onDisconnect('booted'); } return this; }; /** * Disconnects the socket with a sync XHR. * * @api private */ Socket.prototype.disconnectSync = function () { // ensure disconnection var xhr = io.util.request(); var uri = [ 'http' + (this.options.secure ? 's' : '') + ':/' , this.options.host + ':' + this.options.port , this.options.resource , io.protocol , '' , this.sessionid ].join('/') + '/?disconnect=1'; xhr.open('GET', uri, false); xhr.send(null); // handle disconnection immediately this.onDisconnect('booted'); }; /** * Check if we need to use cross domain enabled transports. Cross domain would * be a different port or different domain name. * * @returns {Boolean} * @api private */ Socket.prototype.isXDomain = function () { var port = global.location.port || ('https:' == global.location.protocol ? 443 : 80); return this.options.host !== global.location.hostname || this.options.port != port; }; /** * Called upon handshake. * * @api private */ Socket.prototype.onConnect = function () { if (!this.connected) { this.connected = true; this.connecting = false; if (!this.doBuffer) { // make sure to flush the buffer this.setBuffer(false); } this.emit('connect'); } }; /** * Called when the transport opens * * @api private */ Socket.prototype.onOpen = function () { this.open = true; }; /** * Called when the transport closes. * * @api private */ Socket.prototype.onClose = function () { this.open = false; clearTimeout(this.heartbeatTimeoutTimer); }; /** * Called when the transport first opens a connection * * @param text */ Socket.prototype.onPacket = function (packet) { this.of(packet.endpoint).onPacket(packet); }; /** * Handles an error. * * @api private */ Socket.prototype.onError = function (err) { if (err && err.advice) { if (err.advice === 'reconnect' && (this.connected || this.connecting)) { this.disconnect(); if (this.options.reconnect) { this.reconnect(); } } } this.publish('error', err && err.reason ? err.reason : err); }; /** * Called when the transport disconnects. * * @api private */ Socket.prototype.onDisconnect = function (reason) { var wasConnected = this.connected , wasConnecting = this.connecting; this.connected = false; this.connecting = false; this.open = false; if (wasConnected || wasConnecting) { this.transport.close(); this.transport.clearTimeouts(); if (wasConnected) { this.publish('disconnect', reason); if ('booted' != reason && this.options.reconnect && !this.reconnecting) { this.reconnect(); } } } }; /** * Called upon reconnection. * * @api private */ Socket.prototype.reconnect = function () { this.reconnecting = true; this.reconnectionAttempts = 0; this.reconnectionDelay = this.options['reconnection delay']; var self = this , maxAttempts = this.options['max reconnection attempts'] , tryMultiple = this.options['try multiple transports'] , limit = this.options['reconnection limit']; function reset () { if (self.connected) { for (var i in self.namespaces) { if (self.namespaces.hasOwnProperty(i) && '' !== i) { self.namespaces[i].packet({ type: 'connect' }); } } self.publish('reconnect', self.transport.name, self.reconnectionAttempts); } clearTimeout(self.reconnectionTimer); self.removeListener('connect_failed', maybeReconnect); self.removeListener('connect', maybeReconnect); self.reconnecting = false; delete self.reconnectionAttempts; delete self.reconnectionDelay; delete self.reconnectionTimer; delete self.redoTransports; self.options['try multiple transports'] = tryMultiple; }; function maybeReconnect () { if (!self.reconnecting) { return; } if (self.connected) { return reset(); }; if (self.connecting && self.reconnecting) { return self.reconnectionTimer = setTimeout(maybeReconnect, 1000); } if (self.reconnectionAttempts++ >= maxAttempts) { if (!self.redoTransports) { self.on('connect_failed', maybeReconnect); self.options['try multiple transports'] = true; self.transports = self.origTransports; self.transport = self.getTransport(); self.redoTransports = true; self.connect(); } else { self.publish('reconnect_failed'); reset(); } } else { if (self.reconnectionDelay < limit) { self.reconnectionDelay *= 2; // exponential back off } self.connect(); self.publish('reconnecting', self.reconnectionDelay, self.reconnectionAttempts); self.reconnectionTimer = setTimeout(maybeReconnect, self.reconnectionDelay); } }; this.options['try multiple transports'] = false; this.reconnectionTimer = setTimeout(maybeReconnect, this.reconnectionDelay); this.on('connect', maybeReconnect); }; })( 'undefined' != typeof io ? io : module.exports , 'undefined' != typeof io ? io : module.parent.exports , this ); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, io) { /** * Expose constructor. */ exports.SocketNamespace = SocketNamespace; /** * Socket namespace constructor. * * @constructor * @api public */ function SocketNamespace (socket, name) { this.socket = socket; this.name = name || ''; this.flags = {}; this.json = new Flag(this, 'json'); this.ackPackets = 0; this.acks = {}; }; /** * Apply EventEmitter mixin. */ io.util.mixin(SocketNamespace, io.EventEmitter); /** * Copies emit since we override it * * @api private */ SocketNamespace.prototype.$emit = io.EventEmitter.prototype.emit; /** * Creates a new namespace, by proxying the request to the socket. This * allows us to use the synax as we do on the server. * * @api public */ SocketNamespace.prototype.of = function () { return this.socket.of.apply(this.socket, arguments); }; /** * Sends a packet. * * @api private */ SocketNamespace.prototype.packet = function (packet) { packet.endpoint = this.name; this.socket.packet(packet); this.flags = {}; return this; }; /** * Sends a message * * @api public */ SocketNamespace.prototype.send = function (data, fn) { var packet = { type: this.flags.json ? 'json' : 'message' , data: data }; if ('function' == typeof fn) { packet.id = ++this.ackPackets; packet.ack = true; this.acks[packet.id] = fn; } return this.packet(packet); }; /** * Emits an event * * @api public */ SocketNamespace.prototype.emit = function (name) { var args = Array.prototype.slice.call(arguments, 1) , lastArg = args[args.length - 1] , packet = { type: 'event' , name: name }; if ('function' == typeof lastArg) { packet.id = ++this.ackPackets; packet.ack = 'data'; this.acks[packet.id] = lastArg; args = args.slice(0, args.length - 1); } packet.args = args; return this.packet(packet); }; /** * Disconnects the namespace * * @api private */ SocketNamespace.prototype.disconnect = function () { if (this.name === '') { this.socket.disconnect(); } else { this.packet({ type: 'disconnect' }); this.$emit('disconnect'); } return this; }; /** * Handles a packet * * @api private */ SocketNamespace.prototype.onPacket = function (packet) { var self = this; function ack () { self.packet({ type: 'ack' , args: io.util.toArray(arguments) , ackId: packet.id }); }; switch (packet.type) { case 'connect': this.$emit('connect'); break; case 'disconnect': if (this.name === '') { this.socket.onDisconnect(packet.reason || 'booted'); } else { this.$emit('disconnect', packet.reason); } break; case 'message': case 'json': var params = ['message', packet.data]; if (packet.ack == 'data') { params.push(ack); } else if (packet.ack) { this.packet({ type: 'ack', ackId: packet.id }); } this.$emit.apply(this, params); break; case 'event': var params = [packet.name].concat(packet.args); if (packet.ack == 'data') params.push(ack); this.$emit.apply(this, params); break; case 'ack': if (this.acks[packet.ackId]) { this.acks[packet.ackId].apply(this, packet.args); delete this.acks[packet.ackId]; } break; case 'error': if (packet.advice){ this.socket.onError(packet); } else { if (packet.reason == 'unauthorized') { this.$emit('connect_failed', packet.reason); } else { this.$emit('error', packet.reason); } } break; } }; /** * Flag interface. * * @api private */ function Flag (nsp, name) { this.namespace = nsp; this.name = name; }; /** * Send a message * * @api public */ Flag.prototype.send = function () { this.namespace.flags[this.name] = true; this.namespace.send.apply(this.namespace, arguments); }; /** * Emit an event * * @api public */ Flag.prototype.emit = function () { this.namespace.flags[this.name] = true; this.namespace.emit.apply(this.namespace, arguments); }; })( 'undefined' != typeof io ? io : module.exports , 'undefined' != typeof io ? io : module.parent.exports ); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, io, global) { /** * Expose constructor. */ exports.websocket = WS; /** * The WebSocket transport uses the HTML5 WebSocket API to establish an * persistent connection with the Socket.IO server. This transport will also * be inherited by the FlashSocket fallback as it provides a API compatible * polyfill for the WebSockets. * * @constructor * @extends {io.Transport} * @api public */ function WS (socket) { io.Transport.apply(this, arguments); }; /** * Inherits from Transport. */ io.util.inherit(WS, io.Transport); /** * Transport name * * @api public */ WS.prototype.name = 'websocket'; /** * Initializes a new `WebSocket` connection with the Socket.IO server. We attach * all the appropriate listeners to handle the responses from the server. * * @returns {Transport} * @api public */ WS.prototype.open = function () { var query = io.util.query(this.socket.options.query) , self = this , Socket if (!Socket) { Socket = global.MozWebSocket || global.WebSocket; } this.websocket = new Socket(this.prepareUrl() + query); this.websocket.onopen = function () { self.onOpen(); self.socket.setBuffer(false); }; this.websocket.onmessage = function (ev) { self.onData(ev.data); }; this.websocket.onclose = function () { self.onClose(); self.socket.setBuffer(true); }; this.websocket.onerror = function (e) { self.onError(e); }; return this; }; /** * Send a message to the Socket.IO server. The message will automatically be * encoded in the correct message format. * * @returns {Transport} * @api public */ // Do to a bug in the current IDevices browser, we need to wrap the send in a // setTimeout, when they resume from sleeping the browser will crash if // we don't allow the browser time to detect the socket has been closed if (io.util.ua.iDevice) { WS.prototype.send = function (data) { var self = this; setTimeout(function() { self.websocket.send(data); },0); return this; }; } else { WS.prototype.send = function (data) { this.websocket.send(data); return this; }; } /** * Payload * * @api private */ WS.prototype.payload = function (arr) { for (var i = 0, l = arr.length; i < l; i++) { this.packet(arr[i]); } return this; }; /** * Disconnect the established `WebSocket` connection. * * @returns {Transport} * @api public */ WS.prototype.close = function () { this.websocket.close(); return this; }; /** * Handle the errors that `WebSocket` might be giving when we * are attempting to connect or send messages. * * @param {Error} e The error. * @api private */ WS.prototype.onError = function (e) { this.socket.onError(e); }; /** * Returns the appropriate scheme for the URI generation. * * @api private */ WS.prototype.scheme = function () { return this.socket.options.secure ? 'wss' : 'ws'; }; /** * Checks if the browser has support for native `WebSockets` and that * it's not the polyfill created for the FlashSocket transport. * * @return {Boolean} * @api public */ WS.check = function () { return ('WebSocket' in global && !('__addTask' in WebSocket)) || 'MozWebSocket' in global; }; /** * Check if the `WebSocket` transport support cross domain communications. * * @returns {Boolean} * @api public */ WS.xdomainCheck = function () { return true; }; /** * Add the transport to your public io.transports array. * * @api private */ io.transports.push('websocket'); })( 'undefined' != typeof io ? io.Transport : module.exports , 'undefined' != typeof io ? io : module.parent.exports , this ); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, io) { /** * Expose constructor. */ exports.flashsocket = Flashsocket; /** * The FlashSocket transport. This is a API wrapper for the HTML5 WebSocket * specification. It uses a .swf file to communicate with the server. If you want * to serve the .swf file from a other server than where the Socket.IO script is * coming from you need to use the insecure version of the .swf. More information * about this can be found on the github page. * * @constructor * @extends {io.Transport.websocket} * @api public */ function Flashsocket () { io.Transport.websocket.apply(this, arguments); }; /** * Inherits from Transport. */ io.util.inherit(Flashsocket, io.Transport.websocket); /** * Transport name * * @api public */ Flashsocket.prototype.name = 'flashsocket'; /** * Disconnect the established `FlashSocket` connection. This is done by adding a * new task to the FlashSocket. The rest will be handled off by the `WebSocket` * transport. * * @returns {Transport} * @api public */ Flashsocket.prototype.open = function () { var self = this , args = arguments; WebSocket.__addTask(function () { io.Transport.websocket.prototype.open.apply(self, args); }); return this; }; /** * Sends a message to the Socket.IO server. This is done by adding a new * task to the FlashSocket. The rest will be handled off by the `WebSocket` * transport. * * @returns {Transport} * @api public */ Flashsocket.prototype.send = function () { var self = this, args = arguments; WebSocket.__addTask(function () { io.Transport.websocket.prototype.send.apply(self, args); }); return this; }; /** * Disconnects the established `FlashSocket` connection. * * @returns {Transport} * @api public */ Flashsocket.prototype.close = function () { WebSocket.__tasks.length = 0; io.Transport.websocket.prototype.close.call(this); return this; }; /** * The WebSocket fall back needs to append the flash container to the body * element, so we need to make sure we have access to it. Or defer the call * until we are sure there is a body element. * * @param {Socket} socket The socket instance that needs a transport * @param {Function} fn The callback * @api private */ Flashsocket.prototype.ready = function (socket, fn) { function init () { var options = socket.options , port = options['flash policy port'] , path = [ 'http' + (options.secure ? 's' : '') + ':/' , options.host + ':' + options.port , options.resource , 'static/flashsocket' , 'WebSocketMain' + (socket.isXDomain() ? 'Insecure' : '') + '.swf' ]; // Only start downloading the swf file when the checked that this browser // actually supports it if (!Flashsocket.loaded) { if (typeof WEB_SOCKET_SWF_LOCATION === 'undefined') { // Set the correct file based on the XDomain settings WEB_SOCKET_SWF_LOCATION = path.join('/'); } if (port !== 843) { WebSocket.loadFlashPolicyFile('xmlsocket://' + options.host + ':' + port); } WebSocket.__initialize(); Flashsocket.loaded = true; } fn.call(self); } var self = this; if (document.body) return init(); io.util.load(init); }; /** * Check if the FlashSocket transport is supported as it requires that the Adobe * Flash Player plug-in version `10.0.0` or greater is installed. And also check if * the polyfill is correctly loaded. * * @returns {Boolean} * @api public */ Flashsocket.check = function () { if ( typeof WebSocket == 'undefined' || !('__initialize' in WebSocket) || !swfobject ) return false; return swfobject.getFlashPlayerVersion().major >= 10; }; /** * Check if the FlashSocket transport can be used as cross domain / cross origin * transport. Because we can't see which type (secure or insecure) of .swf is used * we will just return true. * * @returns {Boolean} * @api public */ Flashsocket.xdomainCheck = function () { return true; }; /** * Disable AUTO_INITIALIZATION */ if (typeof window != 'undefined') { WEB_SOCKET_DISABLE_AUTO_INITIALIZATION = true; } /** * Add the transport to your public io.transports array. * * @api private */ io.transports.push('flashsocket'); })( 'undefined' != typeof io ? io.Transport : module.exports , 'undefined' != typeof io ? io : module.parent.exports ); /* SWFObject v2.2 is released under the MIT License */ if ('undefined' != typeof window) { var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O[(['Active'].concat('Object').join('X'))]!=D){try{var ad=new window[(['Active'].concat('Object').join('X'))](W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y0){for(var af=0;af0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad'}}aa.outerHTML='"+af+"";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab // License: New BSD License // Reference: http://dev.w3.org/html5/websockets/ // Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol (function() { if ('undefined' == typeof window || window.WebSocket) return; var console = window.console; if (!console || !console.log || !console.error) { console = {log: function(){ }, error: function(){ }}; } if (!swfobject.hasFlashPlayerVersion("10.0.0")) { console.error("Flash Player >= 10.0.0 is required."); return; } if (location.protocol == "file:") { console.error( "WARNING: web-socket-js doesn't work in file:///... URL " + "unless you set Flash Security Settings properly. " + "Open the page via Web server i.e. http://..."); } /** * This class represents a faux web socket. * @param {string} url * @param {array or string} protocols * @param {string} proxyHost * @param {int} proxyPort * @param {string} headers */ WebSocket = function(url, protocols, proxyHost, proxyPort, headers) { var self = this; self.__id = WebSocket.__nextId++; WebSocket.__instances[self.__id] = self; self.readyState = WebSocket.CONNECTING; self.bufferedAmount = 0; self.__events = {}; if (!protocols) { protocols = []; } else if (typeof protocols == "string") { protocols = [protocols]; } // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc. // Otherwise, when onopen fires immediately, onopen is called before it is set. setTimeout(function() { WebSocket.__addTask(function() { WebSocket.__flash.create( self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null); }); }, 0); }; /** * Send data to the web socket. * @param {string} data The data to send to the socket. * @return {boolean} True for success, false for failure. */ WebSocket.prototype.send = function(data) { if (this.readyState == WebSocket.CONNECTING) { throw "INVALID_STATE_ERR: Web Socket connection has not been established"; } // We use encodeURIComponent() here, because FABridge doesn't work if // the argument includes some characters. We don't use escape() here // because of this: // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't // preserve all Unicode characters either e.g. "\uffff" in Firefox. // Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require // additional testing. var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data)); if (result < 0) { // success return true; } else { this.bufferedAmount += result; return false; } }; /** * Close this web socket gracefully. */ WebSocket.prototype.close = function() { if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) { return; } this.readyState = WebSocket.CLOSING; WebSocket.__flash.close(this.__id); }; /** * Implementation of {@link DOM 2 EventTarget Interface} * * @param {string} type * @param {function} listener * @param {boolean} useCapture * @return void */ WebSocket.prototype.addEventListener = function(type, listener, useCapture) { if (!(type in this.__events)) { this.__events[type] = []; } this.__events[type].push(listener); }; /** * Implementation of {@link DOM 2 EventTarget Interface} * * @param {string} type * @param {function} listener * @param {boolean} useCapture * @return void */ WebSocket.prototype.removeEventListener = function(type, listener, useCapture) { if (!(type in this.__events)) return; var events = this.__events[type]; for (var i = events.length - 1; i >= 0; --i) { if (events[i] === listener) { events.splice(i, 1); break; } } }; /** * Implementation of {@link DOM 2 EventTarget Interface} * * @param {Event} event * @return void */ WebSocket.prototype.dispatchEvent = function(event) { var events = this.__events[event.type] || []; for (var i = 0; i < events.length; ++i) { events[i](event); } var handler = this["on" + event.type]; if (handler) handler(event); }; /** * Handles an event from Flash. * @param {Object} flashEvent */ WebSocket.prototype.__handleEvent = function(flashEvent) { if ("readyState" in flashEvent) { this.readyState = flashEvent.readyState; } if ("protocol" in flashEvent) { this.protocol = flashEvent.protocol; } var jsEvent; if (flashEvent.type == "open" || flashEvent.type == "error") { jsEvent = this.__createSimpleEvent(flashEvent.type); } else if (flashEvent.type == "close") { // TODO implement jsEvent.wasClean jsEvent = this.__createSimpleEvent("close"); } else if (flashEvent.type == "message") { var data = decodeURIComponent(flashEvent.message); jsEvent = this.__createMessageEvent("message", data); } else { throw "unknown event type: " + flashEvent.type; } this.dispatchEvent(jsEvent); }; WebSocket.prototype.__createSimpleEvent = function(type) { if (document.createEvent && window.Event) { var event = document.createEvent("Event"); event.initEvent(type, false, false); return event; } else { return {type: type, bubbles: false, cancelable: false}; } }; WebSocket.prototype.__createMessageEvent = function(type, data) { if (document.createEvent && window.MessageEvent && !window.opera) { var event = document.createEvent("MessageEvent"); event.initMessageEvent("message", false, false, data, null, null, window, null); return event; } else { // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes. return {type: type, data: data, bubbles: false, cancelable: false}; } }; /** * Define the WebSocket readyState enumeration. */ WebSocket.CONNECTING = 0; WebSocket.OPEN = 1; WebSocket.CLOSING = 2; WebSocket.CLOSED = 3; WebSocket.__flash = null; WebSocket.__instances = {}; WebSocket.__tasks = []; WebSocket.__nextId = 0; /** * Load a new flash security policy file. * @param {string} url */ WebSocket.loadFlashPolicyFile = function(url){ WebSocket.__addTask(function() { WebSocket.__flash.loadManualPolicyFile(url); }); }; /** * Loads WebSocketMain.swf and creates WebSocketMain object in Flash. */ WebSocket.__initialize = function() { if (WebSocket.__flash) return; if (WebSocket.__swfLocation) { // For backword compatibility. window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation; } if (!window.WEB_SOCKET_SWF_LOCATION) { console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf"); return; } var container = document.createElement("div"); container.id = "webSocketContainer"; // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents // Flash from loading at least in IE. So we move it out of the screen at (-100, -100). // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is // the best we can do as far as we know now. container.style.position = "absolute"; if (WebSocket.__isFlashLite()) { container.style.left = "0px"; container.style.top = "0px"; } else { container.style.left = "-100px"; container.style.top = "-100px"; } var holder = document.createElement("div"); holder.id = "webSocketFlash"; container.appendChild(holder); document.body.appendChild(container); // See this article for hasPriority: // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html swfobject.embedSWF( WEB_SOCKET_SWF_LOCATION, "webSocketFlash", "1" /* width */, "1" /* height */, "10.0.0" /* SWF version */, null, null, {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"}, null, function(e) { if (!e.success) { console.error("[WebSocket] swfobject.embedSWF failed"); } }); }; /** * Called by Flash to notify JS that it's fully loaded and ready * for communication. */ WebSocket.__onFlashInitialized = function() { // We need to set a timeout here to avoid round-trip calls // to flash during the initialization process. setTimeout(function() { WebSocket.__flash = document.getElementById("webSocketFlash"); WebSocket.__flash.setCallerUrl(location.href); WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG); for (var i = 0; i < WebSocket.__tasks.length; ++i) { WebSocket.__tasks[i](); } WebSocket.__tasks = []; }, 0); }; /** * Called by Flash to notify WebSockets events are fired. */ WebSocket.__onFlashEvent = function() { setTimeout(function() { try { // Gets events using receiveEvents() instead of getting it from event object // of Flash event. This is to make sure to keep message order. // It seems sometimes Flash events don't arrive in the same order as they are sent. var events = WebSocket.__flash.receiveEvents(); for (var i = 0; i < events.length; ++i) { WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]); } } catch (e) { console.error(e); } }, 0); return true; }; // Called by Flash. WebSocket.__log = function(message) { console.log(decodeURIComponent(message)); }; // Called by Flash. WebSocket.__error = function(message) { console.error(decodeURIComponent(message)); }; WebSocket.__addTask = function(task) { if (WebSocket.__flash) { task(); } else { WebSocket.__tasks.push(task); } }; /** * Test if the browser is running flash lite. * @return {boolean} True if flash lite is running, false otherwise. */ WebSocket.__isFlashLite = function() { if (!window.navigator || !window.navigator.mimeTypes) { return false; } var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"]; if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) { return false; } return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false; }; if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) { if (window.addEventListener) { window.addEventListener("load", function(){ WebSocket.__initialize(); }, false); } else { window.attachEvent("onload", function(){ WebSocket.__initialize(); }); } } })(); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, io, global) { /** * Expose constructor. * * @api public */ exports.XHR = XHR; /** * XHR constructor * * @costructor * @api public */ function XHR (socket) { if (!socket) return; io.Transport.apply(this, arguments); this.sendBuffer = []; }; /** * Inherits from Transport. */ io.util.inherit(XHR, io.Transport); /** * Establish a connection * * @returns {Transport} * @api public */ XHR.prototype.open = function () { this.socket.setBuffer(false); this.onOpen(); this.get(); // we need to make sure the request succeeds since we have no indication // whether the request opened or not until it succeeded. this.setCloseTimeout(); return this; }; /** * Check if we need to send data to the Socket.IO server, if we have data in our * buffer we encode it and forward it to the `post` method. * * @api private */ XHR.prototype.payload = function (payload) { var msgs = []; for (var i = 0, l = payload.length; i < l; i++) { msgs.push(io.parser.encodePacket(payload[i])); } this.send(io.parser.encodePayload(msgs)); }; /** * Send data to the Socket.IO server. * * @param data The message * @returns {Transport} * @api public */ XHR.prototype.send = function (data) { this.post(data); return this; }; /** * Posts a encoded message to the Socket.IO server. * * @param {String} data A encoded message. * @api private */ function empty () { }; XHR.prototype.post = function (data) { var self = this; this.socket.setBuffer(true); function stateChange () { if (this.readyState == 4) { this.onreadystatechange = empty; self.posting = false; if (this.status == 200){ self.socket.setBuffer(false); } else { self.onClose(); } } } function onload () { this.onload = empty; self.socket.setBuffer(false); }; this.sendXHR = this.request('POST'); if (global.XDomainRequest && this.sendXHR instanceof XDomainRequest) { this.sendXHR.onload = this.sendXHR.onerror = onload; } else { this.sendXHR.onreadystatechange = stateChange; } this.sendXHR.send(data); }; /** * Disconnects the established `XHR` connection. * * @returns {Transport} * @api public */ XHR.prototype.close = function () { this.onClose(); return this; }; /** * Generates a configured XHR request * * @param {String} url The url that needs to be requested. * @param {String} method The method the request should use. * @returns {XMLHttpRequest} * @api private */ XHR.prototype.request = function (method) { var req = io.util.request(this.socket.isXDomain()) , query = io.util.query(this.socket.options.query, 't=' + +new Date); req.open(method || 'GET', this.prepareUrl() + query, true); if (method == 'POST') { try { if (req.setRequestHeader) { req.setRequestHeader('Content-type', 'text/plain;charset=UTF-8'); } else { // XDomainRequest req.contentType = 'text/plain'; } } catch (e) {} } return req; }; /** * Returns the scheme to use for the transport URLs. * * @api private */ XHR.prototype.scheme = function () { return this.socket.options.secure ? 'https' : 'http'; }; /** * Check if the XHR transports are supported * * @param {Boolean} xdomain Check if we support cross domain requests. * @returns {Boolean} * @api public */ XHR.check = function (socket, xdomain) { try { var request = io.util.request(xdomain), usesXDomReq = (global.XDomainRequest && request instanceof XDomainRequest), socketProtocol = (socket && socket.options && socket.options.secure ? 'https:' : 'http:'), isXProtocol = (socketProtocol != global.location.protocol); if (request && !(usesXDomReq && isXProtocol)) { return true; } } catch(e) {} return false; }; /** * Check if the XHR transport supports cross domain requests. * * @returns {Boolean} * @api public */ XHR.xdomainCheck = function (socket) { return XHR.check(socket, true); }; })( 'undefined' != typeof io ? io.Transport : module.exports , 'undefined' != typeof io ? io : module.parent.exports , this ); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, io) { /** * Expose constructor. */ exports.htmlfile = HTMLFile; /** * The HTMLFile transport creates a `forever iframe` based transport * for Internet Explorer. Regular forever iframe implementations will * continuously trigger the browsers buzy indicators. If the forever iframe * is created inside a `htmlfile` these indicators will not be trigged. * * @constructor * @extends {io.Transport.XHR} * @api public */ function HTMLFile (socket) { io.Transport.XHR.apply(this, arguments); }; /** * Inherits from XHR transport. */ io.util.inherit(HTMLFile, io.Transport.XHR); /** * Transport name * * @api public */ HTMLFile.prototype.name = 'htmlfile'; /** * Creates a new Ac...eX `htmlfile` with a forever loading iframe * that can be used to listen to messages. Inside the generated * `htmlfile` a reference will be made to the HTMLFile transport. * * @api private */ HTMLFile.prototype.get = function () { this.doc = new window[(['Active'].concat('Object').join('X'))]('htmlfile'); this.doc.open(); this.doc.write(''); this.doc.close(); this.doc.parentWindow.s = this; var iframeC = this.doc.createElement('div'); iframeC.className = 'socketio'; this.doc.body.appendChild(iframeC); this.iframe = this.doc.createElement('iframe'); iframeC.appendChild(this.iframe); var self = this , query = io.util.query(this.socket.options.query, 't='+ +new Date); this.iframe.src = this.prepareUrl() + query; io.util.on(window, 'unload', function () { self.destroy(); }); }; /** * The Socket.IO server will write script tags inside the forever * iframe, this function will be used as callback for the incoming * information. * * @param {String} data The message * @param {document} doc Reference to the context * @api private */ HTMLFile.prototype._ = function (data, doc) { this.onData(data); try { var script = doc.getElementsByTagName('script')[0]; script.parentNode.removeChild(script); } catch (e) { } }; /** * Destroy the established connection, iframe and `htmlfile`. * And calls the `CollectGarbage` function of Internet Explorer * to release the memory. * * @api private */ HTMLFile.prototype.destroy = function () { if (this.iframe){ try { this.iframe.src = 'about:blank'; } catch(e){} this.doc = null; this.iframe.parentNode.removeChild(this.iframe); this.iframe = null; CollectGarbage(); } }; /** * Disconnects the established connection. * * @returns {Transport} Chaining. * @api public */ HTMLFile.prototype.close = function () { this.destroy(); return io.Transport.XHR.prototype.close.call(this); }; /** * Checks if the browser supports this transport. The browser * must have an `Ac...eXObject` implementation. * * @return {Boolean} * @api public */ HTMLFile.check = function (socket) { if (typeof window != "undefined" && (['Active'].concat('Object').join('X')) in window){ try { var a = new window[(['Active'].concat('Object').join('X'))]('htmlfile'); return a && io.Transport.XHR.check(socket); } catch(e){} } return false; }; /** * Check if cross domain requests are supported. * * @returns {Boolean} * @api public */ HTMLFile.xdomainCheck = function () { // we can probably do handling for sub-domains, we should // test that it's cross domain but a subdomain here return false; }; /** * Add the transport to your public io.transports array. * * @api private */ io.transports.push('htmlfile'); })( 'undefined' != typeof io ? io.Transport : module.exports , 'undefined' != typeof io ? io : module.parent.exports ); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, io, global) { /** * Expose constructor. */ exports['xhr-polling'] = XHRPolling; /** * The XHR-polling transport uses long polling XHR requests to create a * "persistent" connection with the server. * * @constructor * @api public */ function XHRPolling () { io.Transport.XHR.apply(this, arguments); }; /** * Inherits from XHR transport. */ io.util.inherit(XHRPolling, io.Transport.XHR); /** * Merge the properties from XHR transport */ io.util.merge(XHRPolling, io.Transport.XHR); /** * Transport name * * @api public */ XHRPolling.prototype.name = 'xhr-polling'; /** * Indicates whether heartbeats is enabled for this transport * * @api private */ XHRPolling.prototype.heartbeats = function () { return false; }; /** * Establish a connection, for iPhone and Android this will be done once the page * is loaded. * * @returns {Transport} Chaining. * @api public */ XHRPolling.prototype.open = function () { var self = this; io.Transport.XHR.prototype.open.call(self); return false; }; /** * Starts a XHR request to wait for incoming messages. * * @api private */ function empty () {}; XHRPolling.prototype.get = function () { if (!this.isOpen) return; var self = this; function stateChange () { if (this.readyState == 4) { this.onreadystatechange = empty; if (this.status == 200) { self.onData(this.responseText); self.get(); } else { self.onClose(); } } }; function onload () { this.onload = empty; this.onerror = empty; self.onData(this.responseText); self.get(); }; function onerror () { self.onClose(); }; this.xhr = this.request(); if (global.XDomainRequest && this.xhr instanceof XDomainRequest) { this.xhr.onload = onload; this.xhr.onerror = onerror; } else { this.xhr.onreadystatechange = stateChange; } this.xhr.send(null); }; /** * Handle the unclean close behavior. * * @api private */ XHRPolling.prototype.onClose = function () { io.Transport.XHR.prototype.onClose.call(this); if (this.xhr) { this.xhr.onreadystatechange = this.xhr.onload = this.xhr.onerror = empty; try { this.xhr.abort(); } catch(e){} this.xhr = null; } }; /** * Webkit based browsers show a infinit spinner when you start a XHR request * before the browsers onload event is called so we need to defer opening of * the transport until the onload event is called. Wrapping the cb in our * defer method solve this. * * @param {Socket} socket The socket instance that needs a transport * @param {Function} fn The callback * @api private */ XHRPolling.prototype.ready = function (socket, fn) { var self = this; io.util.defer(function () { fn.call(self); }); }; /** * Add the transport to your public io.transports array. * * @api private */ io.transports.push('xhr-polling'); })( 'undefined' != typeof io ? io.Transport : module.exports , 'undefined' != typeof io ? io : module.parent.exports , this ); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, io, global) { /** * There is a way to hide the loading indicator in Firefox. If you create and * remove a iframe it will stop showing the current loading indicator. * Unfortunately we can't feature detect that and UA sniffing is evil. * * @api private */ var indicator = global.document && "MozAppearance" in global.document.documentElement.style; /** * Expose constructor. */ exports['jsonp-polling'] = JSONPPolling; /** * The JSONP transport creates an persistent connection by dynamically * inserting a script tag in the page. This script tag will receive the * information of the Socket.IO server. When new information is received * it creates a new script tag for the new data stream. * * @constructor * @extends {io.Transport.xhr-polling} * @api public */ function JSONPPolling (socket) { io.Transport['xhr-polling'].apply(this, arguments); this.index = io.j.length; var self = this; io.j.push(function (msg) { self._(msg); }); }; /** * Inherits from XHR polling transport. */ io.util.inherit(JSONPPolling, io.Transport['xhr-polling']); /** * Transport name * * @api public */ JSONPPolling.prototype.name = 'jsonp-polling'; /** * Posts a encoded message to the Socket.IO server using an iframe. * The iframe is used because script tags can create POST based requests. * The iframe is positioned outside of the view so the user does not * notice it's existence. * * @param {String} data A encoded message. * @api private */ JSONPPolling.prototype.post = function (data) { var self = this , query = io.util.query( this.socket.options.query , 't='+ (+new Date) + '&i=' + this.index ); if (!this.form) { var form = document.createElement('form') , area = document.createElement('textarea') , id = this.iframeId = 'socketio_iframe_' + this.index , iframe; form.className = 'socketio'; form.style.position = 'absolute'; form.style.top = '0px'; form.style.left = '0px'; form.style.display = 'none'; form.target = id; form.method = 'POST'; form.setAttribute('accept-charset', 'utf-8'); area.name = 'd'; form.appendChild(area); document.body.appendChild(form); this.form = form; this.area = area; } this.form.action = this.prepareUrl() + query; function complete () { initIframe(); self.socket.setBuffer(false); }; function initIframe () { if (self.iframe) { self.form.removeChild(self.iframe); } try { // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) iframe = document.createElement(' ================================================ FILE: res/tpl/error.html ================================================
Error: {{.error}}
================================================ FILE: res/tpl/file.html ================================================
{{range $k,$file:=.files}} {{end}}
  /f/{{.currentDir}}  
no {{if .isSubDir}} ../ {{end}} Name Size
{{$k}} {{if $file.IsDir}} {{else}}{{end}} {{$file.Name}}   {{if $file.IsDir}} {{else}} {{end}} {{$file.Size}}
================================================ FILE: res/tpl/file_edit.html ================================================
edit file
   go File List    view
js req write:
use_file("{{.file.Name}}")
================================================ FILE: res/tpl/file_new.html ================================================
new file
{{.dir}}/
================================================ FILE: res/tpl/layout.html ================================================ {{.subTitle}}{{.title}} | pproxy {{.version}}
{{.body}}
================================================ FILE: res/tpl/login.html ================================================





================================================ FILE: res/tpl/network.html ================================================
session filter
client: user:
exclude:
url:
#
host
path
================================================ FILE: res/tpl/replay.html ================================================

replay
basic
action url:
method:
Host:
header (only use these replay with pproxy)   show/hide {{range $k,$v:=.req.header}} {{end}}
get_params +param {{range $k,$v:=.req.form_get}} {{end}}
{{$k}}: {{range $index,$_v:=$v}}
{{end}}
+  X
{{if eq .req.method "POST"}}
post_params +param {{range $k,$v:=.req.form_post}} {{end}}
{{$k}}: {{range $index,$_v:=$v}}
{{end}}
+  X
{{end}}


================================================ FILE: res/tpl/replay_direct.html ================================================


{{range $k,$v:=.form.get}} {{range $_k,$_v:=$v}} {{end}} {{end}} {{range $k,$v:=.form.post}} {{range $_k,$_v:=$v}} {{end}} {{end}}
{{.form.basic.method}}  {{.form.basic.action_url}}
{{$k}}:
{{$k}}:
================================================ FILE: res/tpl/useage.html ================================================

Useage

1.Client(eg:phone)

set wifi http proxy:
proxy host : {{.pproxy_host}}
proxy port : {{.pproxy_port}}

android users can use proxyDroid to manager proxys

2.Server:user interface

visit Session List Page to view all the http request through this proxy.
in the session filter form, all text input can use| to enter multiple conditions.
eg user:,it mean user is a or b.
you can use replay to replay a request.

3.Modify Requests

you can modify http request.GET、POST paramas and http headers.
pproxy use javascript to achieve it:
function rewrite(req){
    //you code start
    if(req.host=="www.baidu.com"){
	   req.host="www.163.com"
	   req.host_addr="127.0.0.0:81" // send req to 127.0.0.1:81
	   form_get.add("a","a")
	   //form_post.set("d","a")
	}
	
    // you code end
    return req
}
req object has these attributes(url=http://www.example.com/album/list?cid=126):
schema : http
host : www.example.com
port : 80
path : /album/list
get: {cid:[123]}
post: {}
username : 
password : 
method: GET
form_get  : {add:function(k,v){},set:function(k,v){},get:function(k){},len:function(){}} 
form_post : {add:function(k,v){},set:function(k,v){},get:function(k){},len:function(){}}

host_addr: #the real host you wish,eg 127.0.0.1:3218

4.Modify Hosts

#all port
news.baidu.com 127.0.0.1

#only 81 port match
news.baidu.com:81 127.0.0.1:82

news.163.com 127.0.0.1:8080
================================================ FILE: res/version ================================================ 0.5.2 ================================================ FILE: script/create_dest_zip.sh ================================================ #!/bin/bash echo "bye bye" exit cd $(dirname $0) cd ../ if [ -z "$1" ];then gox -arch amd64 -os linux bash build.sh windows fi version=$(cat res/version) cd dest ################################################ if [ -d conf ];then rm -rf conf fi rm -rf data/* mkdir conf cp ../res/conf/demo.conf conf/pproxy.conf echo -e "name:admin psw:psw is_admin:admin">conf/users cp ../conf/req_rewrite_8080.js conf/ echo -e "news.baidu.com 127.0.0.1\nnews.163.com 127.0.0.1:81">conf/hosts_8080 t=$(date +"%Y%m%d%H") rm pproxy_*.tar.gz pproxy_*.zip ################################################ target_linux="pproxy_${version}_linux_$t.tar.gz" mkdir -p linux/data mkdir -p linux/file/ cp pproxy ../script/pproxy_control.sh linux/ cp -r conf linux/conf dir_new="pproxy_${version}" if [ -d $dir_new ];then rm -rf $dir_new fi mv linux $dir_new tar -czvf $target_linux $dir_new rm -rf $dir_new ################################################ target_windows="pproxy_${version}_windows_$t.zip" mkdir -p windows/data mkdir -p windows/file/ cp pproxy.exe windows cp ../script/windows_run.bat windows/start.bat cp -r conf windows/conf mv windows $dir_new zip -r $target_windows $dir_new rm -rf $dir_new conf ================================================ FILE: script/pproxy_control.sh ================================================ #!/bin/bash CUR_DIR=$(dirname $0) BIN_NAME="./pproxy" DEFAULT_CONF="./conf/pproxy.conf" INTRO="get more info from github.com/hidu/pproxy" CONF_FILE=$2 if [ -z "$CONF_FILE" ];then cd $CUR_DIR CONF_FILE="$DEFAULT_CONF" fi CONF_PATH=$(readlink -f "$CONF_FILE") cd $CUR_DIR BIN_PATH=$(readlink -f $BIN_NAME) if [ ! -f "$CONF_PATH" ];then echo "conf file[${CONF_PATH}] not exists!" exit 2 fi RUN_CMD="$BIN_PATH -conf $CONF_PATH" function start(){ nohup $RUN_CMD>/dev/null 2>&1 & status=$? if [ "$status" == "0" ];then echo "start suc! pid="$! else echo "start failed!" exit 2 fi } function stop(){ list=$(ps aux|grep "$RUN_CMD"|grep -v grep) if [ -z "${list}" ];then echo "no process to kill" else pid=$( echo "$list"|awk '{print $2}') kill $pid if [ "$?"=="0" ];then echo "stop suc! pid=${pid}" else echo "stop failed! pid=${pid}" exit 3 fi fi } function restart(){ stop start } function useage(){ echo "pproxy useage:" echo $0 "start|stop|restart" [conf_path] echo -e "$INTRO" } if [ $# -lt 1 ]; then useage exit 1 fi case "$1" in start) start ;; stop) stop ;; restart) restart ;; esac ================================================ FILE: script/windows_run.bat ================================================ echo "start pproxy,don't close it" @echo off pproxy -conf=./conf/pproxy.conf ================================================ FILE: serve/assest.go ================================================ // generated by goassest(0.5.3 20161126) // https://github.com/hidu/goassest/ package serve import ( "bytes" "compress/gzip" "encoding/base64" "errors" "flag" "io" "mime" "net/http" "os" "path" "path/filepath" "runtime" "strings" "time" ) // AssestFile assest file struct type AssestFile struct { Name string Mtime int64 Content string } // AssestStruct assest files type AssestStruct struct { Files map[string]*AssestFile } var _assestDirect bool func init() { exeName := filepath.Base(os.Getenv("_")) // only enable with go run if exeName == "go" || (runtime.GOOS == "windows" && strings.Contains(os.Args[0], "go-build")) { flag.BoolVar(&_assestDirect, "assest_direct", false, "for debug,read assest direct") } } var _assestCwd, _ = os.Getwd() // GetAssestFile get file by name func (statics *AssestStruct) GetAssestFile(name string) (*AssestFile, error) { name = filepath.ToSlash(name) if name != "" && name[0] != '/' { name = "/" + name } if _assestDirect { f, err := os.Open(filepath.Join(_assestCwd, name)) if err != nil { return nil, err } defer f.Close() info, err := f.Stat() if err != nil { return nil, err } if info.Mode().IsRegular() { content, err := io.ReadAll(f) if err != nil { return nil, err } return &AssestFile{ Content: string(content), Name: name, Mtime: info.ModTime().Unix(), }, nil } return nil, errors.New("not file") } if sf, has := statics.Files[name]; has { return sf, nil } return nil, errors.New("not exists") } // GetContent get content by name func (statics AssestStruct) GetContent(name string) string { s, err := statics.GetAssestFile(name) if err != nil { return "" } return s.Content } // GetFileNames get all file names func (statics AssestStruct) GetFileNames(dir string) []string { if dir == "" { dir = "/" } names := make([]string, len(statics.Files)) dirRaw := dir dir = path.Clean(dir) if dir != "/" && strings.HasSuffix(dirRaw, "/") { dir += string(filepath.Separator) } dir = filepath.ToSlash(dir) for name := range statics.Files { if strings.HasPrefix(name, dir) { names = append(names, name) } } return names } // FileHandlerFunc handler http files func (statics *AssestStruct) FileHandlerFunc(name string) http.HandlerFunc { if strings.Contains(name, "private") { return http.NotFound } name = filepath.ToSlash(name) static, err := statics.GetAssestFile(name) return func(w http.ResponseWriter, r *http.Request) { if err != nil { http.NotFound(w, r) return } modtime := time.Unix(static.Mtime, 0) modifiedSince := r.Header.Get("If-Modified-Since") if modifiedSince != "" { t, err := time.Parse(http.TimeFormat, modifiedSince) if err == nil && modtime.Before(t.Add(1*time.Second)) { w.Header().Del("Content-Type") w.Header().Del("Content-Length") w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat)) w.WriteHeader(http.StatusNotModified) return } } mimeType := mime.TypeByExtension(filepath.Ext(static.Name)) if mimeType != "" { w.Header().Set("Content-Type", mimeType) } w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat)) w.Write([]byte(static.Content)) } } // HTTPHandler handler http request // eg:on file system is :/res/js/a.js and request is /res/js/a.js // http.Handle("/res/",res.Assest.HttpHandler("/")) // eg:on file system is :/res/js/a.js and request is /js/a.js // http.Handle("/js/",res.Assest.HttpHandler("/res/")) func (statics *AssestStruct) HTTPHandler(baseDir string) http.Handler { return &_assestFileServer{sf: statics, pdir: baseDir} } type _assestFileServer struct { sf *AssestStruct pdir string } // ServeHTTP ServeHTTP func (f *_assestFileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { rname := filepath.ToSlash(filepath.Join(f.pdir, r.URL.Path)) f.sf.FileHandlerFunc(rname).ServeHTTP(w, r) } func _assestGzipBase64decode(data string) string { b, _ := base64.StdEncoding.DecodeString(data) gr, _ := gzip.NewReader(bytes.NewBuffer(b)) bs, _ := io.ReadAll(gr) return string(bs) } func _assestBase64Decode(data string) string { b, _ := base64.StdEncoding.DecodeString(data) return string(b) } // Assest export assests var Assest = &AssestStruct{ Files: map[string]*AssestFile{ _assestBase64Decode("L3Jlcy9jb25mL2RlbW8uY29uZg=="): { Name: _assestBase64Decode("L3Jlcy9jb25mL2RlbW8uY29uZg=="), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/6RT3W7iRhS+n6c4km8rflJVinxVVXmAlbbqLfLC7GLJ8bj2UIqqSLCCLLRm7e6G/ABRS0lU2irAVUL4y8t4ZswVr1ANQ1rSXFRq58Yz53znO9/5Zqz954U0eLYcxyXfliCHDwlkif36OUIuDf2PrkjjQRgtO9GsL8Jj3m2y73uiXRV/jFjQRw5xKeiwn9pPIaSJYU+Ex6Llry5/+QuznvvRZJqKewNxNWUf/F2iaPEgTgaiM2Gh/zepkTs07ReKOYUQNamFQYc8tiyyHRnZhJpZGd1u1EeqbY15c8huzvnJUnSGbNFaz33ZYXQfTd+zxUfWaK5qTbEYinY1vhuxZRXlDGocmC7okEgk5SGJkLa6vODlCj99F81ud+nQa9PCB6arg0TLQxL9oy+7+o23xuu5n4omUz6eRPc1dnPO3g7W87okaTRZ8DvrDlbvmiwciXaVn92y8kU0b8cPnbjnKy4+qYnweD1vbPS9pMTFB0YJdEh/hpC2a2M8vIpHFX56z+YB0ohDTWJ7+nc2saVD/OxnBfjkleGZWT1PqQObLahIhrolnY0v41FL5r6QMVXxCDDskh7NZrwasEnIgrsjZBRo/suSo27Alsav3vajacfFXxewR9kH38WeQ2wPq2HUYMqFHYlgWJaU2CjzbgMAiG2VMq9cYuSyhifvnwU/rsoVVh972PNMYoNlelQ61mgqsiP02Oil8Y2U85QCIW1byepncW/Af7oW3R+UJqTBVggoJbrSEU2nLBjFv1bAdDLEzRQ8LN9GvPzIatf8pi968u2YDq+fxuWazLLwvao4QttuX5m4CLokRUjDb0AaqyeT6VQinUrsJfb0T9N7+0Dcx4RtHGLd8YqfP0Ug7V8AQPMYNgINOweOV4SiaVngGJ4HNO+Swps8ULJBOYaLbQrq/1GHF3Kv/wkAAP//AQAA//8Vmt1TlAQAAA=="), }, _assestBase64Decode("L3Jlcy9jc3MvZmxhdC5jc3M="): { Name: _assestBase64Decode("L3Jlcy9jc3MvZmxhdC5jc3M="), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/+w96a7jNnev4iYIMLcjKVq8Gxm06J/++Z4gmAK0RFvCyKIqyTOeGHr3QlwkLodafG+6AEUQzBV5Np6F6yHtJRUpE/KjcFFVkR9uVnzHVY2fZ1IluHLPpGnIzY1JTqrjr9F6fdjgf8puJakaVDQnDtWQ0gbSoievWYf7c4xODX40boJjUqEmI8WxIAU+uT/w+VvWuE2Fijqj5V64qU/ad4uOKfmOKwcdLyS+1zNItwKU3Js8K/DRb73sdnUrci8SnIh2VijJ7vVxWz5YdZPeb+cCZfmzREmSFdfjunycOgJuirNr2hwDbxeGYXg6o/jblVITKrhcLlwxx7B8rGqSZ8nq13MSR/HuZPCD2o7yfNW1d4VRjd2scMm9OU3VJ1ld5ujnMSuomOecxN9ON/Rwf2RJkx4D3//txGVH94awdsbkdsNF87yQonHr7C98DDZGQ8MTq25+5viYNSjP4tMNVdesOIbr8rHy2zRw0tBJIyddO+nGSbeOlwaOl4aOl0aOl64dL904XrpljC7oluWdpCmusoZR/8G47Xxf4x6cmF45dJsGq/qG8txJQ/FHJP5Yiz824o8t/8Pr0bwez+sRvR7T61E9gSu8DO/wAcd9W59MBZ3zHyO/fHCV8JChemyFOmRYqmAIliuHK2LtU7V6aSAZZxt0gJ1WQ6l0E3WlnZ4jqXTtMwG8dC2VhoeutLPFRi7dUwEGC7HSdfloS9k39pYg4N7gr/wVbYuXY5Q81UZSHaksNVrr7Trcb3aB4hCR77f/csNJhj7dsoI78267Lx9vT8ZmoBn5nu93MdxyEzL7DQD76DeFZ+j5213r0Y7jdm9wImzNopXXiD5xiG9eUVbZDVU/1W6oRUol67EESLA5bHcJR/+BqiIrrj3dIF77F4HOK1X0ODgkfszRE1Rchyq8W8dRLLBZnYp8PkRJ5HPk+h7HuO570BDH8S4Q2LxSRQ83KNqgXiUX8uy7/MM+OQvcrkZDRDuELq13vuoKoz2l0X+yIaRFEgInaMKGYXiJEkpbtMgESi4XP9kzgmrTTNg4wOE5ogRpGwFqB5xcdoya1FgTEF2SA8aUlLA0MFjElz2OGDXV5ADsDsfnDSXIbQ/AhAlOuPIUJzBB8fp8OB9ar0TXLiBQgisx2vUd07rvq45bv+vqVzSMldnBMRhGOd5H3nOH5E+gk0v00o5cmzROkjyBjsUe+UnupqTK/iJFg/JV0jz5KNdJ2eq1fVeU40tzDPYdTIvO5+rPJmty/NWhfyeoQS6psmtWoNxlNU+zpQlpGtwP6C0dZ//zTpp+zkSZRLpOTmIi0XWS/irYDpr1uVolWiu52w19o6vcbE7aYKENKd4a32R6rEOUCoy+0dq5m4O/OiDrXI5nfCEVNpnximdMigYXzfGXXyRcr7znuVt1nHsvrJgcnapEEVWu30+j2BBh6Fq2hA8z4bKiS0N7ql4ilCRV140AgxfkoDFJsPPtnDhlhZ0a3UplhvMPUqCYOP/ARU6cfyNFTXJUOzG5VxmuVgX+4dxIQeoSxZhS6meb3cRxq4yX+81vJ9FH7cLNGkMzz8MlvKy1OWY3iH87J/NIj/XIJt3TmTzcOkUJ+cEm22U1NGGvOjid9kjeFlm8TfGt2bNr1e7S7PpHmjXYpRo+lhVuvbLCbh1XJM/ROcfPboLMJYjojMk7NwWP5KMvC7wW8ksxF9+rmlTHkmRFgyt9OgMoTChnWz5WQSjN/hmjGyFN2tXX93OZPXDuoqLJUJ6hGifQSoFxYIuBjjuqHKonpURXolx5+khiVHl8iUb/1JZeJ3kG1dWjuMm+Ywrrsb8lYNFczckMp6PYSVZ39kworT/F11fnkuE8qXEzFK2YhQ3H4gs09lVdz+hTuNk44n9vt3k7kRLFWfPz6O1OlyxvcHVEeZmiT7z8j53/RqX58meco7r+jz8u98z9+uyjIOg8gHBtVzhHXYtVrznRJQL3Q/dRA+Qkn+wIdgg+A0+vAHhXHwqCCb6ge96MT8DE5FdCkIzaF1HjqkWSPUWZJ8pIiYvVsN3QkOs1x/OFilF8SUIRUkph+4EyDIyPv6IAbdFBY8kKFZaq84nSaScUkA5IDFD5gM8rJwgDJAbypvkk8qxyirxJYiAPmEGiz2unGEC2FKDeKAdvHgfR61i7A9X4cmTMWsvwJaGMIBtFFEmKFEVy63jZDDeet8AKd9tzoDWNFbYfKIMcSnyHTmUJKEcLJV46I5Q4pAMSA1Q+O5TsVgNDySQ/Hkp2D4BDyaQ/4ehjtgRDyeAwFUqqZwChNGl8eTfBNgOleww9tGwO+i3pj37LLeoKZvjttBSbBCVYH4NYYft+1nK8hPE+OOsqY4UDJy1YuqIZkdKBOSYNXaOzAwQyBxgaGtXxuICMCkeERnbCWWELgYGgEp6KAsnSQAgw/9W7XMmp1c082P34Fp8ErwzwrEQek1mJMk7SojnzoTnyxDt/G2mNYoXtRwkgB0W8XgeRzo8Vyvz06RgtnDMbo4AORMnU8/ypmMVQ8ERMpz0xD7OY3DIL04lPTZGs1oOnYBr5yRmY7AnQ9iT1dt27pBDQtrBhH+Ub2zKCbAtRJKlQFMlt42Uz3HaWUJt9st0n+jhCC9sPlEEZUHYo2fr6gEILFZZa8PDSGdHDIR2QGKDy2QFktxoYQib58RiyewAcRSb9CT8fsyUYSAaHqUhSPQM6JaFBoBtfigz9JAr0Wn4+JSPIRhFFkiJFkdw6XjbDjecJtU6iQB8TWGH7gTIow1CCdr4evaxQYamFEi+dEUoc0gGJASqfHUp2q4GhZJIfDyW7B8ChZNKfcPQxW4KhZHCYCiXVM6DzNRoEur9JkWEeFtuPOGUEdQrNipT5LytSJ6i0bNb6YoZQ62Bz3m31ZRotbD9QBnWZE+GNMSrRQoWlsdKhpbMWOxTSAYkBKl+w6rFZzbL20clPLX9sHmBbBOn0J5crdltaVkMah+kFkewZ4JpIPlUCIgPfzqSucfIETgKyosbNyl+5IT2Vpvv1vtP95wWbt9NsSJWT0mJRyHU0JsVsIUZk+JEl+DkcdAdrXz/2jOQSfvQpjqzcPCu+PYG9la5c9lL6PSd77l4kuMqzAjhopGdGJapw0QxMZocOJNHswDDF5xu1YPIfRUmv/ZlkEJWPVaglIoVmwlsIJOrxlkrE/PKxCg7qsebOzGoaZl46sfrWEzt0tKKpI9JovwFOdymtR/2UDxc1scw2+vtIIxUJUk1WPrUULNURA1WDh/A3hkgP2p/ykWtBqhvKWy9GFW5EggRz8D0/hx7JNLXml4oKlpVAz4A1OqxmSXKpCnch1e1YxyjHnwLP9wPn7eTeamudpbylI+G9XDEFOP0nc6W+uEDfz6hyL9kDJ7wFwyC6UrXHG9grsVWJPSWd9TBKvgQvlnTfGXUobb0a5zhunpaMUiWpp+t/2HnjP/9Rl6j4+oVjK4VK/o3fQiDsWHbIVRVA/B/uW0IkJsslJ6hhRlYYnBgZluYKkBEZKmCdlhGy+qXPrG26jggmyJFyjKrjmTQpCDWridLk6YaLOwAukwlDfzAYreBd3spjJ9QuKTsPf1LF0J6v4tkL/Og4iGwEmDsxcJhNDrOhmUR8fNpDePUNxosGPFCqDk+WCpb9Uc+hThu/seGPctHJi2TSLqIGVt0Ad8nJj2OaJQkuhoN/dK5Jfm+wIkw3eqE8uxbHjgKzjK5zIZaNEk2Uos3yfztJGceu2QBtgg6lwcPw1CelWYrulxroKmmePEmGn7ie1BAWY5YYmEeJ5dmxIM2nfoL69oVfCFAStc3cDd9fv00Q/oJoq4QppyX5gjxSDqlqbIKm+5OJxiVV0LwNvk1xS3jCmpEjLuN9kdZJo+R6uC9IM89ADxp0jBwseWi+XC5t58eowmgsezGUE6E23bwnKB9tVpT35s/mZ4n/qDGq4vRrP+FGZYlRhYqYDejS5ZIcnbGhktPIZYbQW7deN067XQdfkfzo3shfbpmjGKckT6Rs6fAcxxsNeAK6TxAKDCa1Sxu4gBNv/CI0bybc03o/RZ1mybmMOWqI8+84/46bLEbOv1YZyp0aFbVb4yq7jGk98Nbb3UlKDGS5b7x2LefBabYeuS8zkRb2fyFbjpqkqy49uqRZKUZyVI9gix7oEP30Qs6cTFraR1HLK4wSUuQ/4f0V1aGAndnL9qJvJyWb5JycT9qXOkDMyrJTvP6So0bVjLJCNWBFSjyY22NC2zXfeiw865uYBHRDgJrl6U8t6tbbEFgg8qkhSF/Gp2OO6HZ7cIfPsG/3vMnKHH81CPE7YLQ0v4rStSw+W+Bupha4YaQvSg35NQYy/hqQP7/a5B8IyfKnfekmMuTfTq32o2A/Jb/GQManl680+VOr/Kkmf4pqsee88lKcl2wR4KgV3BtdOtxpdZ3IRIdPcfztTB4QqMtWcxYMXqtdSVJhlbif3haf3ydZuViH3Un57CM2F04ascfYjwzfc2SYGssnaUAD+zRjqON6l1FYA+iI5aIkIcUc+4M5/IwwrqpuTNQdnxcDbs9rJKcXsIrLy2CKw2vQmruL7AIJEnL2kcyEZXoFeFj9dUI2u6NzwTRHh1mPuPk0/yknn6AAufgUU8jB32EIq3uPWXzEvfnBvOngfQXg4n2d5OQDvOLmKqji6AaG5uri9F+BhZx9JHdgmY5BLlavnZTP7vBcOM3hbexHXH6ODFNOP0kDcvtpxpDjv8soVtcfs7/N9Xtv198VUDePuxLlupe8InqaF2VUbLZHQxGku556RBn1PKpMvD6yYJQ+uqyYIsKknTf9VuZwjtLVbsEGSCqQN7VdWYFsv89ld+1HKPQb3WMwkxveM5CljW8YWpilblCTxU9YDRbDq2uyz9xXs5gUwFYov+7Et0T12X7ULV++46rJYpTzDddbliQ5Pmk7VurGDnwPcbjSG7CDXR866RMzf0VwZQl0kBYt7GpwR2tYndkxAxVzo2LWNwVz5K6vfLTcEfIFIaCToLSAnkqbJcpg8lRVGWtlIDHYw1sTckts2xQKzNSWhZ1gvzv6GZCS71mMnYnP28GQe9z0+kXb+tFrjf55HIRuBp+b4m9eF1ukt8CYbZgBCLZkagFukcsKZUo2C1TIZlvtj6pnBNqmqJkousrkrRIGkY/6Wz7tbzqIzvJv2keySG+BmeVvs1oytWFlkcsKNdPfbLKNu4NFPSPQC/1tSmWmv9W3MX/raif8TQfRWf49264W4S0ws9xtVkOm9nctclmhZrqbTbZxb7CoZwR6obtNqcx0N7aAUbufUPWHYe1BZ3LS+XuM6VsOEzeC5Udvljzt9r/tCEodR4B1oN7vT4LUN2g1qcSewlZf2WZV3bhxmuWJY9KxV9PnJoZK5rjjEFoShAmdI4UcTT6QCt94NoJK5k07M2c5V9wrfPncXK0ZU8rAE9KJrRZowjjApEJ0BTONSKV642lmGdR2pQI6CR33MivU0uvH44xFVyOeC/gsf0wKNIotvxa1WKiVYcsxQPjQGF7SviTJEo0oCPMeT1gmhEgtXiiKQFvIjCfoL2XG0RYyE9npS7kJvGXsvBfZTdwFWepSMJUPdbTRBzH+30b/XTaa/WDIQh0NSXFLtTRgLmMpZ9Qs5DlxqWmhZSASCwVa7JL69apXlf5aj2xcQ3ndACP3WF4akj7AGK/pxLiS9rJJXhq3jMs97zCJ/XbQSwP3R5jkJZ2Y1/hetslrI4d5VfAdVhm5Cvja8PkRdnlxRDVuP75qGO+9hnlVgnl3NF+baHyAYV58KApe2Sx7fV/dyVH2JPbDUd0rU0iRPbpwZTJnQTFrjrl0hvd6S0cfYvwfFXv2243LZ4tLp2mzV8mLpk+vzVXe65Jjs4fXhur3BsDo4PniOPV+zx0bOF7so1/rRkefrhzb8O27VeUAItB7Rn0TGEQLDTR9YxhECw5AP2zsOz6B+6y+lhwTQoS07dCneQHWV66SglTmbeqZww9MSG7cZ0+65qfdmZVx+a/XGPr8PHLKZsORdKLsFkLb16H0+L+xg80SslT/erfUM0ksasRurBE709vf3YiZJBY1YjPWiI3eiJ7lvLMWq4QSBiSivKuv+4peN89V5sk710uWiL8bEX+2k8wTf65/LBF/MyK+3T1GnVq9teq3Us4kTZHUf/AjNB4wCcEH0IFj0tFjT7NSe6Zf+62MQdAVbR8Xl30MD85Lv2smEiBZomyP7WUxKWqBzr60Z0jU283SdeITf5oAzlgUTxZQSPMwWh9YjWRF9XcI3q1Qo80rj/keS5aT26/WGGg1jkmRgHhSlf7UBE08BVTFdcg0NpqCJ97cH9LA4VS8wPffgNbOFFsQ92HiMmkx06bE+AXED7QTvwdvmsmsWC41pA+g5rlI1+yPbp1F6YlP/Q0jHRxoIlS1pJEDvtlMsG5OQ9WT1I8MSWmFykQU3/pLCyKvdov3GAH4an+mFz+n0U1LQHUL3WIgAVgDrFxk636V1hsWVIJWPamMMfccg3mn7CMqerfnGm3lD71rGlNLn9o7bjqyHvVasTX6NThYBovRrMT0QNLLLQElVnNWehbpLAE2SW6i0RMuK8hTpM/i/otg9rn/S/mNSjo/bwjJ+7fAbLOrcL/tIb1McrChtCGl/sNyB4l+V78SH+xnaJ9TD3+x2dKBPXRGr13w+zXK4l3hwn71C+bDVk8zODFu8ts6BzG1Y6/sCG5MIOWey96st4gz9hYa36UQ0hgNppMjN5B5dXUWTnL2ka3dBq++3XynRW64mxUF+GuDnLrhPEOWeAhOffk7Q/tIesVsSNdlvw+4IkX+c1XHFcbFChXJ6pMYbm9Z4Sb4exZjl/2eFn0d8Bi+OUtRAm8DId3IX4uYEBt09Psy+PD3YKE8cAs+Ajh8e3o5uWbKeUx2Q1d8vFf5J8/7nX7Uv1Og7iN2w4dXFte3tv0vAAAA//8BAAD//+dedruCegAA"), }, _assestBase64Decode("L3Jlcy9jc3Mvc3R5bGUuY3Nz"): { Name: _assestBase64Decode("L3Jlcy9jc3Mvc3R5bGUuY3Nz"), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/6RW3Y7bNhN9FX0xAiTASpDWdr6NhAC96hO0V0UhUOTIIpYmBWq0tkvo3YuhqD/baS+KIIaWnBkOz5w5w194w2wHGH36/bdf47dPRcIVMOv8b14ZbIYGz+qlMuLmzsyepM7TomVCSH3K02Gn2YerpUKwOVNtw76YlnGJtx/f069F+M6T70XF+PvJml6LfFfX9RKimKOuTQ6Hb7zaF9woY73DsDuD7t1FCmzyt/Tz7BaxHo3PI+qV26yuDlGyw7jDm4IYby3k2miYnCIlXa0Mw1xBjbNTlrbXojYa407+BXl2aK9FZawAG1t5ajDP2mvUGSVFZE8V+5K+0L8k+1pU5hp3DRPm4m3SKB0tXo/Hl+l/kmVfx+gX8NH+n6YFwhVjpuRJ5xw0gh2S8cSylrZDF46nNP/76dP1dxSu7PqKAI6UzBvzAdbdFeyfrSPmQqnGwq2gjZgTsmsVu+VSK6khrpTh78VYySOBvK6yBrwY+x5jFaFwH2BRcqYCKGjaYcc6KSAQYb//XJyljse/DhRtSC7Zaxr2s9ewcphXDmHlOK94rx03WgPHskOGfecmFnzb5oesUjDVgRulWNtBPn1M/OiI974/EqzKLNivSrbjnBc+VqzYzfSY1/IKorgYK+LKAnvP/W/MlApIZWn6+aGLRrrHlUE05/xIN6MDI85alEa7B0JtI6T1of72wMJw9RhNSyGLZtx6Pc4NcH+TcO2QRrh1xJZu3M+ZoY2weVm+xYz0/qfhPWumLNIlVANMjAHdv3bS5uKwBwYwhSFtC2FWbr7Hn8E1wTOKwII51sbg1Aac8yFBUXK64ZO2RlHauy0fb0iw5I8O/5Pn1lhk2hvYB7ftvlrvU7+ut1umQREn13gc4RWqWfkO1A+hEctR3Mva2HNUS1CiA3RbDKi6O6zK4OJWopm114mkRKf4bWtKEJB+1Mpcci/ZvLedsXlrpAdq508oudEIGt0qFAG/3Y18QxG9nq+L+8SeqcvWs7VTs9OsE/KjXHIvaabcZX9m14moGRChSZzCwqhOaJMOFHAEsSkBr2FI/OFhFgU6UPlW02lI+Ho2Jw1p4aSvYait0iuF/JgI/shuT8NeuWU4+hCR1CSxT0Z0cKCB+dxnQEEmDetoRMytna4IuLGIRn1cngAz1pVI74b5PPqHhMgYU42sUW4SBlLq5WEyU3Ua2UzIvhuH+oPKnCy7UWUsEI6hhyvVU0mgK1GU4f2zVJd0qJiL33FrlBoSYkhVyqcTb0iUIfJ23AJot2ZGSsHCRPPf25s/6seuEj9DZyPfe6+WBFeZjm3glmmybDwRvmD2lj7vk8lV6rbHP+hJ9YNC/LkKfzecXteZiESxDoPx93SpXHjb+LSNBkLPrZR/QbyRQoAe5PnktufEpEZ/AwAA//8BAAD//+I2kz/XCgAA"), }, _assestBase64Decode("L3Jlcy9pbWcvZmF2aWNvbi5pY28="): { Name: _assestBase64Decode("L3Jlcy9pbWcvZmF2aWNvbi5pY28="), Mtime: 1448806296, Content: _assestGzipBase64decode("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"), }, _assestBase64Decode("L3Jlcy9pbWcvZm9sZGVyLnBuZw=="): { Name: _assestBase64Decode("L3Jlcy9pbWcvZm9sZGVyLnBuZw=="), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/wCMAnP9iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAN1wAADdcBQiibeAAAAAd0SU1FB9sFCBErNh1XQGQAAAIMSURBVDjLjZLPS1VREMc/M/d4n7osqKBFuSwjoR+uMhemIUEQuGnXvl2tgsD/JqKINklRVBAiRgZCFrUoiIxELXq+fOa998y0uPe9rlDUbA5nmPn+YuAvNTV9j/8pmZqeeR5Fh3EQABdCmry+dX38yL+Wp6ZnkMkrN3zi7CQec1SFJAk8ePLM0zQVJEEBA1QBB5EOSePNzWunB8OP5jpb7Q0W36+jKAN7+xgdHhJDcMDccFdijJgLZgbaw8LLF4cBwsa3FZqtTYqtTRKFdx9bxDyytrbMZvM71rEmpYLoMLB/N4tzj8sMDo5c8kPHR/nwaY1EBRB+brU4N3aMC+fHMPN6ZIDRCAmWpLxdevUonDg1ytXLF2m1t1EVANyF7e2MxaUVstyQapVSBCEIEnp5euf2ePCkn9mFLxRZ1h0AUBE8gR4tQ/QKRIBoTkMU0UBwF1SAij0BYgfIIANC1ZOamcIcHIJ7+YnmCFAAShmWSLmUVYDaAXYwcwp3grtj0Smi7/Dp3ch+l9csFtFwc4KIoJqQqHUtdNiseqUGVvYdTRIUIax8bXJ/fgm3YgeT1lT4H844pH0sf14lHNi3i6Gjg8Qi7zLVpdZ7VgNOG33Mrc4TNtrZwyy3iSJ3RMC8DLKH6u4rSXkFFARwJxJp535X2HPyDEU+AgjuiiC4S9e+O4hUAqQUJ2I4Tm//7C/8V/xJTO95pgAAAABJRU5ErkJgggAAAP//AQAA///J9dz9jAIAAA=="), }, _assestBase64Decode("L3Jlcy9pbWcvbGlzdC1hZGQucG5n"): { Name: _assestBase64Decode("L3Jlcy9pbWcvbGlzdC1hZGQucG5n"), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/wA0Asv9iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAABuwAAAbsBOuzj4gAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAGxSURBVHjapZNNaxNRFIaf+zVJ2klMad24SBBdmLWCFUQRROg/cOci+gPEjRtbdNOVYEH/QBFc6UKQUhduxC7qBwiCVLQBFURxTDM2k3Tm3jtCRXDhREKf7ct5OLycI/I8Zy9ICri2fvnumYetwdlHR6Lld0t3KKBwg/Orc37u4DmRuiGvoxf29sn7ZqwNXn59LrwZUqpo3m690RRQGGTe8XMnQUhJ6hz/EwhAAfZPYJ0nTgcIBJl1/5pzQK4B2ivtxU7cufLsy1MdKE2gFUZJkmyIFIIdlzH7oJlXjKESGI7uP2EbtebNS4cXru6WaBbN94vHLkyjHLnyCCl+C3y8K6jrOlJqSoRE/R5x8oNscjO6d/rVjAaw1rIZddCBxpQVtVKIkYK+20ZJMLkhSRM2og/0BgOmgiozQfpXB575x++f3EJj0GC0JFCK2WaLySBg7eM6ZaMpaU011JggzqzV8yPvoLxk8lOHWkxNlFnrbPCp3RNj3YHznsxlSAGZ9xShRwm+bffIhce6sQXXZSOs55+7XdHtZ4R2nx3zmRb88enqMjA8MNHYqvnajZHPtBd+AV8ir6y+hjXrAAAAAElFTkSuQmCCAAAA//8BAAD//3j7Bz40AgAA"), }, _assestBase64Decode("L3Jlcy9pbWcvbGlzdC5wbmc="): { Name: _assestBase64Decode("L3Jlcy9pbWcvbGlzdC5wbmc="), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/+oM8HPn5ZLiYmBg4PX0cAliYGAQAGEOZgYGBg1d/mAGBgbmYifPEA4OjtsP/R8wMDBwFnhEFjMw8F4HYUYnjdkVDAwMkiWuESXB+Wkl5YlFqQzl5eV6mXnZxcmJBal6+UXps9/ZSDEwMHgE+IS4/v//nyENhKLmQMmoOf/9/f3Ly8sZ0v4rVYG4ENLf3z85Oblw1f/y8vIHDx48evTo5cuXHz58+Pbt258/f3g6ps5lYGDgKwnyC2ZISUtLT89saNixY8ezZ9bPUv4yMDC4ero4hkhcnnpuu+ABBQaWg+wtgoeUL9cvV1L60ZLIvcOyYeaKB6+eCHS5/2v7vrM6W/K4ZsLllI3LLF9Zan1b/OpSjdSPkDy3+sDj7EvvzPm8hoGBgcHT1c9lnVNCEwAAAP//AQAA//8A3/5bNQEAAA=="), }, _assestBase64Decode("L3Jlcy9pbWcvbG9nby5wbmc="): { Name: _assestBase64Decode("L3Jlcy9pbWcvbG9nby5wbmc="), Mtime: 1448806296, Content: _assestGzipBase64decode("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"), }, _assestBase64Decode("L3Jlcy9pbWcvdGV4dC5wbmc="): { Name: _assestBase64Decode("L3Jlcy9pbWcvdGV4dC5wbmc="), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/wCrAlT9iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAbrwAAG68BXhqRHAAAAAd0SU1FB9sBBwINDzIP4PcAAAIrSURBVDjLlZPNSltRFIW/k3uTKAkxQXQo1KgDpaWTDCuOKxVRH8JH6LCDCsVCCzGd+w59gA4zKXZQqBQMWLAIyU1sfszPzT1ndaAR06al3cNzzlpn7bX2NgCzs7Pper3+EPD5t4qSyeSXwWDQHB080X8W8JR7P3oArw8P2d/fp1at0u50sNYi54j7Pr3BAN/zmMlmmZubu8OYW4INSR8G/T69bhfP8zDm5srEYtgoIp5I4JwDIJVOY4zZAt6P9fyuVGJre5tOu00YhkxPT9Pv9/F8H2stM5kMrVaL/PLyHWZMgY0iwjAEwPdvuI0xYAxyDklgDIlEYrKCUqnE5uYmnetrrLUk4nEc4KzF8zxyuRzNZpN8Pv9bLBsjd8MwVBRFkiTnnKy1CsNQw+FQvV5P3W53lMIzfs29WCyyt7dHEARIIpVKEfd9nEQQBKTTaVrNJg8WFyd7wK1cE4sBIOeoNxqUy2XkHNY5pqamVCgUzPz8fAH4ONZCsVjU94sLfTs/V6VS0cnJiQ4ODu7Pj+u023r86NHztbW12EQPnLWy1mo4HKrT6ej4+FiS1Gg03NfTU62urr5YWFjwRsAxD46Ojtjd3aVarZJKpXDOcXl5CaAfjYbZ3tl5FdRqL2tBYP+ewq3jQRCoXC67bq+n5ZWVN7lcLvGn7VqXpEqlIkmKokj9fl9XV1c6OzvT0tLS20wmk5wE9AGy2ewnY8w6EJvwpp3NZj+3Wq3hJIKf+1KUYNzdR+cAAAAASUVORK5CYIIAAAD//wEAAP//kff/kKsCAAA="), }, _assestBase64Decode("L3Jlcy9pbWcvdmlldy5wbmc="): { Name: _assestBase64Decode("L3Jlcy9pbWcvdmlldy5wbmc="), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/wA4Asf9iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAN1wAADdcBQiibeAAAAAd0SU1FB9sDCwAbGenR2McAAAG4SURBVDjLjVM9b9NQFD33+RkTBkPt2ApyMpE0UVQ2xgyN+A90ZmAGJJb+hwqJja0j4icwwMRvABUydEhd4diJMKEhcfwuAy74I5Y40l3ux9E9576nDe6PKQzOmwDaAJo1YQOg/nC8imbnyIMAONPpxUvPcyxAKeyEEBfTYNHutJ8BCPMVCcDyPMfqncrRJCITXJoloGtz/PWx+zHbpEJAgFKTiEx+EbzFWugFAkMldOIeAbwFcA8AA5j3hw+js0/vWf5tZABroc+vtAKBdV1TVxu18I/pTjPOyxG7FFMWRRs2W/H67qh3qo28truXyYHA/yLVJT/5/G4SCTMzm649qIBrkj9/SVleTRb2NlRilQcNlVT1VAiE6Noc04l7BM4ZwP/OiGS3XAmAVUral0eLDyR3PyTekvi+0TSdAJQ6BIC5fxnMUsNsbOVe48f6hlY2gXRWt3nJt2x9WfFgcHAYdjre8+zkvWXoHztvWg/CmIxCM/2R1LU5BoTIp/Pop2nySqziFWrlCME3zYYm9acAzspnnPt+MGu1XKfWdg249L+FAKJKbXBwSNn33QfQr4l9AM3+cEwA8BsG1aBqp74kfgAAAABJRU5ErkJgggAAAP//AQAA//92SZjSOAIAAA=="), }, _assestBase64Decode("L3Jlcy9qcy9iYXNlNjQuanM="): { Name: _assestBase64Decode("L3Jlcy9qcy9iYXNlNjQuanM="), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/5xUWXPaMBB+z69IeWCkMQEsOQ5ElmdIeh9J2/RMaTNUyOCGCGrkXIb/3llJHMmEkPTFmtXufrve79Oed7LNvc5YhgEvTk7l1ZHOdkutvf2nz56/ePnq9Zu37w4O33/4ePTp85ev374fd36Lrkx6/fTP6eBMDUd/s7HOzy8ur67rPqHBdrjTaHo1XqpIJYZduZvkSuh0qFCqRrnGBZQb5nqUa14qMbBEP/Mrop8R+FDI8+FD4GPMwISlvM4MBrfdVk9ynTRObBmHzi766UCiNDJmdSBVT/dxARW4vRL9TrY/7MqWRqnnYQZ1V7voChf0yAE1jgkYhCMEZpniKArwBAwSx4GJpNZJyv42jiJivTSOQ+MNAIeWQ8rSBKXjg86BCca4MKkmIgzYVA7GcnM5hNoQ691wI7WHt6H76bjq2DTdtzSCprG3wkPwyiS6MinAbLqRSZ1nynHKppWu/C/ezdV67i0fmRwNOkKi2o9fra3jztZ1favZ9tq1Nv9Z61VKpVVCMLzd+JlUdeXlYYIWPDuOseX1EcH0McHBg4ONeA15VjzQlpGWkS4ytpVWYL00jolTr/FSo8oQT8w4bwrlSGep6lWTbHi27zRuhIxBjZD7hIcBLh6UREANNi14RBrFbDrX742nbZWErAuz20Jbfv8LuY1NFVzYk9tjIZh21lYgkbYqYSOtXCdaXhpRJsMMwZXidaYil2mlw5TnWQ2LGeTSTlBmXCLySQMXDtDjd/4vzGj2lJGIfbKDy2UkIlIPGnhNMhKwNCZ+k2C2JrAcUjyBdly59cA+wRNCgnXItocF/MP7mG8KlzJn8PbCcH47bkejWxmwBywJsH8Jr8/fuUtarHw+u1ne3cs8WeSVNKWed4uqpu+oIgHGhSB3VvB8zO5DhplQ375HJAjMB2pxMiPqHlxB73aR9SXNfgCKkSvqGqDzBugSQxaNTaf/AAAA//8BAAD//7lfkqMcCAAA"), }, _assestBase64Decode("L3Jlcy9qcy9kZWZhdWx0Lmpz"): { Name: _assestBase64Decode("L3Jlcy9qcy9kZWZhdWx0Lmpz"), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/1TMQQuCMBiH8a8SEmzD2D3EU/UFqmMgs/1nQ3k35msp4nePAoNuD7/D4wa6sw+0iTGFcarY1FU/RMkmNWA1b9fSzpOVGWNkk2AypesPiBaTDS8Su/UkoWbvJHSL6RAsynKvZuiY8ATxEc4MHUtV8MP3ugefDTW4YmQpbix+3uF7O5Et8/zfLmwSF8uiiuUNAAD//wEAAP//UCCwb78AAAA="), }, _assestBase64Decode("L3Jlcy9qcy9qcXVlcnkuanM="): { Name: _assestBase64Decode("L3Jlcy9qcy9qcXVlcnkuanM="), Mtime: 1448806296, Content: _assestGzipBase64decode("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"), }, _assestBase64Decode("L3Jlcy9qcy9zZXNzaW9uLmpz"): { Name: _assestBase64Decode("L3Jlcy9qcy9zZXNzaW9uLmpz"), Mtime: 1478614292, Content: _assestGzipBase64decode("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"), }, _assestBase64Decode("L3Jlcy9qcy9zb2NrZXQuaW8uanM="): { Name: _assestBase64Decode("L3Jlcy9qcy9zb2NrZXQuaW8uanM="), Mtime: 1448806296, Content: _assestGzipBase64decode("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"), }, _assestBase64Decode("L3Jlcy9wcml2YXRlL2NsaWVudF9jZXJ0LnBlbQ=="): { Name: _assestBase64Decode("L3Jlcy9wcml2YXRlL2NsaWVudF9jZXJ0LnBlbQ=="), Mtime: 1448806450, Content: _assestGzipBase64decode("H4sIAAAJbogA/3TUyc6jOBcG4D1X8e/RL4ZMZFELGwwGAokZA7sEwhggEMCEq299X0nVXeouL189R7L0Hp3/fz2INN3+n4wcT1d1GXjoO2UsXVfaVZZB98gB1SHIdQOYcAL+wHqHbrIAr8lur7n6faMQBCHxgaVFi7wCA+Z2wEAQeeBZRJbjU0QjJSAEK2BBcRgXabjwOrJfKa4XvIL0awCCzlKb9JVqhRCVu4q5i/x805516h5pgo358Ru2fsP/YSmDi8S2PJ/aHlgtz18sxdqEX1nlU1ux6K+sgo1F3lQm37/UEDWiUEEBY0FLA4KPIKWWowVNdA3eqQyV29XZWY5E8c8BU4Fxe7s6XRrqUyQex5NYvGLsz0yC4ee+Kf6JlT/hX1bXoV4BG+Z1zxR1qR0pDwFBKgBnGRAJfAE5N2VAEOCN9/UNS+6d5bLTZw/3kWXC7dk4ZZrxNG6ZauTEYjSban1OBpfxbDmYIg/O+vYmj6ecXc3uZUi3R037RyfOjVQf8SgtYXLd5wd9HzGxjZSgGtTLM4lTFqtZLOojPvARMIjeWrhH4TDYsu1wbYrbJ8lZ7A+A7dINrspHTpiJOzdBcLm5WvkZgZocydrLU3QZzXM8y+3ehJfrprneB9iGGQ/c13poU+fiZNGFt3x+YkYWZHZSbbmnMrvK2VSDu+8+9pdh2SbzFtcJmS5tvvb4vH089wBmfBwaFaRzrIahsL0yyBCMRTwrRhOxoN9wn/4YH62qnQL4PpCqw3rvW9MaPuSD5cLYl3fLziW6AgiAnQAo41U/tw6TLVJz4l+xfe7m5cTaILjm0ymYMZvs/Rcb+RR/rbrDVxDmVO3At2X+hBXwjT0IfAoogtz6s/e/a/e/a2e+um4E81xNjmk4lZ+7SR1lgvzShsE12cj5JO/XJfNyCcUC7yHn2FVaJS3qjkRhvuMZJcrVftndw1PzGfU1Y893AZR7KhxL9jRPZieyM6hgI5EsGPqBo+mq6AWUK3YuI668M4ez5qEKXKxa2B6TD9dMrDTe9ptUG67C7q7DU+ZuxXZsvWdmB6Xop6M78vhew2KdpGfHUG2DPRre1dNH8jDXFN50U9FR7k9r5AP29KlFzlDMtsDOq1YueVXGdpmSTLlIvG7ZHCODZ8JlQg0DQcVn3yX5eitdsys3n9c7YLP2ZCN7ZZMKIpTr5QZy6c3B7Bj0ZC44yWFOc+Sp6vYOkrSbopL/iM/HNcPFi/74wXxfPWQr/76EfwEAAP//AQAA//8Ir+SsJgUAAA=="), }, _assestBase64Decode("L3Jlcy9wcml2YXRlL3NlcnZlcl9rZXkucGVt"): { Name: _assestBase64Decode("L3Jlcy9wcml2YXRlL3NlcnZlcl9rZXkucGVt"), Mtime: 1448806450, Content: _assestGzipBase64decode("H4sIAAAJbogA/2zVpxKkUAKFYc9TjKe2yEmMgAs0OTRNdDSZJmd4+q0dvcce87vvP/+bIL1U68/b4/84bzXgP9IfXYr/PZCpqtLEqwLP64B3JR7V1mgVGmQtK/Cey8IryhJLu/7d5CV6JkO7IXi96X37dLuGlCgEN4uOo7ytkinYjAp+9HHS2LT4nXMx4kfP/jhlY68wi+iKUek4sSQxaBfZ6bIkhxUZKhNc3RQGjXnNVQdTmaVwWSxgvZEhV4bOrWDFX3h4zAmlbYrK3RG7DwIn9V7NvfFyBnHuM4M9djbdTg4w0LrgREQffRdhCEuU96aHGfK38y5jBzV9dN9gvrSylkQ68fBEW4fk4Ot7Be0sF5kdpPLL3N0ZqmdWbLLoaF4o0STUWuE8EjkMMTKSNEy7cFvU+hjmZwKB7plLOLMd9kBYGbcdFXX2zf0JC8CYnpD4gLooz1VF3uUFflQF3gWsNFTgKbDQNuQfNPyqFUnoOw9xjqqjakhhn5/2m6ouftYamXv1S1H7uLq6WY2ZP4sT8P69Wcedmi0GREgv2KE/6fK2LnteB8fR67VV7nYmn8BIIn2I37Bbcz5A+plJl+0C9mbJZiIH4SIYNwr9Vq7Jac2XW6VYJvn3ra3YNVFkM5Ca3aTRJn/p4H+vmdoHVsZAh1vXOwaVGh72i7JsyE8CZ2vp8Vb8m7Q1gqisn6RhpBKLOIrKaHOyS93sFZPoaZSznneRt3TYPvBcmJ4CGpI123UNna0GPGBj3XAt/5tvE6mNrlekqlGS+h66JE74aoiEkaqOTYfjeA2sQbTeqQqFIeU0BU7x40vgneJgSzqMifWaImWyzCXSWwagn6CENfcreR7RmpPVw1gj4Tt/cVwB0VNkfkMFYQ42bHOjUM2XbE2rilvg+bpwVpWLgnqC1ThP0U3TKxGXZn7tN148LhFPOzSZ5kLpN3BgAYwm5eiJ+8HncG5ubBOo3nFMfyUpVfu+OOpFUIdPmD0oHOZfsVV8oeh8yWoPRPkBFNGi7CuBCbO2WmOFaUVkKJvPDbv3Tk2BrUZMO8rDr0eMTjj1zjg+KEgo2LSjggneu0TDTWLu2+6JJnJg1Wscy60pRAWMt6Aa89KPniKjaQDub7cWWX65XgdJBhtU4kfOhupMNi29koyJ5gJ/9U/ah1hNEOinr4HBjy8+IMN9wnCXSTonO1hDnMYA+kYOR33e91e0sk9k9tNG1Hukrt2kcmK/yU/s53qmsDa3vZJ2aReRykKMx5F3rbFELUFRxlGolAQD4x/YUMnW/sqe4kXoCDbrGK21VRT2bYq+KU+cGwtwSCKJqwYu7uIkl/egGNWUL+KfVbk9VXqM8NkR8AcFVcwvSLGTE40rMJM/w/cXPlHNwk1V1yrL9qjI6/qrhmBsBxdHzL0xwh/JbHZGWFTvtO+Oyhg3/THrGcmKR52C2X3wKcHsQVIIkGQHe292S0ESsGkCJ3Vdk++fS3iHQ2F1IDw+ay/hoTN3v+mhx+7ipq58Ig953e/aRHQr7+G6Sa4QfDFj6OpC5QJfzdZl4RxKBytrfJpD8ZLviX8vweN1RDaG9xdJiEZCZRRUzFqO80fIoP74oCEGBlROo34jVOcH11X+09+xhwxjpucKlvO63ajkOuY0J6Lxukzs+b6te3Tf5wJhdoSZARbkPiWuAD5VicF9cqKzjfdTPjVJR6GsTpmNRbwVITh0nCLF6u9f6B8rkiX+f27+CwAA//8BAAD//xldrVWPBgAA"), }, _assestBase64Decode("L3Jlcy9zanMvcmVxX3Jld3JpdGUuanM="): { Name: _assestBase64Decode("L3Jlcy9zanMvcmVxX3Jld3JpdGUuanM="), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/4SSX6ubTBDG7/MpfPdKiZj3rhBZSulNKRRDcwotIrLGMW6y2fWMq2kw+93Lxj/Hes6hdzvz/MaZecaikQfNlXSqCtXvW4pwRa7BRXj2OoTn4AiaDlrFkF3q9KCqm9UDhfzIpSXu9854oc1Vqv4Xb5G+oFDotgyds8OlMyNKYDngo398TugrJT4na0JCs7LFhcJLOptSZae0BFEBusP8Xjhx8/EWoJV6sqkhLbgAOprj2mjHdOl144uOjzUhK1veoJhyQd1ktUb3f/+DRykpta62mw35OOpbd8ztdt+jn7/SL9H+iazJptiQ9dTLrD7/2D9F39Kv+xWCbvDhUWhWy5vNfVbZyevsEhKuUXaiEq5OlJ3goN2F3xPJEB/YJ0R2m1F8oOJz4nUMMeYJ7cOYD/73TeyNGGJoxjH79OtBZ463TDRQ2xPbiu4Ieju5LdkFRsXpwdjmEuPXS85vmbBrvEA0bplIjM/y/B2UtkzYs/HC1bcKVPFXE0pJI3MouIScvPXpYUvzTv1/lPSGv1U8RWtCEvv/vmSCqqlL64tn/BzE0o4cBGhY2CFALjlePB6UTkssrAwEyKMuDYgaBsWdf/V+jxNvhIwxfwAAAP//AQAA//9Fj23NIgQAAA=="), }, _assestBase64Decode("L3Jlcy90cGwvYWJvdXQuaHRtbA=="): { Name: _assestBase64Decode("L3Jlcy90cGwvYWJvdXQuaHRtbA=="), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/3yQsc7aMBSFdz/FldUBJBSn6gaOB7p0RAJ1RY5jEoPxtexrKEK8e5U00O1ffb7jq+/Izt3AdQ1vu5orBrJNQjEZwXidc8OHH1zpFgtJEcc4ql+Hww5iQkKDHnTQ/pFdBkL01Qfa6mw7wADbPehkBkfWUEl2dU+OyAZoH9Cj16H/39klPFtDsAapYUj21PCBKOa1EL2jobSVwasYXFdEjAn/PDiQTr2lhh9br8OFq695KbT6XPtZUrKB4LdN2WFYgzxhIDDoMTWtL1Y9n9XtX/h6STGmUxvG+jRZsPfjTByzR+Lv36cNmRSduyk2Lzq/ZZNcJMWypYO7Wiy0OJVgyGFYLJ8M4FvVW9pP1OLjP0pUs5TDWUeYwZpLdc58uWGv1fe6rpcbJsX7xF8AAAD//wEAAP//Hz2bTdsBAAA="), }, _assestBase64Decode("L3Jlcy90cGwvY29uZmlnL3JlcV9kZW1vLmh0bWw="): { Name: _assestBase64Decode("L3Jlcy90cGwvY29uZmlnL3JlcV9kZW1vLmh0bWw="), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/5RTwW7bOBC98ysG3AViA4EY2UA24FLa06KXHnsrCoMWRxJTiqOQVBzX0L8XlJ3GSFGglQBR4nvz5j2SUsY+Q0xHhxVvyFGQXdBHXjNPCSVT5GqmnK3HKYFqySdYWNXeTVgLYTtPAZXISA06QeoRWhtiAmc9QiI4UxbgMSrh7FlwihhuIjxGsBEMtnpy6ZbCwusc7bU7c9npZFvwlKCw8SN11s/zIqA09AHbiguXZ3m9DEroOndFYxMcaQrQTDHRAGOg1jo8i55O6M08M6bEElAY+1wzta8DPoHBgaQS+7cOj/pZxybYMcl/OZBvnG2+Vvzv1c1fY8BdwKddLrpZF4m6zuFqzevY00H01mB2xNQYEKyp+DWfvy68sXF0+ig9eeQ1m4IDCX1KoxTicDgU+KKH0WHR0CC020+DcDam/xprqnJzz2LT46AvJaynmEDCuzo2UsjTD3ds1KkHCVdKrMMk4dRYIz+Xm+2XmY0U88zM8jZ5PSBIYKOO8UDB5PcBU09Gwof/P7Hqzy4mRE4PDRmUKj9rZttVwKcie68qnr3vtTVTds7XJwYAP+AFLe+3C3aN7LQxoeLl5p/iLt/yoeQgBET0JpPysXgFS/lQ5tqWwrDrMBXamBXX/JZrvs6AEAuU16GImFbcXLBZibPlS4oN/DJCr6ncbN9lmIKr+GVvv/XW6Lekgv+kvv199SniLp/wFbfe4EvRp4GvlyRTPP9+MelkG8gksB6MDcDzx1VbJcaQhz6IWu2DqL8DAAD//wEAAP//KQqcVCAEAAA="), }, _assestBase64Decode("L3Jlcy90cGwvY29uZmlnL3JlcV9mb3JtLmh0bWw="): { Name: _assestBase64Decode("L3Jlcy90cGwvY29uZmlnL3JlcV9mb3JtLmh0bWw="), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/3ySwYrbMBCG73qKQaTQwm58aqFZWedS+g5BiUf2uLLkSOPsBqN3L3Ls7NLDXgwa//N/vzSj0mg8nJ1JqZbdD6kjXiDiayRGVZWfWsxzNL5F2NHTrk+Het+nlLNQNsQBYEDuQlPLMSSWYM5MwdeyOgdvqZVgJg7nMIwOGWsZrJXAJrbItSQbj8VDaqEsoWsSshbKYYu+0fO869OeiR3mrKq1KuzkF8QW8mvEy7dZKMY3NhENJL45rOUrNdwdfn7/8tIhtR0f7n59+rUccx7fJHgzYC37JFfa6vk7FeLmqEVEnmIhXl7UKVZaZKHIjxMD30asZUdNg36zKzUJV+Om1fszcfk+xPcQpZRzaWvoul2nhHk2jlp/OKNnjFILMc9kYben9Ce05PN/qdJ0GojlNtwTezixfx4jDSbeHtBkrijLkNElzFnAgl2bFix5G6R2hQE2RLgbq6qh69Lnm4K+H4UK7sPG9E+7KeHRksNDXS63nRaQIy0AAJSBLqJdHmAT7Kfocn5fluPJGf93GdRDcn8pVRktQFXF7T1NiSFU9WGvqrJrD8k/AAAA//8BAAD//+1fByb5AgAA"), }, _assestBase64Decode("L3Jlcy90cGwvY29uZmlnLmh0bWw="): { Name: _assestBase64Decode("L3Jlcy90cGwvY29uZmlnLmh0bWw="), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/5yRQW7CMBBF9z6FZYTULopDl+D4FN1HTsZJBsWOOx4oEeLuVQJV6a7t2v/Ns/43uSFMLDM1pdLksz5kDb51x4E3h6ys0beEFSbzNHgrVs0YW+zeXD142Y4ULskBYOx22yKd98FRh/GFsOt5J1+LdL4Ko++sqUlbYQBPEqFUNRTKCsPLqQ8E7ku1LYq1Wl4fPEuKrGGwl3WYKozNcAQv7xlN/r2av7LpOQxKSrm+Gs1gjWb6BQo+jP9A+zFzfvT+FX3w/kT1UokVwmBLLngZXfClwpYWnZJLn6UCzGlw0y6O0e/nsW5xK6UwGvA0X/jaLyUaz1PFrq7yMT2p1dz+s/ge+BMAAP//AQAA///wACsmDAIAAA=="), }, _assestBase64Decode("L3Jlcy90cGwvZXJyb3IuaHRtbA=="): { Name: _assestBase64Decode("L3Jlcy90cGwvZXJyb3IuaHRtbA=="), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/yzLwQ6CMAwA0Pu+YukdMR7n2M0PmbSSJkshbYMo4d8Niff3MvIaGQd4IkTzT6MBnDbvauNJ0kjipPfXLN4ZfyndrssGJTxUZ00h21Iljq2a/du7qrBMUPb9Qic6jtyfqoTcI6/lBwAA//8BAAD//7dAZHVxAAAA"), }, _assestBase64Decode("L3Jlcy90cGwvZmlsZS5odG1s"): { Name: _assestBase64Decode("L3Jlcy90cGwvZmlsZS5odG1s"), Mtime: 1478614292, Content: _assestGzipBase64decode("H4sIAAAJbogA/2xTwY7bIBC9+ysQivbUenYPvbRAL71UqnrZD4iwGdsoDraATbal/HsFxk6T+jTJzHjmvXkPpvSFaMVpo6iomJfNiKQdpXOc+ub4kpKtnL2ejKhycymO2HkqqifTuPlLBR2EULdv1qLx37SNkZQKk2Sw2HEKnR7x6zRzg9cnpS1/+IAKps89cbblFCw60OceRu38R6lUPZueCgZSVAyUvqRwg+UHlEpUhHkrKkKYH8hVKz9w+ul5fqfCTAz8UEophKA7Umv3+tbk3aniZmnuyT1AN/KM96D/oGvljDFCXQMVdQ0JIYM0almDRuXpP+UZ05IbjBXhy3OG+Kp/Y6kySDTSn8yK+WZSv0QVgpWmR3I4fTgkPJ95nYKLsdp4q026Y0tFCIdTjAy8KtUUCNlohZAH1T+0OaX752o+zZL/7hYld3TpplGhXVQJAUeHMe60eXz3W1M5RVqxzE9HKamsbPpRXLOPJKfJHYcdV92Gb/pQsUdix1zL3oXP7ja4B0+Jl7ZHz+mxGaU57Zr4ovH6/45yjn/lWWcnM6zCLW5Y+xkUNzDIT3V9Dn8BAAD//wEAAP//rhRf0ccDAAA="), }, _assestBase64Decode("L3Jlcy90cGwvZmlsZV9lZGl0Lmh0bWw="): { Name: _assestBase64Decode("L3Jlcy90cGwvZmlsZV9lZGl0Lmh0bWw="), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/3xTTY8bKRC98ytQrXY1I+009szOatdDs4eVcooSKdc4suim7MahgUC1PzTxf4+w2844SuaCGnj1XterhzR2w62poTGg2HHXOp1zDd3foNBY4kvrUApjN4rJZUg975G6YGqIIRNw3ZINvgZRcMBJpxVSDXaZFgVdWK2PA3HaR6yhs8agB+51jzWECHyj3YA1ZL1BUOw1dFnfJ7uy/lL1/FwV3eqd7vFrR707HAoJk0436FS5K1czKU4H1/SEO3pJ/irt2ZjS1V0bPKXggGfaO6xhaw11s4fJJO6AR02Eydfwcb6d383FvPr0PP1z+ngAHp1usQvOYKoBV7Pp/UNFO+Ih8fvJ9K/JP0JX6xw88IRfBpvQcD1QWIZ2yJwsFa1+yMT7gdqOjwJnesX+8E2OT6eVSc27hMtxNv8d+7zqDHOrIx4OoqpASduveE5tDSJhFrZfCWczVdGvQK0Cf2Md8rc2kxT610LipUBx7RyIReO0//xTmY3F7UmmfB3pj1FUTJYJ6YT62ud/H39/Ap7CNl9G9X/whJ4+hG0uquNQ29MpqGvUOFIpzvSKnRMeEyq2zsV+vk2WcMZkGwyqIeOiUNzADx3eSnEEMCmOxbJFT5jUVdLy0PSWrsLOy37MVEOeN+TvYrK9TntQUowsTIoSuGOmx1/MbbKRXni4zsLgUg+OqnUutSfEBapYjCns9gvSzSIP8QZ+awzcsu9AJu0y6R5H2y6P92y7sTk6vZ/54PGpKJzgivNvAAAA//8BAAD//yR4z29BBAAA"), }, _assestBase64Decode("L3Jlcy90cGwvZmlsZV9uZXcuaHRtbA=="): { Name: _assestBase64Decode("L3Jlcy90cGwvZmlsZV9uZXcuaHRtbA=="), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/3SSb47TMBDFv/sUo0FIINGm3WURdGMfgQtQVDnxZOMqsY096R9VvTtymwQqxBdLjn7z3rznlMYewBqJlUElxO1adzolie0XVI6O0NiOysLYgxJl42MPPXHrjcTgEyPomq13EovMIbCOb8QSbRN3mUYlSuvCwMDnQBJbaww5BKd7kugDwkF3A0l0dLxt8H/Y2DjTl8vS2Hi93kY6XVGnsv933dOmLO4fxAQVD6JMJ54k8zlr4pQ8772ovePoO4TE544kHq3hdvO8WoUTQtDMFJ3EH9vjdrEttsufl/Wn9csVIXS6ptZ3hqJEetusn56XfGLwEZ5W68+rr4Ve7pN3CJF+DTaSAT2wb3w9JGDL2asfEkM/cN3CPwbjK6m/UkPelRzPyafnup85sI6kH5N8e3n/ihD9MUlcrxDGRkYpVGUxzSkx6omyJscU1UOfaah6y3OLSR8I8n0ss2IHFbtFiLbX8ZyFRxVRFrlpJcRkUKY62sCQYi2xiJSKfSoMNXroeLlPefZOzKgSIUR/Ou9YV7s0hA/4rjL4UfwBRWmbqHsa883/5dSGsSl0+rxx3tFrdrjjCuA3AAAA//8BAAD//0Es6wUcAwAA"), }, _assestBase64Decode("L3Jlcy90cGwvbGF5b3V0Lmh0bWw="): { Name: _assestBase64Decode("L3Jlcy90cGwvbGF5b3V0Lmh0bWw="), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/4xVMXP0NBDt71cIFXwNsaBjwFYDdAyhSEN1o5PX9gZZMtqVw43xf2cs+/I5d5dMrvBa0r63T2+lc/nNr4+/PP3152+i497pQ3kJYGp9KHtgI2xnIgFXMnHz8KPUh9Kh/1t0EZpKqgiksG9VY0a0wRdogxQigqskdSGyTSyWBSnUhdCbHio5IrwMIbIUNngGz5V8wZq7qoYRLTzkwXcCPTIa90DWOKh+KL6Xb2lM4i7EHUmdXgCvkijaXUbHPNBPSrXIXToVNvSqwzqpYYjh3/MCZWQHepoKSqen5X2ep6ng9U38J9ZMMU3FCJEw+Hku1Qo6lGQjDiwo2s2eZ1LP/ySI5+KZpC7VmnDxcbWKzw6oA2C5N9YSqcYZLiyR/CwgL10QW63RxE30cVNcyb16+fNOltq6fwr1WR/KGkeBdSW9GeVu2INPUh/E+iuTy7MOGj5SOr1dzRkOhXWGqJKnEGuIxwYjsdSluexAagJaBJXK6FI5vCbY5drgG2ylXuNdwDRhIwqk30OLfp7f52rQgdTL8x0e8PVHeHMKiaXO4RPSE4FpQeo13gBKldzr4HVysX2170vEtuMv+wLlDvFq9n23p6nwgdHCcmSvZK46aTA+99IG78HykdhwIqm/9ScaloMyGH+7xytwIohHn/pj8A495MLL3GMePgU2blGQucSycEv4Qf+2chtn8YfpL/sRd9K+Wu9Cm1u1xru9yv12BHcLvmFCn4nw/nG9PTa5sWI/rnHUdzosrQMTl7+KnHAV3s/Lz2kqlos7z58DrLc2tMcax13F7e6r/D34HwAA//8BAAD//1aN3LcmBgAA"), }, _assestBase64Decode("L3Jlcy90cGwvbG9naW4uaHRtbA=="): { Name: _assestBase64Decode("L3Jlcy90cGwvbG9naW4uaHRtbA=="), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/6SSzW6rQAxG9zyF5X3EXd8ySF10l0XfoBoYh1iav3pMUt6+moRESZt2UxaDhI8/HzCd4wOwMzi4f9g33SBt/+hoTuDobSkGfZo4bsooRLE27ZIECKT75AzmVBTBjsopGmxPLIJamUgN8k7eKn5qY/KukPZN52mi6PpnFzjCtrZ07fqsgcv1XWFNAnjI1OpmkjTnewag45hnBV0yGVT6UIRoAxmsJ0L2dqR98o7E4EtUEljSLHCuHqyfySDejRlTVEkeQeh9ZiF3b9U6PvTNH0WzLeWYxCGstrkcv8i+XpFfLR/J/TS1zENgvQZu0wR1oWvsoBEGjZssHKwst8lrbtfe7LmtGvV+/h4XhHdiA61vdf1FoOjiyaDjkr1d/scU6Qn7rj3jPcAnAAAA//8BAAD//xNoF86+AgAA"), }, _assestBase64Decode("L3Jlcy90cGwvbmV0d29yay5odG1s"): { Name: _assestBase64Decode("L3Jlcy90cGwvbmV0d29yay5odG1s"), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/7RV227zKBC+jp9ixCry7kXrdHvQbmr7VRCGSUxCAAHO4e1/gQ85KK3a/vp9EQh88803wzCUnjtpA3jHK1I49MXGFw3z+PbyuPGkLoseUGd3kd7wLYZHab4ERu+l0TfQrAysUQhSVERjOBi3fQgNqbMyuPgj0g7zUmBcFHJ/CaVK+kCF3I97PpwUVvmOubXUy2d7zOusXBm3u7JaSRXQ0bgeDVcSlfAYhStcoxb1oBV6YFkMyxkAzAbBBylCW5GnxWJO6mwGkASnsR033xb2SGquJOqwLIvQDgAxAl6icSm17QKEk8WKBDwGAlwx7ysSBT5wo4MzioyxJdPl/6/zPMaU9+xU2hw022FFpgUCVjGOrVEC3bgO0pIayiKI+2I7j+5bUnun0exT2WSSfSOrYV5yYF1ooYtcBIIM0SBWyaiymHI75bjGI1edwCuxdTaLE8UaVNdieYt825jjKLiNBQV7pjqscrlja8whYVAMYBSkThtl0fP9jHvj7xFv/O+xcn+XlvszL6Svz+EwD+Kc0/QPPjzQ6I12Tk0e8x+dLtMnsMwFMCuIbJ+fKWhzcMzWnVPXNciN8pbp6uWTCnSK7ljg7ZfK8L/vCk1jvPj99KJlFNFPHIXcx34W29CgwDKNij6NzWlYFXJPGypvJP2brt9fA01rfPiC0fMHl/I62MWcjO2PKsOZopE+hph8WRbaP+gr0k++7rgBJ9dtIHBN9JrSUTIAo7mSfFsRa407nujQmylXyPTf/xBoHa4qsmF71j8qy3cCddotCzY5TsMMMpjekOg/NPTyKUmd/Pwi5eftqfxDkw60L1hkYmzLLcYwlk/2+G726FbKHJbQSiFQ5+MFhKHa4fK76LHPfdAXV/YO5nX+EeR2eazcYR7VDsIbI04RncYY8lTbV+nKsp4xddYpb+m8aLxYqM9VNCCTy4nuFwAAAP//AQAA//9PV68ZYAgAAA=="), }, _assestBase64Decode("L3Jlcy90cGwvcmVwbGF5Lmh0bWw="): { Name: _assestBase64Decode("L3Jlcy90cGwvcmVwbGF5Lmh0bWw="), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/7yWzW7jNhCAz9FTEFy3kpDEToEeClsSsLfeWrQ99BBAoMSRxYSmtNTYG8PVuxdDSv6L481ui704Cjl/nPlmyESqDVMy5YXkWZAUdpYFSdXYFRMlqsakfGah1WLL2QqwbmTK26ZD7nT8Tk7inKGwS8CU54UW5pmzUouuSzlt5g+ciTU2ZbNqNSCkvKkqckfOB7n6Z555e8lMqg1FoUDLDjALEg1LMDIrRKfKZDb8FyQoCg3012YJ1pmPmK2tniczrLMEZZYo066R4baFlCO8IGdGrCDlzlbuVfK11ZxthF5Dyne76WG1708Ocl82Bm2jeZbMyPgM7cG9z8+7XXvxY7cWPk396ltu2SW/vzYdvttr7Yp36pPW3nXQ2ZjwYx+1khLMqZc/YNUgfJTSnvsqtQKDuWr7nr/D0LqDVyZozWvPDohcwqUGIcEy4tkgKxvd2HRpxTaLGqO3bN0Bwxo6YJ479llhzdrWNi/bOJmRUsZ+NEXXLoJEsNpClfIPnDWm1Kp8TvkkCj9gkXs3YTzFZrnUEMU86+rm86xWEpKZoDhPiXW9s1fkrMOthpRL1VEYc9MY4Fmw21lhlsAmz3eTzTz1lXIafe9qH1Dxd7vJc9/78gdU/4CxvaYyEl7uJvlmnk42fR8wxhj13NuQeAe5t3qU+Em++afG1bV+cE3Ldjswklw5bgKWoBwVtOiQ05Jv70sZtYBra5iQMkcbYa26u3AIKYx5dkvpvFaRQd8XIqdcj3ZizlAhpdlvMqk62ufZ30ONfFRD1I738SwH7r8A3BIwb4UVq+49x3PwuPG4BAzvQtJ2p3QmrpIzar2Bybg9gHLOCXOcvEnJdUIoym/G4yijklL4Co6vZGOfsu8ERnAKxhtk7HaqYvCJHQ10xvjvv/35F6ezv+aGbtRvAocUw7vQ6X8NOu4Kv8JO626Ei/AQO+xb4XGB/k/0/Gd4Dln7TvS8Y6rsN/1DjN5Hw+1AabwXWi3NvASDYBfX78/x1XZ4p+0z7q+4c/VuXawUcjYmtUDDCjT3rVUrYbecjeqvb8uzO9ELhPF0I3QUehHKc5B0rTDjgVbCLpW511Dh/KeHh/aF6kwCXxvXaVhSWSjxakRexEc01oeA279/3e+4FyRdaVWLWVCtjX9hnoHQFE/xLriZuI9pKywY7CKOlsfTShkZcXceHk9BlHU0molI6UZV0cSjNBWINuIDQZLHbp8xNu5bWDUb+HgmtQhubnrQHZxJn1m7O1cJbnr6GNiuhO5gEfTB4ZRDszTF011rId7R82EjLKNWTTnNhkdaogFxXLCQSA2HaoXHjRwOpQ+PWLZqWePis5JYz395+CFkjXmG7bpNQ7T5Rui8rI1Ygm/ZR85vWwu3/JHHIWu1KKFutASbhm7wOfTDzA+qITh5KbgxFen4cTncCx4cbKF/FY8eRmUaQ2E2TpDwQ7iHMByLMsLxyNE+8rGkUbwYy6BFB4swk6Bpfhy5odnBF/SpqojO0lSsKZ7SlDfFExHvC8QuUigqBBtR4WJnYuTlSN5CC0Z6GSdCP2/DcVqdAyM3RIiBF8xd1tMrTYGSDX2x8GobodPJFK1aRYMatWtM26qKaDfl3DXFwcEXMN83xisNQoVT0Lcboa/0Vh/QqB5HwL8AAAD//wEAAP//O8CrWS8PAAA="), }, _assestBase64Decode("L3Jlcy90cGwvcmVwbGF5X2RpcmVjdC5odG1s"): { Name: _assestBase64Decode("L3Jlcy90cGwvcmVwbGF5X2RpcmVjdC5odG1s"), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/8xRy27DIBA8l69A1KoSKTI9J2t+xcKGxih+IFijRpR/rzZucqjSey+wj9mZgQXjEnemEZ0RikEX5OP8WMLEdY9umRuRc0153eno+nqrtmsYSxF8sjgs5jdmq1K/H3WMjaBe+y5uarc4WD/qa2tcsD2SOuputIpBrz3xq+eE/G3uoj/97QjknYDlHPR8try6HKp0bLaJs8VS2Muj114OVZuOTZWozDkHDApwUDlXl1KOIHFQgEaBm/2KHK/eNgLtJwo+68nSywkoeNLjuqVt+hpwou9RIGlUYlCkaWdTCrvfzw36Jf4bhyB/tsJAkjkKYh+cR8WqnXh9ssl9HdducrjbnxjIO5iBNC6pbwAAAP//AQAA///vqKAEcQIAAA=="), }, _assestBase64Decode("L3Jlcy90cGwvdXNlYWdlLmh0bWw="): { Name: _assestBase64Decode("L3Jlcy90cGwvdXNlYWdlLmh0bWw="), Mtime: 1448806296, Content: _assestGzipBase64decode("H4sIAAAJbogA/6xUwW7jNhA9L79iwL3EQCBZzmJrEJJ6aBfbQ4sumvRUFAElji1uJVIhR3YMr4H21gL9gv2W/E0/IL9QkLIcJ21vRQ6RZx4fZ948Tq70BrQqeKXmvGR55dKS5T3UrfS+4M0bXv7oUa4xT/tnibe8zJKvWo2GHh9+x7XoG2vw8eGPI9BhyTwSbPVKQ0PUQ+/s/U6w+A8a6wkeHz5DXpX7fdLH6G2IHg55WpVHWG/dv8BC9AhjeRqvyn0n27aURjmrFQwenYdamvAVDseDX4dcOAZkoZNGrtGNZfk8HQnYix4XyTW6DToRGEEbQreSNZ41udFeE+QSGoergqe8vEbvtTXwrfYEH6J2Ml650bgF2bZADY6aOLwb0BNQ4+ywboAa7ceKEqZNxPkj20q3hA5W1nWXIwneE2jTDzQ1mq+sIahta13hUJWf8jRE4t0YSoduaEn3LUJtjdKkrfEJw3XUS+QjGe16LHhg57CR7YAFl58qDp52LRZ8qxU14s28v+flpSbocLzcgfYgwTqoErazw7n6DvtW7iblx18gp+6TaYgvtL9KvrNKr3bww4jzo+pKb8qJvxsB51om79/d/PXrbx++v76BXjrZSQ/SqBHToFTofJKngWXkGk0VS/0oN9LXTvcU6pR1o3GDoElM+FjmajB1kA4cbp0mvHB4N9szAIA0jZVZheBJOsp9L82kXByMqNoBeRnRehWOJsH1RcG3221SSa2GpLYdn+3ZKwA45WM6e3sVk89St1IpV/Bs8UUyD39imXFIU/BoVACFVqZkJpZZPBxsdLtGSqRSF1zySy75LGbSNOZ66ynxSBdcTckDe5WnoaHy2CucmkWjYswhDS4Ic8cOp6cZpIsmuANbfcSaoJE+eNsjSCKnq4HQB3dcDK4twqBEmoZ+8V52fYuh51S21dClrfb0Za1VkS3ezh4fPj8bjK8b7CSIOGsWV4yAFzwsrhQByznrJTUg4IyZrZEE7GutxE/Z4urnAwsyCNgfWHC4kR2CANZL77fWqfDdITVWCXj/7oZNogII2EulxOSUi18uN7P94dIj/SO2fhYLkRbNU2S2PxyAnUbyPzMzdnKQgNdh3TiU7bidw3C32jePD3/i+sxBV4ts+R8v9s30Yr+xp+caca/DwgrKM4Nb/2TzJ1rGXlvT7mCZjUu/k1Q3L9BimZ07ecHG/PFVnKfmy/nJftEhfwMAAP//AQAA//+CM/4q6AYAAA=="), }, _assestBase64Decode("L3Jlcy92ZXJzaW9u"): { Name: _assestBase64Decode("L3Jlcy92ZXJzaW9u"), Mtime: 1478614292, Content: _assestGzipBase64decode("H4sIAAAJbogA/zLQM9UzAgAAAP//AQAA//9B6J7GBQAAAA=="), }, }, } ================================================ FILE: serve/auth.go ================================================ package serve import ( "encoding/base64" "net/http" "strings" "github.com/hidu/goutils/str_util" ) var proxyAuthorizatonHeader = "Proxy-Authorization" func getAuthorInfo(req *http.Request) *User { defaultInfo := new(User) authheader := strings.SplitN(req.Header.Get(proxyAuthorizatonHeader), " ", 2) if len(authheader) != 2 || authheader[0] != "Basic" { return defaultInfo } userpassraw, err := base64.StdEncoding.DecodeString(authheader[1]) if err != nil { return defaultInfo } userpass := strings.SplitN(string(userpassraw), ":", 2) if len(userpass) != 2 { return defaultInfo } return &User{Name: userpass[0], PswMd5: str_util.StrMd5(userpass[1]), Psw: userpass[1]} } func (ser *ProxyServe) checkUserLogin(userInfo *User) bool { if userInfo == nil || ser.Users == nil { return false } if userInfo.SkipCheckPsw { return true } if user, has := ser.Users[userInfo.Name]; has { return user.PswMd5 == userInfo.PswMd5 } return false } // (ser.conf.AuthType == AuthType_Basic && !ser.CheckUserLogin(reqCtx.User)) func (ser *ProxyServe) checkHTTPAuth(reqCtx *requestCtx) bool { switch ser.conf.AuthType { case authTypeNO: return true case authTypeBasic: return ser.checkUserLogin(reqCtx.User) case authTypeBasicWithAny: return reqCtx.User.Name != "" case authTypeBasicTry: if reqCtx.ClientSession.RequestNum == 1 { return reqCtx.User.Name != "" } return true default: return false } return false } ================================================ FILE: serve/broadcast.go ================================================ package serve import ( "strconv" ) // broadcastReq broadcast request to user's browser func (ser *ProxyServe) broadcastReq(reqCtx *requestCtx) bool { req := reqCtx.Req data := make(map[string]any) data["docid"] = strconv.Itoa(reqCtx.Docid) data["sid"] = reqCtx.SessionID % 10000 data["host"] = req.Host data["client_ip"] = req.RemoteAddr urlPath := req.URL.Path if req.URL.RawQuery != "" { urlPath += "?" + req.URL.RawQuery } data["path"] = urlPath data["url"] = req.URL.String() if req.Method == "CONNECT" && !ser.conf.SslOn { data["path"] = "https req,unknow path" } data["method"] = req.Method data["replay"] = reqCtx.IsRePlay hasSend := ser.wsSer.broadcastReq(req, reqCtx, data) return hasSend } ================================================ FILE: serve/certs.go ================================================ package serve import ( "crypto/tls" "crypto/x509" "log" "os" ) func newCaCert(caCert []byte, caKey []byte) (tls.Certificate, error) { ca, err := tls.X509KeyPair(caCert, caKey) if err != nil { log.Println("NewCaCert error:", err) return ca, err } if ca.Leaf, err = x509.ParseCertificate(ca.Certificate[0]); err != nil { log.Println("NewCaCert error:", err) return ca, err } log.Println("NewCaCert Ok") return ca, nil } // getSslCert get user's caCert or use the default buildin func getSslCert(caCertPath string, caKeyPath string) (ca tls.Certificate, err error) { if caCertPath == "" { caCert := Assest.GetContent("/res/private/client_cert.pem") caKey := Assest.GetContent("/res/private/server_key.pem") return newCaCert([]byte(caCert), []byte(caKey)) } cert, err := os.ReadFile(caCertPath) if err != nil { return ca, err } key, err := os.ReadFile(caKeyPath) if err != nil { return ca, err } return newCaCert(cert, key) } ================================================ FILE: serve/config.go ================================================ package serve import ( "crypto/tls" "errors" "fmt" "log" "net/http" "net/url" "strings" "github.com/Unknwon/goconfig" "github.com/hidu/goutils/fs" "github.com/hidu/goutils/str_util" ) // Config pproxy's config type Config struct { Port int AdminPort int Title string Notice string AuthType int DataDir string FileDir string ResponseSave int SessionView int DataStoreDay float64 ParentProxy *url.URL SslOn bool SslCert tls.Certificate ModifyRequest bool } const ( authTypeNO = 0 authTypeBasic = 1 authTypeBasicWithAny = 2 authTypeBasicTry = 3 responseSaveAll = 0 responseSaveHasBroad = 1 // has show sessionViewALL = 0 sessionViewIPOrUser = 1 ) // User user struct type User struct { Name string Psw string PswMd5 string IsAdmin bool SkipCheckPsw bool } // String string format func (u *User) String() string { return fmt.Sprintf("Name:%s,Psw:%s,isAdmin:%v,SkipCheckPsw:%v", u.Name, u.Psw, u.IsAdmin, u.SkipCheckPsw) } // ConfigString one line in file func (u *User) ConfigString() string { return fmt.Sprintf("name:%s\tpsw:%s\tis_admin:%v\tpsw_md5:%s", u.Name, u.Psw, u.IsAdmin, u.PswMd5) } const ( contentEncoding = "Content-Encoding" ) // "0:no auth | 1:basic auth | 2:basic auth with any name" // GetVersion get current version func GetVersion() string { return Assest.GetContent("res/version") } // GetDemoConf get the demo config func GetDemoConf() string { return strings.TrimSpace(Assest.GetContent("res/conf/demo.conf")) } func (u *User) isPswEq(psw string) bool { return u.PswMd5 == str_util.StrMd5(psw) } // LoadConfig load the pproxy's config func LoadConfig(confPath string) (*Config, error) { gconf, err := goconfig.LoadConfigFile(confPath) if err != nil { log.Println("load config", confPath, "failed,err:", err) return nil, err } config := new(Config) config.Port = gconf.MustInt(goconfig.DEFAULT_SECTION, "port", 8080) config.AdminPort = gconf.MustInt(goconfig.DEFAULT_SECTION, "adminPort", 0) if config.AdminPort == 0 { config.AdminPort = config.Port } config.DataStoreDay = gconf.MustFloat64(goconfig.DEFAULT_SECTION, "dataStoreDay", 0) if config.DataStoreDay < 0 { log.Println("wrong DataStoreDay,skip") config.DataStoreDay = 0 } config.Title = gconf.MustValue(goconfig.DEFAULT_SECTION, "title") config.Notice = gconf.MustValue(goconfig.DEFAULT_SECTION, "notice") config.DataDir = gconf.MustValue(goconfig.DEFAULT_SECTION, "dataDir", "../data/") config.FileDir = gconf.MustValue(goconfig.DEFAULT_SECTION, "fileDir", "../file/") _authType := strings.ToLower(gconf.MustValue(goconfig.DEFAULT_SECTION, "authType", "none")) authTypes := map[string]int{"none": 0, "basic": 1, "basic_any": 2, "basic_try": 3, "try_basic": 3} hasError := false if authType, has := authTypes[_authType]; has { config.AuthType = authType } else { hasError = true log.Println("conf error,unknow value authType:", _authType) } _responseSave := strings.ToLower(gconf.MustValue(goconfig.DEFAULT_SECTION, "responseSave", "all")) responseSaveMap := map[string]int{"all": 0, "only_broadcast": 1} if responseSave, has := responseSaveMap[_responseSave]; has { config.ResponseSave = responseSave } else { hasError = true log.Println("conf error,unknow value responseSave:", _authType) } _sessionView := strings.ToLower(gconf.MustValue(goconfig.DEFAULT_SECTION, "sessionView", "all")) sessionViewMap := map[string]int{"all": 0, "ip_or_user": 1} if sessionView, has := sessionViewMap[_sessionView]; has { config.SessionView = sessionView } else { hasError = true log.Println("conf error,unknow value responseSave:", _authType) } parentProxy := gconf.MustValue(goconfig.DEFAULT_SECTION, "parentProxy", "") if parentProxy != "" { _urlObj, err := url.Parse(parentProxy) if err != nil || _urlObj.Scheme != "http" { hasError = true log.Println("parentProxy wrong,must http proxy") } else { config.ParentProxy = _urlObj } } config.SslOn = gconf.MustValue(goconfig.DEFAULT_SECTION, "ssl", "off") == "on" if config.SslOn { _sslClientCert := gconf.MustValue(goconfig.DEFAULT_SECTION, "ssl_client_cert", "") _sslServerKey := gconf.MustValue(goconfig.DEFAULT_SECTION, "ssl_server_key", "") cert, err := getSslCert(_sslClientCert, _sslServerKey) if err != nil { hasError = true log.Println("ssl ca config error:", err) } else { config.SslCert = cert } } config.ModifyRequest = gconf.MustValue(goconfig.DEFAULT_SECTION, "modifyRequest", "on") == "on" if hasError { return config, errors.New("config error") } return config, nil } type configHosts map[string]string // loadHosts 读取host配置文件 func loadHosts(confPath string) (hosts configHosts, err error) { hosts = make(configHosts) if !fs.FileExists(confPath) { return } hostsByte, err := fs.FileGetContents(confPath) if err != nil { log.Println("load hosts_file failed:", confPath, err) return nil, err } hostsArr := str_util.LoadText2Slice(string(hostsByte)) for _, v := range hostsArr { if len(v) != 2 { log.Println("hosts file line wrong,ignore,", v) continue } hosts[v[0]] = v[1] } return } func loadUsers(confPath string) (users map[string]*User, err error) { users = make(map[string]*User) if !fs.FileExists(confPath) { return } userInfoByte, err := fs.FileGetContents(confPath) if err != nil { log.Println("load user file failed:", confPath, err) return } lines := str_util.LoadText2SliceMap(string(userInfoByte)) for _, line := range lines { name, has := line["name"] if !has || name == "" { continue } if _, has := users[name]; has { log.Println("dup name in users:", name, line) continue } user := new(User) user.Name = name if val, has := line["is_admin"]; has && (val == "admin" || val == "true") { user.IsAdmin = true } if val, has := line["psw_md5"]; has { user.PswMd5 = val } if user.PswMd5 == "" { if val, has := line["psw"]; has { user.Psw = val user.PswMd5 = str_util.StrMd5(val) } } users[user.Name] = user } return } func (config *Config) getTransport() *http.Transport { if config.ParentProxy == nil { return nil } tr := &http.Transport{ Proxy: func(req *http.Request) (*url.URL, error) { if config.ParentProxy.User.Username() == "pass" { user := getAuthorInfo(req) urlTmp, err := url.Parse(config.ParentProxy.String()) if err != nil { return nil, err } urlTmp.User = url.UserPassword(user.Name, user.Psw) return urlTmp, nil } return config.ParentProxy, nil }, } return tr } ================================================ FILE: serve/init.go ================================================ package serve // 系统版本 var PproxyVersion string func init() { PproxyVersion = GetVersion() } ================================================ FILE: serve/kvStore.go ================================================ package serve import ( "time" "github.com/boltdb/bolt" "github.com/hidu/goutils/time_util" ) type KV_TBALE_NAME_TYPE string const ( KV_TABLE_REQ KV_TBALE_NAME_TYPE = "req" KV_TABLE_RES KV_TBALE_NAME_TYPE = "res" ) type kvStore struct { dbPath string db *bolt.DB tables map[KV_TBALE_NAME_TYPE]*kvStoreTable } type kvStoreTable struct { name KV_TBALE_NAME_TYPE kv *kvStore } type StoreType struct { Now int64 `json:"now"` Data KvType `json:"data"` } func newStoreType(data map[string]any) *StoreType { return &StoreType{Now: time.Now().Unix(), Data: data} } func newKvStore(dbPath string) (kv *kvStore, err error) { kv = &kvStore{ dbPath: dbPath, } kv.db, err = bolt.Open(kv.dbPath, 0600, nil) if err != nil { return } kv.tables = make(map[KV_TBALE_NAME_TYPE]*kvStoreTable) kv.initTable(KV_TABLE_REQ) kv.initTable(KV_TABLE_RES) return } func (kv *kvStore) initTable(name KV_TBALE_NAME_TYPE) { kv.tables[name] = newkvStoreTable(name, kv) } func (kv *kvStore) GetkvStoreTable(name KV_TBALE_NAME_TYPE) (tb *kvStoreTable) { if tb, has := kv.tables[name]; has { return tb } return nil } func (kv *kvStore) Gc(max_life int64) { for _, tb := range kv.tables { tb.Gc(max_life) } } func (kv *kvStore) StartGcTimer(sec int64, max_life int64) { if max_life < 1 { return } time_util.SetInterval(func() { kv.Gc(max_life) }, sec) } func newkvStoreTable(name KV_TBALE_NAME_TYPE, kv *kvStore) *kvStoreTable { tb := &kvStoreTable{ name: name, kv: kv, } tb.kv.db.Update(func(tx *bolt.Tx) error { _, err := tx.CreateBucketIfNotExists([]byte(name)) return err }) return tb } func (tb *kvStoreTable) Save(key []byte, val *StoreType) error { err := tb.kv.db.Update(func(tx *bolt.Tx) error { bk, _ := tx.CreateBucketIfNotExists([]byte(tb.name)) return bk.Put(key, dataEncode(val)) }) return err } func (tb *kvStoreTable) Get(key []byte) (val *StoreType, err error) { err = tb.kv.db.View(func(tx *bolt.Tx) error { bk := tx.Bucket([]byte(tb.name)) bs := bk.Get(key) if len(bs) > 0 { return dataDecode(bs, &val) } return nil }) return } func (tb *kvStoreTable) Del(key []byte) (err error) { err = tb.kv.db.Update(func(tx *bolt.Tx) error { bk := tx.Bucket([]byte(tb.name)) return bk.Delete(key) }) return } func (tb *kvStoreTable) Gc(gc_life int64) { if gc_life < 1 { return } max_time := time.Now().Unix() - gc_life var val *StoreType tb.kv.db.View(func(tx *bolt.Tx) error { bk := tx.Bucket([]byte(tb.name)) bk.ForEach(func(k, v []byte) error { dataDecode(v, &val) if val != nil && val.Now < max_time { tb.Del(k) } return nil }) return nil }) } ================================================ FILE: serve/proxy.go ================================================ package serve import ( "io" "log" "net" "net/http" "net/http/httputil" "strconv" "sync" "time" "github.com/elazarl/goproxy" ) type HttpProxy struct { GoProxy *goproxy.ProxyHttpServer ser *ProxyServe ctxs map[string]*requestCtx mu sync.RWMutex goproxyMitmConnect *goproxy.ConnectAction } func NewHttpProxy(ser *ProxyServe) *HttpProxy { proxy := new(HttpProxy) proxy.ser = ser proxy.GoProxy = goproxy.NewProxyHttpServer() tr := ser.conf.getTransport() if tr != nil { proxy.GoProxy.Tr = tr } proxy.ctxs = make(map[string]*requestCtx) if proxy.ser.conf.SslOn { proxy.goproxyMitmConnect = &goproxy.ConnectAction{ Action: goproxy.ConnectMitm, TLSConfig: goproxy.TLSConfigFromCA(&proxy.ser.conf.SslCert), } proxy.GoProxy.OnRequest().HandleConnectFunc(proxy.httpsHandle) } proxy.GoProxy.OnRequest().DoFunc(my_requestHanderFunc) proxy.GoProxy.OnResponse().DoFunc(proxy.onResponse) return proxy } func my_requestHanderFunc(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { log.Println("trace_my_requestHanderFunc call url:", r.URL.String()) return r, nil } const PROXY_CTX_NAME = "X-PPROXY-CTX-ID" func (proxy *HttpProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { // fmt.Println("call url:",req.URL.String()) proxy.GoProxy.ServeHTTP(rw, req) } func (proxy *HttpProxy) httpsHandle(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) { log.Println("https conn", host, ctx.Req.URL.String()) return proxy.goproxyMitmConnect, host } func (proxy *HttpProxy) RoundTrip(ctx *requestCtx) { sid := strconv.FormatInt(ctx.SessionID, 10) ctx.Req.Header.Set(PROXY_CTX_NAME, sid) func() { proxy.mu.Lock() defer proxy.mu.Unlock() proxy.ctxs[sid] = ctx }() defer func() { proxy.mu.Lock() defer proxy.mu.Unlock() if _, has := proxy.ctxs[sid]; has { delete(proxy.ctxs, sid) } }() if ctx.Req.Header.Get("Upgrade") != "" { proxy.roundTripUpgrade(ctx) return } proxy.ServeHTTP(ctx.Rw, ctx.Req) } func (proxy *HttpProxy) getReqCtx(req *http.Request) *requestCtx { sid := req.Header.Get(PROXY_CTX_NAME) if sid == "" { return nil } proxy.mu.RLock() defer proxy.mu.RUnlock() if ctx, has := proxy.ctxs[sid]; has { return ctx } return nil } func (proxy *HttpProxy) onResponse(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { if resp == nil || resp.Request == nil { return resp } reqCtx := proxy.getReqCtx(resp.Request) if reqCtx != nil { reqCtx.saveResponse(resp) } return resp } func (proxy *HttpProxy) roundTripUpgrade(ctx *requestCtx) (err error) { // save it,so we know it has been closed defer func() { resp := &http.Response{ Request: ctx.Req, Header: make(http.Header), Body: nil, } ctx.saveResponse(resp) }() reqDump, err := httputil.DumpRequest(ctx.Req, false) if err != nil { ctx.Msg = "dump req failed:" + err.Error() return } ctx.SetTimePoint("startDial") dia, err := net.Dial("tcp", ctx.DestAddr()) if err != nil { ctx.Msg = "dia connect " + ctx.DestAddr() + " failed!" + err.Error() return } defer dia.Close() _, err = dia.Write(reqDump) if err != nil { return } hijack, _ := ctx.Rw.(http.Hijacker) conn, _, _ := hijack.Hijack() errc := make(chan error, 2) cp := func(dst io.Writer, src io.Reader) { _, err := io.Copy(dst, src) errc <- err time.AfterFunc(3*time.Second, func() { dia.Close() conn.Close() }) } go cp(dia, conn) go cp(conn, dia) <-errc return } ================================================ FILE: serve/reqCtx.go ================================================ package serve import ( "encoding/base64" "errors" "fmt" "log" "net/http" "net/http/httputil" "net/url" "strconv" "strings" "time" ) // requestCtx 一次http请求的数据结构 type requestCtx struct { RemoteAddr string Req *http.Request Rw http.ResponseWriter Host string // eg www.baidu.com Port int // eg 80 User *User Docid int IsRePlay bool SessionID int64 HasBroadcast bool FormPost *url.Values ClientSession *clientSession OriginURL string logData map[any]any Msg string ser *ProxyServe startTime time.Time timeDurations map[string]time.Duration hasPrint bool } // NewRequestCtx 构建一个新的请求 func NewRequestCtx(ser *ProxyServe, rw http.ResponseWriter, req *http.Request) *requestCtx { ctx := &requestCtx{} ctx.Req = req ctx.ser = ser ctx.Rw = rw ctx.SessionID = ser.reqNum ctx.logData = make(map[any]any) ctx.timeDurations = make(map[string]time.Duration) ctx.FormPost = &url.Values{} ctx.init() ctx.startTime = time.Now() return ctx } func (ctx *requestCtx) init() { if ctx.Req == nil { return } fixRequest(ctx.Req) req := ctx.Req ctx.Host, ctx.Port, _ = getHostPortFromReq(req) ctx.User = getAuthorInfo(req) ctx.FormPost = getPostData(req) ctx.OriginURL = req.URL.String() ctx.IsRePlay = len(req.Header.Get(REPLAY_FLAG)) > 0 ctx.SetLog("url", req.URL.String()) ctx.RemoteAddr = req.RemoteAddr if _replayAddr := req.Header.Get(REPLAY_REMOTEADDR); _replayAddr != "" { ctx.RemoteAddr = _replayAddr } if _replayUser := req.Header.Get(REPLAY_USER_NAME); _replayUser != "" { ctx.User = &User{Name: _replayUser, SkipCheckPsw: true} } ctx.Docid = ctx.getNewDocid() ctx.ser.regirestReq(ctx) } func fixRequest(req *http.Request) { if req.Method != "CONNECT" && !req.URL.IsAbs() { urlOrigin := req.URL.String() urlStr := "http://" + req.Host + req.URL.Path if req.URL.RawQuery != "" { urlStr += "?" + req.URL.RawQuery } var err error req.URL, err = url.Parse(urlStr) if err != nil { log.Println("fix url failed,originUrl:", urlOrigin, "err:", err) return } } } func (ctx *requestCtx) IsLocalRequest() bool { isLocalReq := ctx.Port == ctx.ser.conf.Port if isLocalReq { isLocalReq = IsLocalIP(ctx.Host) } return isLocalReq } func (ctx *requestCtx) GetIp() string { hostInfo := strings.Split(ctx.RemoteAddr, ":") return hostInfo[0] } func (ctx *requestCtx) PrintLog() { reqID := 0 if ctx.ClientSession != nil { reqID = ctx.ClientSession.RequestNum } log.Println( "session_id:", ctx.SessionID, "remote:", ctx.RemoteAddr, "reqId:", reqID, "docid:", ctx.Docid, "uname:", ctx.User.Name, "broadcast:", ctx.HasBroadcast, "startTime:", ctx.startTime.Unix(), "timeUsed:", fmt.Sprintf("%.3fs", time.Since(ctx.startTime).Seconds()), "data:", ctx.logData, "times:", ctx.timeDurations, ) } func (ctx *requestCtx) RoundTrip() { defer func() { ctx.hasPrint = true ctx.SetLog("logType", "defer") ctx.PrintLog() }() time.AfterFunc(10*time.Second, func() { if !ctx.hasPrint { ctx.SetLog("logType", "timeout10") ctx.PrintLog() } }) removeHeader(ctx.Req) rewriteCode := ctx.ser.reqRewrite(ctx) ctx.HasBroadcast = ctx.ser.broadcastReq(ctx) // reqDump, _ := httputil.DumpRequest(ctx.Req, true) // fmt.Println("req dump3:\n",string(reqDump)) ctx.SetLog("js_rewrite_code", rewriteCode) ctx.saveRequestData() // 异步的会导致req.body dump不了,先暂时这样,对接口会有一些影响 // time.AfterFunc(1*time.Second, ctx.saveRequestData) if rewriteCode != 200 && rewriteCode != 304 { ctx.badGateway(errors.New("rewrite failed")) return } ctx.ser.proxy.RoundTrip(ctx) } func (ctx *requestCtx) badGateway(err error) { ctx.SetLog("errMsg", fmt.Sprintf("%s", err)) ctx.Rw.WriteHeader(http.StatusBadGateway) ctx.Rw.Write([]byte("pproxy error")) } func (ctx *requestCtx) DestAddr() string { return fmt.Sprintf("%s:%d", ctx.Host, ctx.Port) } func (ctx *requestCtx) saveRequestData() { if ctx.ser.conf.ResponseSave == responseSaveAll || (ctx.ser.conf.ResponseSave == responseSaveHasBroad && ctx.HasBroadcast) { logdata := KvType{} logdata["host"] = ctx.Req.Host logdata["schema"] = ctx.Req.URL.Scheme logdata["header"] = map[string][]string(ctx.Req.Header) logdata["url"] = ctx.Req.URL.String() logdata["url_origin"] = ctx.OriginURL logdata["path"] = ctx.Req.URL.Path // logdata["cookies"] = ctx.Req.Cookies() // logdata["now"] = time.Now().Unix() logdata["user"] = ctx.User.Name logdata["client_ip"] = ctx.RemoteAddr logdata["method"] = ctx.Req.Method logdata["form_get"] = ctx.Req.URL.Query() logdata["replay"] = ctx.IsRePlay logdata["msg"] = ctx.Msg logdata["id"] = strconv.Itoa(ctx.Docid) // 当无普通form post表单数据的时候,比如可能body也有数据 // 比如request 的content-type=application/json dumpBody := len(*ctx.FormPost) == 0 // fmt.Println("dumpBody",dumpBody) // dumpBody=false reqDump, errDump := httputil.DumpRequest(ctx.Req, dumpBody) if errDump != nil { ctx.SetLog("dumpMsg", fmt.Sprintf("dump request failed,%s", errDump.Error())) reqDump = []byte(fmt.Sprintf("dump failed,%s", errDump.Error())) } logdata["dump"] = base64.StdEncoding.EncodeToString(reqDump) // buf := forgetRead(&ctx.Req.Body) // logdata["dump"] = base64.StdEncoding.EncodeToString(buf.Bytes()) logdata["form_post"] = ctx.FormPost tb := ctx.ser.mydb.GetkvStoreTable(KV_TABLE_REQ) data := newStoreType(logdata) err := tb.Save(IntToBytes(ctx.Docid), data) if err != nil { log.Println("save req failed:", err) } } } func (ctx *requestCtx) saveResponse(res *http.Response) { if ctx.Docid < 1 || res == nil { return } data := KvType{} data["now"] = time.Now().Unix() data["header"] = map[string][]string(res.Header) data["status"] = res.StatusCode data["content_length"] = res.ContentLength data["msg"] = ctx.Msg data["id"] = strconv.Itoa(ctx.Docid) resDump, dumpErr := httputil.DumpResponse(res, false) if dumpErr != nil { log.Println("dump res err", dumpErr) resDump = []byte("dump res failed") } data["dump"] = base64.StdEncoding.EncodeToString(resDump) // data["cookies"]=res.Cookies() body := []byte("pproxy skip") if res.Body != nil && res.ContentLength <= ctx.ser.MaxResSaveLength { buf := forgetRead(&res.Body) if res.Header.Get(contentEncoding) == "gzip" { body = []byte(gzipDocode(buf)) } else { body = buf.Bytes() } l := int64(len(body)) if l > ctx.ser.MaxResSaveLength { body = []byte(fmt.Sprintf("pproxy skip,body too large,[len=%d]", l)) } } data["body"] = base64.StdEncoding.EncodeToString(body) tb := ctx.ser.mydb.GetkvStoreTable(KV_TABLE_RES) storeData := newStoreType(data) err := tb.Save(IntToBytes(ctx.Docid), storeData) log.Println("save_res", ctx.SessionID, "docid=", ctx.Docid, "body_len=", len(data["body"].(string)), err) } func (ctx *requestCtx) SetLog(k, v any) { ctx.logData[k] = v } func (ctx *requestCtx) SetTimePoint(key string) { ctx.timeDurations[key] = time.Since(ctx.startTime) } func (ctx *requestCtx) getNewDocid() int { idStr := fmt.Sprintf("%s%d", time.Now().Format("200601021504"), ctx.ser.reqNum) id, err := parseDocID(idStr) if err == nil { return id } log.Println("GetNewDocid failed", idStr, err) return int(time.Now().UnixNano() + ctx.ser.reqNum) } ================================================ FILE: serve/req_modifer.go ================================================ package serve import ( "errors" "fmt" "log" "os" "strings" "sync" "github.com/hidu/goutils/fs" "github.com/robertkrimen/otto" ) var rewriteJsTpl = Assest.GetContent("res/sjs/req_rewrite.js") /* * request动态修改引擎 * 使用javascript 来对请求进行修改 */ type requestModifier struct { mu sync.RWMutex jsVm *otto.Otto jsFns map[string]*otto.Value canMod bool ser *ProxyServe } func NewRequestModifier(ser *ProxyServe) *requestModifier { reqMod := &requestModifier{ jsVm: otto.New(), jsFns: make(map[string]*otto.Value), ser: ser, } return reqMod } func (reqMod *requestModifier) getJsPath(name string) string { baseName := fmt.Sprintf("%s/req_rewrite_%d", reqMod.ser.configDir, reqMod.ser.conf.Port) if name == "" { return fmt.Sprintf("%s.js", baseName) } return fmt.Sprintf("%s_%s.js", baseName, name) } func (reqMod *requestModifier) tryLoadJs(name string) (err error) { jsContent, err := reqMod.getJsContent(name) if jsContent != "" && err == nil { err = reqMod.parseJs(jsContent, name, false) if err != nil { log.Println("load rewrite js failed:", err) return err } log.Println("load rewrite js[", name, "] suc") } return nil } func (reqMod *requestModifier) loadAllJs() error { if !reqMod.ser.conf.ModifyRequest { log.Println("ignore requestModifier loadAllJs") return nil } names := []string{""} for _, user := range reqMod.ser.Users { names = append(names, user.Name) } for _, name := range names { err := reqMod.tryLoadJs(name) if err != nil { return err } } return nil } func (reqMod *requestModifier) getJsContent(name string) (content string, err error) { jsPath := reqMod.getJsPath(name) if fs.FileExists(jsPath) { script, err := os.ReadFile(jsPath) if err == nil { return string(script), nil } return "", err } return "", nil } func (reqMod *requestModifier) CanMod() bool { return reqMod.canMod } func (reqMod *requestModifier) parseJs(jsStr string, name string, save2File bool) error { jsStr = strings.TrimSpace(jsStr) rewriteJs := strings.Replace(rewriteJsTpl, "CUSTOM_JS", jsStr, 1) rewriteJs = strings.Replace(rewriteJs, "PPROXY_HOST", fmt.Sprintf("127.0.0.1:%d", reqMod.ser.conf.Port), 1) reqMod.mu.Lock() defer reqMod.mu.Unlock() if reqMod.ser.Debug { log.Println("jsvm_execute:", rewriteJs) } reqMod.jsVm.Run(rewriteJs) jsFn, err := reqMod.jsVm.Get("pproxy_rewrite") if err != nil { log.Println("rewrite js init error:", err) return err } if strings.HasPrefix(jsStr, "//ignore") { if _, has := reqMod.jsFns[name]; has { delete(reqMod.jsFns, name) } log.Println("req_mod [", name, "] ignore") } else { reqMod.jsFns[name] = &jsFn log.Println("req_mod [", name, "] register suc") } reqMod.canMod = true if save2File { jsPath := reqMod.getJsPath(name) err = fs.FilePutContents(jsPath, []byte(jsStr)) log.Println("save rewritejs ", jsPath, err) } return err } func (reqMod *requestModifier) getJsFnByName(name string) (*otto.Value, error) { names := []string{name, ""} for _, name := range names { if jsFn, has := reqMod.jsFns[name]; has { return jsFn, nil } } return nil, errors.New("no rewrite rules") } func (reqMod *requestModifier) rewrite(data map[string]any, name string) (map[string]any, error) { reqMod.mu.Lock() defer reqMod.mu.Unlock() reqJsObj, _ := reqMod.jsVm.Object(`req={}`) reqJsObj.Set("origin", data) jsFn, err := reqMod.getJsFnByName(name) if err != nil { return nil, err } defer func() { if caught := recover(); caught != nil { log.Println("fatal:requestModifer recover:", caught) } }() js_ret, err_js := (*jsFn).Call(*jsFn, reqJsObj) if err_js != nil { log.Println("parse js error:", err_js) return nil, err_js } if !js_ret.IsObject() { log.Println("wrong req_rewirte return value,not object:", js_ret) return nil, fmt.Errorf("wrong req_rewirte return value,not object.%t", js_ret) } obj, export_err := js_ret.Export() if export_err != nil { return nil, export_err } reqObjNew := obj.(map[string]any) return reqObjNew, nil } ================================================ FILE: serve/req_replay.go ================================================ package serve import ( "fmt" "net/http" "net/url" "strings" ) const ( REPLAY_FLAG = "Proxy-pproxy_replay" REPLAY_REMOTEADDR = "Proxy-pproxy_remoteaddr" REPLAY_USER_NAME = "Proxy-pproxy_user" ) func (ctx *webRequestCtx) handleReplay() { if ctx.req.Method == "POST" { ctx.reqReplayPost() return } docidStr := strings.TrimSpace(ctx.req.FormValue("id")) if docidStr == "" { ctx.w.WriteHeader(http.StatusBadRequest) ctx.w.Write([]byte("empty id param")) return } docid, errInt := parseDocID(docidStr) if errInt != nil { ctx.w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(ctx.w, "param id[%s] error:\n%s", docidStr, errInt) return } reqDoc, _ := ctx.ser.getRequestByDocid(docid) if reqDoc == nil { ctx.w.WriteHeader(http.StatusNotFound) ctx.w.Write([]byte("request doc not found!")) return } _url := fmt.Sprintf("%s", reqDoc.Data["url"]) u, err := url.Parse(_url) if err != nil { ctx.w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(ctx.w, "parse url[%s] error\n%s", _url, err) return } u.RawQuery = "" ctx.values["req"] = reqDoc ctx.values["action_url"] = u.String() ctx.render("replay.html", true) } var replaySkipHeaders = map[string]int{"Content-Length": 1} func (ctx *webRequestCtx) reqReplayPost() { replay := ctx.req.FormValue("replay") basic := make(map[string]string) basic["action_url"] = strings.TrimSpace(ctx.req.FormValue("basic_action_url")) method := strings.TrimSpace(strings.ToUpper(ctx.req.FormValue("basic_method"))) basic["method"] = method host := strings.TrimSpace(ctx.req.FormValue("basic_host")) basicRemoteAddr := ctx.req.FormValue("basic_RemoteAddr") basicUser := ctx.req.FormValue("basic_user") header := getFormValuesWithPrefix(ctx.req.Form, "header_") get := getFormValuesWithPrefix(ctx.req.Form, "get_") post := getFormValuesWithPrefix(ctx.req.Form, "post_") formData := make(map[string]any) formData["basic"] = basic formData["header"] = header formData["get"] = get formData["post"] = post ctx.values["form"] = formData if replay == "direct" { ctx.render("replay_direct.html", true) return } reqBd := "" _url := basic["action_url"] if len(get) > 0 { formValues := make(url.Values) for k, v := range get { for _, _v := range v { formValues.Add(k, _v) } } if strings.Contains(_url, "?") { _url += "&" } else { _url += "?" } _url += formValues.Encode() } if len(post) > 0 { formValues := make(url.Values) for k, v := range post { for _, _v := range v { formValues.Add(k, _v) } } reqBd = formValues.Encode() } replayReq, err := http.NewRequest(method, _url, strings.NewReader(reqBd)) if err != nil { ctx.w.Write([]byte("build request failed\n" + err.Error())) return } if host != "" { replayReq.Host = host } replayReq.Header.Set(REPLAY_FLAG, "replay") replayReq.Header.Set(REPLAY_REMOTEADDR, basicRemoteAddr) replayReq.Header.Set(REPLAY_USER_NAME, basicUser) for k, v := range header { if _, has := replaySkipHeaders[k]; has { continue } replayReq.Header.Set(k, strings.Join(v, ";")) } ctx.ser.ServeHTTPProxy(ctx.w, replayReq) } ================================================ FILE: serve/req_rewrite.go ================================================ package serve import ( "bytes" "fmt" "io" "log" "net/http" "net/url" "strconv" "strings" ) func (ser *ProxyServe) reqRewriteByjs(reqCtx *requestCtx) int { modifer := ser.reqMod if !modifer.CanMod() { return 304 } req := reqCtx.Req schema := req.URL.Scheme originURL := req.URL.String() originGetQuery := req.URL.Query() // /================================================================ headerKv := make(map[string]string) headerKv["method"] = req.Method headerKv["schema"] = schema headerKv["path"] = req.URL.Path _host, portInt, _ := getHostPortFromReq(req) headerKv["host"] = _host headerKv["port"] = strconv.Itoa(portInt) username := "" psw := "" if req.URL.User != nil { username = req.URL.User.Username() psw, _ = req.URL.User.Password() } headerKv["username"] = username headerKv["proxy_user"] = reqCtx.User.Name headerKv["password"] = psw // =================================================================== rewriteData := make(map[string]any) rewriteData["header"] = headerKv rewriteData["get"] = originGetQuery rewriteData["post"] = *reqCtx.FormPost _buf := forgetRead(&reqCtx.Req.Body) var rawBody string //  暂时只考虑gip的,其他的压缩就不支持了 if req.Header.Get(contentEncoding) == "gzip" { rawBody = gzipDocode(_buf) } else { rawBody = _buf.String() } rewriteData["body"] = rawBody reqObjNew, rErr := modifer.rewrite(rewriteData, reqCtx.User.Name) if rErr != nil { log.Println("rewrite failed:", rErr) } headerKvNew := make(map[string]string) isHeaderChange := false skipHeader := false var err error urlStrNew := getMapValStr(reqObjNew, "url") if urlStrNew != "" { req.URL, err = url.Parse(urlStrNew) if err != nil || req.URL.Scheme != "http" { log.Println("new url wrong!url is:", urlStrNew, err) return 500 } req.Host = req.URL.Host skipHeader = true } if !skipHeader { for k, v := range headerKv { _newVal := getMapValStr(reqObjNew, k) headerKvNew[k] = _newVal if _newVal != v { isHeaderChange = true } } } // ------------------------------------------------------- var getNew url.Values isGetChange := false if _get, has := reqObjNew["get"]; has { getNew = _reqMapToURLValue(_get) isGetChange = checkURLValuesChange(originGetQuery, getNew) } // ------------------------------------------------------- var postNew url.Values isPostChange := false if schema == "http" { if _post, has := reqObjNew["post"]; has { postNew = _reqMapToURLValue(_post) isPostChange = checkURLValuesChange(*reqCtx.FormPost, postNew) } } isBodyChange := false bodyNew := "" if _bodyNew, has := reqObjNew["body"]; has { bodyNew = _bodyNew.(string) isBodyChange = rawBody != bodyNew } hostAddr := getMapValStr(reqObjNew, "hostAddr") isHostAddrChange := hostAddr != "" if ser.Debug { fmt.Println("rewriteChange:", "is_get_change:", isGetChange, "new_get:", getNew, "isPostChange:", isPostChange, "new_post:", postNew, "isHostAddrChange:", isHostAddrChange, "newHostAddr:", hostAddr, "isBodyChange:", isBodyChange, ) } // /=============================================================================== if !isHeaderChange && !isGetChange && !isPostChange && !isHostAddrChange && !isBodyChange { return 304 } // /=============================================================================== var urlBase string if isHeaderChange { // schema := headerKvNew["schema"] urlBase = schema + "://" if headerKvNew["username"] != "" { urlBase += fmt.Sprintf("%s:%s@", headerKvNew["username"], headerKvNew["password"]) } urlBase += headerKvNew["host"] if headerKvNew["port"] != "" && headerKvNew["port"] != "80" { urlBase += ":" + headerKvNew["port"] } urlBase += headerKvNew["path"] } else { if req.URL.RawQuery == "" { urlBase = originURL } else { urlBase = originURL[:len(originURL)-len(req.URL.RawQuery)-1] } } if isGetChange { urlBase += "?" + getNew.Encode() } else { urlBase += "?" + req.URL.RawQuery } if isHeaderChange || isGetChange { var urlErr error req.URL, urlErr = url.Parse(urlBase) if ser.Debug { log.Println("DEBUG req_rewrite,url_new:", urlBase, "req_new:", req.URL) } if urlErr != nil { return 502 } req.Host = req.URL.Host } // //////////////////////////////////////////////////////////////////////////// if isPostChange || isBodyChange { buf := bytes.NewBuffer([]byte{}) var bodyData string if isPostChange { bodyData = postNew.Encode() } else if isBodyChange { bodyData = bodyNew } req.Header.Del("Content-Length") if req.Header.Get(contentEncoding) == "gzip" { tmp := gzipEncode([]byte(bodyData)).Bytes() buf.Write(tmp) } else { buf.WriteString(bodyData) } req.ContentLength = int64(buf.Len()) req.Body = io.NopCloser(buf).(io.ReadCloser) } // ////////////////////////////////////////////////////////////////////////// if isHostAddrChange { req.URL.Host = hostAddr if ser.Debug { log.Println("rewrite host addr:", req.URL.Host, "==>", hostAddr) } } return 200 } func (ser *ProxyServe) reqRewrite(reqCtx *requestCtx) int { if !ser.conf.ModifyRequest { return 304 } if reqCtx.Req.Method == "CONNECT" { return 304 } originHost := reqCtx.Req.Host + "#" + reqCtx.Req.URL.Host statusCode1 := ser.reqRewriteByjs(reqCtx) newHost := reqCtx.Req.Host + "#" + reqCtx.Req.URL.Host if ser.Debug { log.Println("rewrte_debug:\n", "originHost:", originHost, "\nnewHost:", newHost, "\n") } statusCode2 := 304 if originHost == newHost { statusCode2 = ser.reqRewriteByHosts(reqCtx.Req) } if statusCode1 == 200 || statusCode2 == 200 { return 200 } if statusCode1 >= 500 || statusCode2 >= 500 { return 502 } return 304 } func (ser *ProxyServe) reqRewriteByHosts(req *http.Request) int { if ser.hosts == nil { return 304 } if host, has := ser.hosts[req.URL.Host]; has { log.Println("rewrite host:", req.URL.Host, "==>", host) req.URL.Host = host return 200 } hostInfo := strings.Split(req.URL.Host, ":") if len(hostInfo) == 1 { if req.URL.Scheme == "http" { hostInfo = append(hostInfo, "80") } } reqHost := strings.Join(hostInfo, ":") if host, has := ser.hosts[reqHost]; has { log.Println("rewrite host:", req.Host, "==>", host) req.URL.Host = host return 200 } if host, has := ser.hosts[hostInfo[0]]; has { log.Println("rewrite host:", req.Host, "==>", host) req.URL.Host = host if !strings.Contains(host, ":") { req.URL.Host += ":" + hostInfo[1] } return 200 } return 304 } func _reqMapToURLValue(values any) url.Values { uValues := make(url.Values) if values == nil { return uValues } vs := values.(map[string]any) for k, arr := range vs { switch value := arr.(type) { case []any: for _, v := range value { uValues.Add(k, fmt.Sprintf("%v", v)) } case any: uValues.Set(k, fmt.Sprintf("%v", value)) default: log.Println("unkonw type:", value) } } return uValues } ================================================ FILE: serve/serve.go ================================================ package serve import ( "fmt" "log" "math/rand" "net/http" "net/http/httputil" "os" "path/filepath" "sync" "sync/atomic" "time" "github.com/hidu/goutils/fs" "github.com/hidu/goutils/time_util" ) type ProxyServe struct { mydb *kvStore proxy *HttpProxy wsSer *wsServer startTime time.Time MaxResSaveLength int64 mu sync.RWMutex Debug bool conf *Config configDir string hosts configHosts Users map[string]*User ProxyClients map[string]*clientSession reqNum int64 reqMod *requestModifier } type KvType map[string]any func (ser *ProxyServe) ServeHTTP(w http.ResponseWriter, req *http.Request) { atomic.AddInt64(&ser.reqNum, 1) // reqDump, _ := httputil.DumpRequest(req, true) // fmt.Println("req dump:\n",string(reqDump)) ctx := NewRequestCtx(ser, w, req) if ctx.Host == "p.info" || ctx.Host == "pproxy.info" { ser.handleUserInfo(w, req) return } if ctx.Host == "pproxy.man" || ctx.Host == "pproxy.com" || ctx.IsLocalRequest() { ser.handleLocalReq(w, req) } else { if ser.Debug { reqDumpDebug, _ := httputil.DumpRequest(req, req.Method == "GET") log.Println("DEBUG req BEFORE:\nurl_full:", req.URL.String(), "\nschema:", req.URL.Scheme, "\n", string(reqDumpDebug), "\n\n") } if !ser.checkHTTPAuth(ctx) { ctx.SetLog("msg", "login required") ctx.Rw.Header().Set("Proxy-Authenticate", "Basic realm=auth required") ctx.Rw.WriteHeader(http.StatusProxyAuthRequired) ctx.Rw.Write([]byte("auth required")) return } ctx.RoundTrip() } } // for replay func (ser *ProxyServe) ServeHTTPProxy(w http.ResponseWriter, req *http.Request) { atomic.AddInt64(&ser.reqNum, 1) ctx := NewRequestCtx(ser, w, req) ctx.RoundTrip() } func (ser *ProxyServe) Start() { addr := fmt.Sprintf("%s:%d", "", ser.conf.Port) fmt.Println("proxy listen at ", addr) defer log.Println("pproxy exit") ser.wsInit() wg := new(sync.WaitGroup) wg.Add(1) go func() { err := http.ListenAndServe(addr, ser) log.Println(err) fmt.Println(err) wg.Done() }() wg.Add(1) go func() { ser.startAdmin() wg.Done() }() wg.Wait() } func (ser *ProxyServe) startAdmin() { if ser.conf.Port == ser.conf.AdminPort { return } addr := fmt.Sprintf(":%d", ser.conf.AdminPort) fmt.Println("admin http service listen at ", addr) httpSer := http.NewServeMux() httpSer.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { ser.handleLocalReq(w, req) }) http.ListenAndServe(addr, httpSer) } func (ser *ProxyServe) getResponseByDocid(docid int) (resData *StoreType, err error) { tb := ser.mydb.GetkvStoreTable(KV_TABLE_RES) return tb.Get(IntToBytes(docid)) } func (ser *ProxyServe) getRequestByDocid(docid int) (reqData *StoreType, err error) { tb := ser.mydb.GetkvStoreTable(KV_TABLE_REQ) return tb.Get(IntToBytes(docid)) } func (ser *ProxyServe) getHostsFilePath() string { return fmt.Sprintf("%s/hosts_%d", ser.configDir, ser.conf.Port) } func (ser *ProxyServe) loadHosts() { ser.mu.Lock() defer ser.mu.Unlock() hostsPath := ser.getHostsFilePath() log.Println("load hosts:", hostsPath) ser.hosts, _ = loadHosts(hostsPath) } func NewProxyServe(confPath string, port int) (*ProxyServe, error) { conf, err := LoadConfig(confPath) if err != nil { log.Println("load config faield", err) return nil, err } if port > 0 && port < 65535 { conf.Port = port } absPath, err := filepath.Abs(confPath) if err != nil { log.Println("get config path failed", confPath) return nil, err } GetVersion() os.Chdir(filepath.Dir(absPath)) setupLog(conf.DataDir, conf.Port) proxy := new(ProxyServe) proxy.configDir = filepath.Dir(absPath) proxy.Users, _ = loadUsers(proxy.configDir + "/users") conf.FileDir, _ = filepath.Abs(conf.FileDir) proxy.conf = conf proxy.reqMod = NewRequestModifier(proxy) err = proxy.reqMod.loadAllJs() if err != nil { return nil, err } proxy.loadHosts() dbPath := fmt.Sprintf("%s/%d.db", conf.DataDir, conf.Port) // proxy.mydb = NewTieDb(fmt.Sprintf("%s/%d/", conf.DataDir, conf.Port), conf.DataStoreDay) proxy.mydb, err = newKvStore(dbPath) if err != nil { log.Fatalln("init db failed", err) } proxy.startTime = time.Now() proxy.MaxResSaveLength = 2 * 1024 * 1024 rand.Seed(time.Now().UnixNano()) proxy.ProxyClients = make(map[string]*clientSession) proxy.proxy = NewHttpProxy(proxy) time_util.SetInterval(func() { proxy.cleanExpiredSession() }, 60) proxy.mydb.StartGcTimer(60, int64(conf.DataStoreDay*86400)) return proxy, nil } func setupLog(dataDir string, port int) { logPath := fmt.Sprintf("%s/%d.log", dataDir, port) logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644) if err != nil { log.Println("create log file failed [", logPath, "]", err) os.Exit(2) } log.SetOutput(logFile) time_util.SetInterval(func() { if !fs.FileExists(logPath) { logFile.Close() logFile, _ = os.OpenFile(logPath, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644) log.SetOutput(logFile) } }, 30) } ================================================ FILE: serve/sessions.go ================================================ package serve import ( "log" "time" ) type clientSession struct { Ip string Port string RequestNum int FirstRequestTime time.Time LastRequestTime time.Time User *User } func (ser *ProxyServe) regirestReq(reqCtx *requestCtx) { ip := reqCtx.GetIp() now := time.Now() ser.mu.Lock() defer ser.mu.Unlock() var session *clientSession client, has := ser.ProxyClients[ip] if has { session = client } else { session = &clientSession{ Ip: ip, RequestNum: 0, FirstRequestTime: now, LastRequestTime: now, } } if reqCtx.User.Name == "" && session.User != nil { reqCtx.User = session.User } else if reqCtx.User.Name != "" { session.User = reqCtx.User } session.LastRequestTime = now session.RequestNum++ if ser.Debug { log.Println("session_debug:", session) } ser.ProxyClients[ip] = session reqCtx.ClientSession = session if !has { ser.wsSer.broadProxyClientNum() } } func (ser *ProxyServe) cleanExpiredSession() { ser.mu.Lock() defer ser.mu.Unlock() now := time.Now() deleteIps := []string{} for ip, session := range ser.ProxyClients { t := now.Sub(session.LastRequestTime) if t.Minutes() > 10 { deleteIps = append(deleteIps, ip) } } for _, ip := range deleteIps { delete(ser.ProxyClients, ip) log.Println("session expired:ip=", ip) } ser.wsSer.broadProxyClientNum() } ================================================ FILE: serve/util.go ================================================ package serve import ( "bytes" "compress/gzip" // "encoding/base64" "encoding/binary" "encoding/json" "errors" "fmt" "io" "log" "net" "net/http" "net/url" "strconv" "strings" // "gopkg.in/vmihailenco/msgpack.v2" ) // Int64ToBytes int64转换为byte func Int64ToBytes(i int64) []byte { var buf = make([]byte, 8) binary.BigEndian.PutUint64(buf, uint64(i)) return buf } // IntToBytes int转换为byte func IntToBytes(i int) []byte { var buf = make([]byte, 8) binary.BigEndian.PutUint64(buf, uint64(i)) return buf } // IsLocalIP 判断一个host是否本地ip func IsLocalIP(host string) bool { ips, _ := net.LookupIP(host) for _, ip := range ips { if ip.IsLoopback() { return true } } if addrs, err := net.InterfaceAddrs(); err == nil { for _, addr := range addrs { _, ipG, err := net.ParseCIDR(addr.String()) if err == nil { for _, ip := range ips { if ipG.Contains(ip) { return true } } } } } return false } func forgetRead(reader *io.ReadCloser) *bytes.Buffer { buf := bytes.NewBuffer([]byte{}) io.Copy(buf, *reader) *reader = io.NopCloser(buf).(io.ReadCloser) return bytes.NewBuffer(buf.Bytes()) } func dataEncode(data any) []byte { bf, err := json.Marshal(data) if err != nil { log.Println("data_encode_err", err) return bf } return bf } func dataDecode(dataInput []byte, out any) error { if len(dataInput) == 0 { return errors.New("empty dataInput") } err := json.Unmarshal(dataInput, &out) if err != nil { log.Println("json_decode_err:", err, "dataInput:", string(dataInput)) return err } return err } func getMapValStr(m map[string]any, k string) string { if val, has := m[k]; has { return fmt.Sprintf("%s", val) } return "" } func gzipDocode(buf *bytes.Buffer) string { if buf.Len() < 1 { return "" } gr, err := gzip.NewReader(buf) defer gr.Close() if err == nil { bdBt, _ := io.ReadAll(gr) return string(bdBt) } log.Println("unzip body failed", err) return "" } func gzipEncode(data []byte) *bytes.Buffer { buf := bytes.NewBuffer([]byte{}) gw := gzip.NewWriter(buf) defer gw.Close() gw.Write(data) return buf } func parseURLInputAsSlice(input string) []string { arr := strings.Split(input, "|") var result []string for _, val := range arr { val = strings.TrimSpace(val) if val != "" { result = append(result, val) } } return result } func getFormValuesWithPrefix(values url.Values, prefix string) map[string][]string { result := make(map[string][]string) for k, v := range values { if strings.HasPrefix(k, prefix) { k1 := strings.TrimPrefix(k, prefix) result[k1] = v } } return result } func getTextAreaHeightByString(mystr string, minHeight int) int { height := (len(strings.Split(mystr, "\n")) + 1) * 25 if height < minHeight { height = minHeight } return height } func getHostPortFromReq(req *http.Request) (host string, port int, err error) { host, port, err = parseHostPort(req.Host) if err == nil && port == 0 { switch req.URL.Scheme { case "http": port = 80 break case "https": port = 443 break default: break } } return } func parseHostPort(hostPortstr string) (host string, port int, err error) { var portStr string if !strings.Contains(hostPortstr, ":") { hostPortstr += ":0" } host, portStr, err = net.SplitHostPort(hostPortstr) if err != nil { return } port, err = strconv.Atoi(portStr) if err != nil { return } return } func checkURLValuesChange(first url.Values, second url.Values) (change bool) { for k, v := range first { secV, has := second[k] if !has { return true } if len(v) != len(secV) || fmt.Sprintf("%v", v) != fmt.Sprintf("%v", secV) { return true } } for k, v := range second { firstV, has := first[k] if !has { return true } if len(v) != len(firstV) || fmt.Sprintf("%v", v) != fmt.Sprintf("%v", firstV) { return true } } return false } func parseDocID(strid string) (docid int, err error) { docid64, parseErr := strconv.ParseUint(strid, 10, 64) if parseErr == nil { return int(docid64), nil } return 0, parseErr } func removeHeader(req *http.Request) { for k := range req.Header { if len(k) > 5 && k[:6] == "Proxy-" { req.Header.Del(k) } } } func getPostData(req *http.Request) (post *url.Values) { post = new(url.Values) if strings.Contains(req.Header.Get("Content-Type"), "x-www-form-urlencoded") { buf := forgetRead(&req.Body) var bodyStr string if req.Header.Get(contentEncoding) == "gzip" { bodyStr = gzipDocode(buf) } else { bodyStr = buf.String() } var err error *post, err = url.ParseQuery(bodyStr) if err != nil { log.Println("parse post err", err, "url=", req.URL.String()) } } return post } func headerEncode(data []byte) []byte { t := bytes.Replace(data, []byte("\r"), []byte("\\r"), -1) t = bytes.Replace(t, []byte("\n"), []byte("\\n"), -1) return t } ================================================ FILE: serve/web.go ================================================ package serve import ( "bytes" "encoding/base64" "fmt" "html" "log" "net" "net/http" "net/url" "regexp" "strconv" "strings" "text/template" "time" "github.com/hidu/goutils/fs" "github.com/hidu/goutils/html_util" "github.com/hidu/goutils/object" ) type webRequestCtx struct { values map[string]any user *User isLogin bool isAdmin bool req *http.Request w http.ResponseWriter ser *ProxyServe } var cookieName = "pproxy" func (ser *ProxyServe) handleLocalReq(w http.ResponseWriter, req *http.Request) { accessLogStr := "web_access " + req.Method + " " + req.URL.String() + " " + req.RemoteAddr + " refer:" + req.Referer() defer (func() { log.Println(accessLogStr) })() if strings.HasPrefix(req.URL.Path, "/socket.io/") { ser.wsSer.server.ServeHTTP(w, req) return } if strings.HasPrefix(req.URL.Path, "/f/") { req.URL.Path = req.URL.Path[3:] http.FileServer(http.Dir(ser.conf.FileDir)).ServeHTTP(w, req) return } if strings.HasPrefix(req.URL.Path, "/res/") { Assest.HTTPHandler("/").ServeHTTP(w, req) return } values := make(map[string]any) values["title"] = ser.conf.Title values["subTitle"] = "" values["version"] = PproxyVersion values["notice"] = ser.conf.Notice values["port"] = strconv.Itoa(ser.conf.Port) values["userOnlineTotal"] = len(ser.ProxyClients) _host, _, _ := getHostPortFromReq(req) values["pproxy_host"] = _host values["pproxy_port"] = ser.conf.Port ctx := &webRequestCtx{ values: values, w: w, req: req, ser: ser, } ctx.checkLogin() funcMap := make(map[string]func()) funcMap["/"] = ctx.handle_index funcMap["/about"] = ctx.handle_about funcMap["/config"] = ctx.handleConfig funcMap["/useage"] = ctx.handle_useage funcMap["/replay"] = ctx.handleReplay funcMap["/login"] = ctx.handle_login funcMap["/logout"] = ctx.handle_logout funcMap["/response"] = ctx.handle_response funcMap["/file"] = ctx.handle_file if fn, has := funcMap[req.URL.Path]; has { if len(req.URL.Path) > 1 { ctx.values["subTitle"] = req.URL.Path[1:] + " |" } fn() } else { ctx.showError("404") } } func (ser *ProxyServe) web_checkLogin(req *http.Request) (user *User, isLogin bool) { if req == nil { return } cookie, err := req.Cookie(cookieName) if err != nil { return } info := strings.SplitN(cookie.Value, ":", 2) if len(info) != 2 { return } if user, has := ser.Users[info[0]]; has { if user.PswMd5 == info[1] { return user, true } } return } func (ctx *webRequestCtx) checkLogin() { user, isLogin := ctx.ser.web_checkLogin(ctx.req) if isLogin { ctx.user = user ctx.isLogin = true ctx.isAdmin = user.IsAdmin } ctx.values["isLogin"] = ctx.isLogin ctx.values["user"] = ctx.user ctx.values["isAdmin"] = ctx.isAdmin } func (ctx *webRequestCtx) handle_index() { ctx.render("network.html", true) } func (ctx *webRequestCtx) handle_useage() { ctx.render("useage.html", true) } func (ctx *webRequestCtx) getRewriteJsInfo(name string, title string) map[string]any { info := make(map[string]any) jsStr, _ := ctx.ser.reqMod.getJsContent(name) re := regexp.MustCompile(`use_file\(["'](.+)["']\)`) matches := re.FindAllStringSubmatch(jsStr, -1) // fmt.Println(matches) var useFile []map[string]any tmpNames := make(map[string]int) for _, subMatch := range matches { if len(subMatch) != 2 { continue } use := make(map[string]any) fileName := strings.TrimSpace(subMatch[1]) use["name"] = subMatch[0] use["file"] = fileName if _, has := tmpNames[fileName]; has { continue } tmpNames[fileName] = 1 isURL := strings.HasPrefix(fileName, "http://") use["isUrl"] = isURL if isURL { use["url"] = subMatch[1] } else { webFile, err := newWebFileInfo(ctx.ser.conf.FileDir, fileName) if err != nil { continue } use["url"] = webFile.link() defer webFile.Close() } useFile = append(useFile, use) } info["name"] = name info["use_file"] = useFile info["title"] = title info["rewriteJs"] = html.EscapeString(jsStr) info["jsHeight"] = getTextAreaHeightByString(jsStr, 100) return info } func (ctx *webRequestCtx) handleConfig() { if ctx.req.Method == "GET" { jsDataArr := make([]any, 0, 2) jsDataArr = append(jsDataArr, ctx.getRewriteJsInfo("", "global config")) if ctx.isLogin { jsDataArr = append(jsDataArr, ctx.getRewriteJsInfo(ctx.user.Name, ctx.user.Name+"'s config")) } ctx.values["jss"] = jsDataArr hostsByte, _ := fs.FileGetContents(ctx.ser.getHostsFilePath()) ctx.values["hosts"] = html.EscapeString(string(hostsByte)) ctx.values["hostsHeight"] = getTextAreaHeightByString("", 100) ctx.render("config.html", true) } else if ctx.req.Method == "POST" { if !ctx.isLogin { ctx.jsAlert("login first") return } do := ctx.req.PostFormValue("type") var err error if do == "js" { name := strings.TrimSpace(ctx.req.PostFormValue("name")) if !ctx.isAdmin && name != ctx.user.Name { ctx.jsAlert("you are not admin") return } jsStr := strings.TrimSpace(ctx.req.PostFormValue("js")) err = ctx.ser.reqMod.parseJs(jsStr, name, true) } else if do == "hosts" { if !ctx.isAdmin { ctx.jsAlert("you are not admin") return } hosts := strings.TrimSpace(ctx.req.PostFormValue("hosts")) log.Println("hosts_update", hosts) err = fs.FilePutContents(ctx.ser.getHostsFilePath(), []byte(hosts)) ctx.ser.loadHosts() } if err != nil { ctx.jsAlert("save failed,err:" + err.Error()) } else { ctx.w.Write([]byte("")) } } } func (ctx *webRequestCtx) handle_response() { docid, uintParseErr := parseDocID(ctx.req.FormValue("id")) if uintParseErr == nil { responseData, _ := ctx.ser.getResponseByDocid(docid) if responseData == nil { ctx.showError("response not found") } else { walker := object.NewInterfaceWalker(map[string]any(responseData.Data)) var contentType string if typeHeader, has := walker.GetStringSlice("/header/Content-Type"); has { contentType = strings.Join(typeHeader, ";") } customContentType := ctx.req.FormValue("type") // set custom content type if customContentType != "" { switch customContentType { case "json": contentType = "application/json" case "html": contentType = "text/html;charset=utf-8" default: contentType = customContentType } } if contentType != "" { ctx.w.Header().Set("Content-Type", contentType) } if statusCode, has := walker.GetInt("/status"); has { ctx.w.WriteHeader(statusCode) } if bodyStr, has := walker.GetString("/body"); has { bodyByte, err := base64.StdEncoding.DecodeString(bodyStr) if err == nil { ctx.w.Write(bodyByte) } else { log.Println("decode body failed", err) } } else { ctx.showError("response body not found") } } } else { ctx.showError("param err") } } func (ctx *webRequestCtx) jsAlert(msg string) { fmt.Fprintf(ctx.w, "", html.EscapeString(msg)) } func (ctx *webRequestCtx) jsAlertJump(msg string, urlStr string) { fmt.Fprintf(ctx.w, "", html.EscapeString(msg), urlStr) } func (ctx *webRequestCtx) handle_about() { ctx.render("about.html", true) } func (ctx *webRequestCtx) handle_logout() { cookie := &http.Cookie{Name: cookieName, Value: "", Path: "/"} http.SetCookie(ctx.w, cookie) http.Redirect(ctx.w, ctx.req, "/", 302) } func (ctx *webRequestCtx) handle_login() { if ctx.req.Method == "GET" { ctx.render("login.html", true) } else { name := strings.TrimSpace(ctx.req.FormValue("name")) psw := strings.TrimSpace(ctx.req.FormValue("psw")) if name == "" { ctx.jsAlert("empty name") return } if user, has := ctx.ser.Users[name]; has { if user.isPswEq(psw) { log.Println("login suc,name=", name) cookie := &http.Cookie{ Name: cookieName, Value: fmt.Sprintf("%s:%s", name, user.PswMd5), Path: "/", Expires: time.Now().Add(86400 * time.Second), } http.SetCookie(ctx.w, cookie) ctx.w.Write([]byte("")) } else { log.Println("login failed psw incorrect,name=", name, "psw=", psw) ctx.jsAlert("password incorrect") } return } log.Println("login failed not exists,name=", name, "psw=", psw) ctx.jsAlert("user not exists") } } func (ctx *webRequestCtx) render(name string, layout bool) { html := render_html(name, ctx.values, layout) ctx.w.Write([]byte(html)) } func (ctx *webRequestCtx) showError(msg string) { ctx.values["error"] = msg ctx.values["subTitle"] = "Error Page |" ctx.render("error.html", true) } func (ctx *webRequestCtx) showErrorOrAlert(msg string) { if ctx.req.Method == "POST" { ctx.jsAlert(msg) } else { ctx.showError(msg) } } func reader_html_include(fileName string) string { html := Assest.GetContent("/res/tpl/" + fileName) myfn := template.FuncMap{ "my_include": func(name string) string { return reader_html_include(name) }, } tpl, _ := template.New("page_include").Delims("{%", "%}").Funcs(myfn).Parse(html) var bf []byte w := bytes.NewBuffer(bf) tpl.Execute(w, make(map[string]string)) body := w.String() return body } func render_html(fileName string, values map[string]any, layout bool) string { html := reader_html_include(fileName) funcs := template.FuncMap{ "escape": func(str string) string { return url.QueryEscape(str) }, "my_include": func(fileName string) string { return "include (" + fileName + ") with Delims {%my_include %}" }, } tpl, _ := template.New("page").Funcs(funcs).Parse(html) var bf []byte w := bytes.NewBuffer(bf) tpl.Execute(w, values) body := w.String() if layout { values["body"] = body return render_html("layout.html", values, false) } return html_util.Html_reduceSpace(body) } func (ser *ProxyServe) handleUserInfo(w http.ResponseWriter, req *http.Request) { host, _, _ := net.SplitHostPort(req.RemoteAddr) data := "client ip:" + host w.Write([]byte(data)) } ================================================ FILE: serve/web_file.go ================================================ package serve import ( "fmt" "io" "log" "net/url" "os" "path/filepath" "regexp" "strings" "github.com/hidu/goutils/fs" ) type webFileInfo struct { Name string RootDir string IsDir bool Size int64 Link string fullPath string file *os.File subFileInfos []*webFileInfo } func newWebFileInfo(rootDir, name string) (*webFileInfo, error) { rootDir = filepath.Clean(rootDir + "/") fullPath := filepath.Clean(fmt.Sprintf("%s/%s", rootDir, name)) if !strings.HasPrefix(fullPath, rootDir) { return nil, fmt.Errorf("unsafe path:%s", name) } f, err := os.Open(fullPath) if err != nil { return nil, err } stat, err := f.Stat() if err != nil { return nil, err } info := &webFileInfo{ Name: strings.TrimLeft(fullPath[len(rootDir):], "/"), RootDir: rootDir, IsDir: stat.IsDir(), Size: stat.Size(), fullPath: fullPath, file: f, } info.Link = info.link() return info, nil } func (f *webFileInfo) String() string { return fmt.Sprintf("Name:%s\nRootDir:%s\nisDir:%v\nSize:%d\nfullPath:%s\n", f.Name, f.RootDir, f.IsDir, f.Size, f.fullPath) } func (f *webFileInfo) link() string { values := make(url.Values) values.Set("name", f.Name) if !f.IsDir { values.Set("op", "edit") } return "/file?" + values.Encode() } func (f *webFileInfo) getContent() string { if f.IsDir { return "" } data, err := io.ReadAll(f.file) if err != nil { log.Println("read file failed:", err) return "" } return string(data) } func (f *webFileInfo) Close() { f.file.Close() if f.subFileInfos != nil { for _, info := range f.subFileInfos { info.Close() } } } func (f *webFileInfo) subFiles() ([]*webFileInfo, error) { names, err := f.file.Readdirnames(0) if err != nil { return nil, err } fileInfos := make([]*webFileInfo, 0) for _, name := range names { info, err := newWebFileInfo(f.RootDir, fmt.Sprintf("%s/%s", f.Name, name)) if err != nil { log.Println("read file err,skip.name=", name, err) } else { fileInfos = append(fileInfos, info) } } f.subFileInfos = fileInfos return fileInfos, nil } func (ser *ProxyServe) getWebFilePath(name string) (fullPath string, nameNew string, err error) { rootDir := filepath.Clean(ser.conf.FileDir + "/") fullPath = filepath.Clean(fmt.Sprintf("%s/%s", rootDir, name)) if !strings.HasPrefix(fullPath, rootDir) { return "", "", fmt.Errorf("unsafe path:%s", name) } nameNew = fullPath[len(rootDir):] re := regexp.MustCompile(`^[\w/\-\.]*$`) if !re.MatchString(nameNew) { err = fmt.Errorf("illegal path:%s", nameNew) } return fullPath, nameNew, err } func (ctx *webRequestCtx) handle_file() { if !ctx.isLogin { ctx.showError("need login") return } opMap := make(map[string]func()) opMap["edit"] = ctx.handle_file_edit opMap["new"] = ctx.handle_file_new opMap["del"] = ctx.handle_file_del opMap["save"] = ctx.handle_file_save op := ctx.req.FormValue("op") if fn, has := opMap[op]; has { fn() return } name := ctx.req.FormValue("name") if !ctx.isAdmin && name == "" { name = ctx.user.Name dirFullPath, _, err := ctx.ser.getWebFilePath(name) if err != nil { ctx.showError("file dir wrong") return } if !fs.FileExists(dirFullPath) { os.MkdirAll(dirFullPath, os.ModePerm) } } dirInfo, err := newWebFileInfo(ctx.ser.conf.FileDir, name) if err != nil { ctx.showError("open file dir failed:" + name) return } defer dirInfo.Close() ctx.values["currentDir"] = dirInfo.Name ctx.values["isSubDir"] = dirInfo.Name != "" if !ctx.isAdmin && !strings.Contains(dirInfo.Name, "/") { ctx.values["isSubDir"] = false } files, err := dirInfo.subFiles() if err != nil { ctx.showError(err.Error()) return } ctx.values["files"] = files ctx.render("file.html", true) } func (ctx *webRequestCtx) handle_file_edit() { name := ctx.req.FormValue("name") if name == "" { ctx.showError("params wrong") return } info, err := newWebFileInfo(ctx.ser.conf.FileDir, name) if err != nil { ctx.showError("read file info failed:" + err.Error()) return } defer info.Close() if info.IsDir { ctx.showError("params wrong,only file can view") return } ctx.values["file"] = info fileContent := info.getContent() ctx.values["fileContent"] = fileContent ctx.values["fileContentRows"] = len(strings.Split(fileContent, "\n")) + 8 ctx.render("file_edit.html", true) } func (ctx *webRequestCtx) handle_file_del() {} func (ctx *webRequestCtx) handle_file_new() { dirFullPath, dirNew, err := ctx.ser.getWebFilePath(ctx.req.FormValue("dir")) if err != nil { ctx.showErrorOrAlert("params err:" + err.Error()) return } ctx.values["dir"] = dirNew if ctx.req.Method == "GET" { finfo, fErr := os.Stat(dirFullPath) if fErr != nil || !finfo.IsDir() { ctx.showErrorOrAlert("open dir failed") return } ctx.render("file_new.html", true) } else if ctx.req.Method == "POST" { name := strings.TrimSpace(ctx.req.FormValue("name")) if name == "" { ctx.jsAlert("empty filename") return } fpath := ctx.req.FormValue("dir") + "/" + ctx.req.FormValue("name") fileFullPath, fileName, err := ctx.ser.getWebFilePath(fpath) if err != nil { ctx.jsAlert("wrong fileName") return } if fileName == "" || strings.HasSuffix(fileName, "/") { ctx.jsAlert("wrong file name") return } if fs.FileExists(fileFullPath) { ctx.jsAlert("file already exists") return } if !strings.HasPrefix(fileFullPath, dirFullPath) { ctx.jsAlert("file name wrong") return } if !ctx.user.IsAdmin && !strings.HasPrefix(fileName+"/", "/"+ctx.user.Name+"/") { ctx.jsAlert("file path wrong:" + fileName) return } dirName := filepath.Dir(fileFullPath) if !fs.FileExists(dirName) { os.MkdirAll(dirName, os.ModePerm) } content := ctx.req.FormValue("content") wErr := fs.FilePutContents(fileFullPath, []byte(content)) if wErr != nil { ctx.jsAlert("write file failed") return } finfo, _ := newWebFileInfo(ctx.ser.conf.FileDir, fpath) defer finfo.Close() ctx.jsAlertJump("save suc", finfo.link()) } } func (ctx *webRequestCtx) handle_file_save() { nameOrigin := ctx.req.PostFormValue("nameOrigin") name := ctx.req.PostFormValue("name") content := ctx.req.PostFormValue("content") fullPath, nameFix, err := ctx.ser.getWebFilePath(name) if err != nil { ctx.jsAlert("file path wrong:" + err.Error()) return } if name == "" || nameFix == "" || strings.HasSuffix(nameFix, "/") { ctx.jsAlert("wrong file name") return } fullPathOrigin, _, err := ctx.ser.getWebFilePath(nameOrigin) if fullPathOrigin == "" && err != nil { ctx.jsAlert("origin file path wrong:" + err.Error()) return } dirName := filepath.Dir(fullPath) if !fs.FileExists(dirName) { os.MkdirAll(dirName, os.ModePerm) } errWrite := fs.FilePutContents(fullPath, []byte(content)) if errWrite != nil { ctx.jsAlert("save failed:" + errWrite.Error()) return } if fullPath != fullPathOrigin { os.Remove(fullPathOrigin) } info, _ := newWebFileInfo(ctx.ser.conf.FileDir, name) ctx.jsAlertJump("save suc", info.link()) } ================================================ FILE: serve/wsClient.go ================================================ package serve import ( "net/http" "path/filepath" "strings" "github.com/googollee/go-socket.io" ) type wsClient struct { ns *socketio.NameSpace user string filterUser []string filterIP []string filterHideExt []string filterURL []string filterURLHide []string LoginUser *User } var extTypes = map[string][]string{ "js": {"js"}, "css": {"css"}, "image": {"jpg", "jpeg", "png", "gif", "bmp", "tiff", "jpe", "tif", "webp", "ico", "webp"}, } func (client *wsClient) checkFilter(req *http.Request, reqCtx *requestCtx) bool { if len(client.filterUser) > 0 { userInList := false for _, name := range client.filterUser { if name == "any" && client.LoginUser != nil && client.LoginUser.IsAdmin { userInList = true break } if name != "" && name == reqCtx.User.Name { userInList = true break } } if !userInList { return false } } if len(client.filterIP) > 0 { addrInfo := strings.Split(reqCtx.RemoteAddr, ":") ipInList := false for _, ip := range client.filterIP { if ip == "*" { ipInList = true break } if ip != "" && addrInfo[0] == ip { ipInList = true break } } if !ipInList { return false } } if len(client.filterURL) > 0 { url := req.URL.String() hasKw := false for _, subURL := range client.filterURL { if strings.Contains(url, subURL) { hasKw = true break } } if !hasKw { return false } } if len(client.filterHideExt) > 0 { ext := strings.ToLower(strings.Trim(filepath.Ext(req.URL.Path), ".")) for _, hideType := range client.filterHideExt { for _, hideExt := range extTypes[hideType] { if ext == hideExt { return false } } } } if len(client.filterURLHide) > 0 { _url := req.URL.String() for _, hideKw := range client.filterURLHide { if hideKw != "" && strings.Contains(_url, hideKw) { return false } } } return true } ================================================ FILE: serve/wsServer.go ================================================ package serve import ( "fmt" "log" "net/http" "net/url" "sync" "github.com/googollee/go-socket.io" "github.com/hidu/goutils/time_util" ) type wsServer struct { clients map[string]*wsClient server *socketio.SocketIOServer mu sync.RWMutex proxySer *ProxyServe } func (ser *ProxyServe) wsInit() { ser.wsSer = newWsServer(ser) } func newWsServer(ser *ProxyServe) *wsServer { wsSer := &wsServer{ clients: make(map[string]*wsClient), proxySer: ser, } var err error wsSer.server = socketio.NewSocketIOServer(&socketio.Config{}) if err != nil { log.Fatal(err) } wsSer.init() return wsSer } func (wsSer *wsServer) init() { wsSer.server.On("connect", func(ns *socketio.NameSpace) { wsSer.mu.Lock() defer wsSer.mu.Unlock() wsSer.clients[ns.Id()] = &wsClient{ns: ns, user: "guest"} log.Println("ws connected", ns.Id(), "ws_client_num:", len(wsSer.clients)) }) wsSer.server.On("disconnect", func(ns *socketio.NameSpace) { wsSer.remove(ns.Id()) log.Println("ws disconnect", ns.Id(), "ws_client_num:", len(wsSer.clients)) }) wsSer.server.On("error", func(ns *socketio.NameSpace, err error) { log.Println("ws error:", err) }) wsSer.server.On("get_response", wsSer.getResponse) wsSer.server.On("client_filter", wsSer.saveFilter) time_util.SetInterval(func() { wsSer.broadcast("hello", "hello", false) }, 120) } func (wsSer *wsServer) remove(id string) { wsSer.mu.Lock() defer wsSer.mu.Unlock() if _, has := wsSer.clients[id]; has { delete(wsSer.clients, id) } } func (wsSer *wsServer) broadProxyClientNum() { wsSer.broadcast("user_num", len(wsSer.proxySer.ProxyClients), false) } /* * https://github.com/googollee/go-socket.io */ func (wsSer *wsServer) getResponse(ns *socketio.NameSpace, docidStr string) { docid, uintParseErr := parseDocID(docidStr) if uintParseErr != nil { log.Println("parse str2int failed", docidStr, uintParseErr) return } log.Println("receive docid", docid) req, _ := wsSer.proxySer.getRequestByDocid(docid) res, _ := wsSer.proxySer.getResponseByDocid(docid) if wsSer.proxySer.Debug { fmt.Println("req:\n", req, "\n==========\n") fmt.Println("res:\n", res, "\n==========\n") } // delete(req,"header") data := make(map[string]any) data["req"] = nil data["res"] = nil if req != nil { data["req"] = req.Data } if res != nil { data["res"] = res.Data } wsSer.send(ns, "res", data, true) } func (wsSer *wsServer) saveFilter(ns *socketio.NameSpace, formData string) { m, err := url.ParseQuery(formData) if err != nil { log.Println("parse filter data err", err) return } wsSer.mu.Lock() defer wsSer.mu.Unlock() if nsClient, has := wsSer.clients[ns.Id()]; has { nsClient.filterIP = parseURLInputAsSlice(m.Get("client_ip")) nsClient.filterHideExt = m["hide"] nsClient.filterURL = parseURLInputAsSlice(m.Get("url_match")) nsClient.filterURLHide = parseURLInputAsSlice(m.Get("hide_url")) nsClient.filterUser = parseURLInputAsSlice(m.Get("user")) loginUser, isLogin := wsSer.proxySer.web_checkLogin(ns.Session.Request) if isLogin { nsClient.LoginUser = loginUser } } else { log.Println("ws_saveFilter failed,ws not exists") } } var nnnn int func (wsSer *wsServer) send(ns *socketio.NameSpace, msgName string, data any, encode bool) { wsSer.mu.Lock() defer func(ns *socketio.NameSpace) { wsSer.mu.Unlock() if e := recover(); e != nil { log.Println("ws_send failed", e, ns.Session.Request.RemoteAddr, "msgName:", msgName, "client:", len(wsSer.clients)) wsSer.remove(ns.Id()) } }(ns) var err error encode = false if encode { err = ns.Emit(msgName, dataEncode(data)) } else { err = ns.Emit(msgName, data) } if err != nil { log.Println("emit_failed", msgName, err) } } func (wsSer *wsServer) broadcastReq(req *http.Request, reqCtx *requestCtx, data any) bool { wsSer.mu.RLock() defer wsSer.mu.RUnlock() hasSend := false for _, client := range wsSer.clients { if wsSer.proxySer.conf.SessionView == sessionViewIPOrUser && len(client.filterIP) == 0 && len(client.filterUser) == 0 { continue } if reqCtx.User.Name != "" && len(client.filterUser) < 1 { continue } if client.checkFilter(req, reqCtx) { go wsSer.send(client.ns, "req", data, true) hasSend = true } } return hasSend } func (wsSer *wsServer) broadcast(name string, data any, encode bool) { wsSer.mu.RLock() defer wsSer.mu.RUnlock() for _, client := range wsSer.clients { go wsSer.send(client.ns, name, data, encode) } }