Merge pull request #322 from fatedier/dev

bump version to 0.10.0
This commit is contained in:
fatedier 2017-05-18 13:47:43 -05:00 committed by GitHub
commit 11260389a1
193 changed files with 11817 additions and 5409 deletions

26
.github/ISSUE_TEMPLATE vendored Normal file
View File

@ -0,0 +1,26 @@
Issue is only used for submiting bug report and documents typo. If there are same issues or answers can be found in documents, we will close it directly.
Use the commands below to provide key information from your environment:
You do NOT have to include this information if this is a FEATURE REQUEST
**What version of frp are you using (./frpc -v or ./frps -v)?**
**What operating system and processor architecture are you using (`go env`)?**
**Steps to reproduce the issue:**
1.
2.
3.
**Describe the results you received:**
**Describe the results you expected:**
**Additional information you deem important (e.g. issue happens only occasionally):**
**Can you point out what caused this issue (optional)**

View File

@ -2,9 +2,8 @@ sudo: false
language: go
go:
- 1.5.4
- 1.6.4
- 1.7.4
- 1.7.5
- 1.8
install:
- make

View File

@ -1,7 +1,7 @@
FROM alpine:3.4
FROM alpine:3.5
COPY bin/frpc /frpc
COPY bin/frps /frps
COPY tmp/frpc /frpc
COPY tmp/frps /frps
COPY conf/frpc_min.ini /frpc.ini
COPY conf/frps_min.ini /frps.ini

37
Godeps/Godeps.json generated
View File

@ -1,16 +1,11 @@
{
"ImportPath": "github.com/fatedier/frp",
"GoVersion": "go1.7",
"GodepVersion": "v75",
"GoVersion": "go1.8",
"GodepVersion": "v79",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "github.com/astaxie/beego/logs",
"Comment": "v1.7.0-7-gefbde1e",
"Rev": "efbde1ee77517486eac03e814e01d724ddad18e6"
},
{
"ImportPath": "github.com/davecgh/go-spew/spew",
"Comment": "v1.1.0",
@ -21,6 +16,25 @@
"Comment": "0.6.2",
"Rev": "784ddc588536785e7299f7272f39101f7faccc3f"
},
{
"ImportPath": "github.com/fatedier/beego/logs",
"Comment": "v1.7.2-72-gf73c369",
"Rev": "f73c3692bbd70a83728cb59b2c0423ff95e4ecea"
},
{
"ImportPath": "github.com/golang/snappy",
"Rev": "5979233c5d6225d4a8e438cdd0b411888449ddab"
},
{
"ImportPath": "github.com/julienschmidt/httprouter",
"Comment": "v1.1-41-g8a45e95",
"Rev": "8a45e95fc75cb77048068a62daed98cc22fdac7c"
},
{
"ImportPath": "github.com/pkg/errors",
"Comment": "v0.8.0-5-gc605e28",
"Rev": "c605e284fe17294bda444b34710735b29d1a9d90"
},
{
"ImportPath": "github.com/pmezard/go-difflib/difflib",
"Comment": "v1.0.0",
@ -39,6 +53,15 @@
{
"ImportPath": "github.com/vaughan0/go-ini",
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
},
{
"ImportPath": "github.com/xtaci/smux",
"Comment": "v1.0.5-8-g2de5471",
"Rev": "2de5471dfcbc029f5fe1392b83fe784127c4943e"
},
{
"ImportPath": "golang.org/x/crypto/pbkdf2",
"Rev": "1f22c0103821b9390939b6776727195525381532"
}
]
}

View File

@ -3,53 +3,46 @@ export GO15VENDOREXPERIMENT := 1
all: fmt build
build: frps frpc build_test
build_test: echo_server http_server
build: frps frpc
# compile assets into binary file
assets:
file:
rm -rf ./assets/static/*
cp -rf ./web/frps/dist/* ./assets/static
go get -d github.com/rakyll/statik
@go install github.com/rakyll/statik
@rm -rf ./src/assets/statik
go generate ./src/...
go install github.com/rakyll/statik
rm -rf ./assets/statik
go generate ./assets/...
fmt:
go fmt ./src/...
@go fmt ./test/echo_server.go
@go fmt ./test/http_server.go
@go fmt ./test/func_test.go
go fmt ./...
frps:
go build -o bin/frps ./src/cmd/frps
@cp -rf ./src/assets/static ./bin
go build -o bin/frps ./cmd/frps
@cp -rf ./assets/static ./bin
frpc:
go build -o bin/frpc ./src/cmd/frpc
echo_server:
go build -o test/bin/echo_server ./test/echo_server.go
http_server:
go build -o test/bin/http_server ./test/http_server.go
go build -o bin/frpc ./cmd/frpc
test: gotest
gotest:
go test -v ./src/...
go test -v ./assets/...
go test -v ./client/...
go test -v ./cmd/...
go test -v ./models/...
go test -v ./server/...
go test -v ./utils/...
alltest:
cd ./test && ./run_test.sh && cd -
go test -v ./src/...
go test -v ./test/func_test.go
cd ./test && ./clean_test.sh && cd -
alltest: gotest
cd ./tests && ./run_test.sh && cd -
go test -v ./tests/...
cd ./tests && ./clean_test.sh && cd -
clean:
rm -f ./bin/frpc
rm -f ./bin/frps
rm -f ./test/bin/echo_server
rm -f ./test/bin/http_server
cd ./test && ./clean_test.sh && cd -
save:
godep save ./src/...
godep save ./...

View File

@ -1,26 +1,32 @@
export PATH := $(GOPATH)/bin:$(PATH)
export GO15VENDOREXPERIMENT := 1
LDFLAGS := -s -w
all: build
build: app
app:
env GOOS=darwin GOARCH=386 go build -o ./frpc_darwin_386 ./src/cmd/frpc
env GOOS=darwin GOARCH=386 go build -o ./frps_darwin_386 ./src/cmd/frps
env GOOS=darwin GOARCH=amd64 go build -o ./frpc_darwin_amd64 ./src/cmd/frpc
env GOOS=darwin GOARCH=amd64 go build -o ./frps_darwin_amd64 ./src/cmd/frps
env GOOS=linux GOARCH=386 go build -o ./frpc_linux_386 ./src/cmd/frpc
env GOOS=linux GOARCH=386 go build -o ./frps_linux_386 ./src/cmd/frps
env GOOS=linux GOARCH=amd64 go build -o ./frpc_linux_amd64 ./src/cmd/frpc
env GOOS=linux GOARCH=amd64 go build -o ./frps_linux_amd64 ./src/cmd/frps
env GOOS=linux GOARCH=arm go build -o ./frpc_linux_arm ./src/cmd/frpc
env GOOS=linux GOARCH=arm go build -o ./frps_linux_arm ./src/cmd/frps
env GOOS=windows GOARCH=386 go build -o ./frpc_windows_386.exe ./src/cmd/frpc
env GOOS=windows GOARCH=386 go build -o ./frps_windows_386.exe ./src/cmd/frps
env GOOS=windows GOARCH=amd64 go build -o ./frpc_windows_amd64.exe ./src/cmd/frpc
env GOOS=windows GOARCH=amd64 go build -o ./frps_windows_amd64.exe ./src/cmd/frps
env GOOS=linux GOARCH=mips64 go build -o ./frpc_linux_mips64 ./src/cmd/frpc
env GOOS=linux GOARCH=mips64 go build -o ./frps_linux_mips64 ./src/cmd/frps
env GOOS=linux GOARCH=mips64le go build -o ./frpc_linux_mips64le ./src/cmd/frpc
env GOOS=linux GOARCH=mips64le go build -o ./frps_linux_mips64le ./src/cmd/frps
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_darwin_amd64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_darwin_amd64 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_386 ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_386 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_amd64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_amd64 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_arm ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags "$(LDFLAGS)" -o ./frps_linux_arm ./cmd/frps
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_windows_386.exe ./cmd/frpc
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_windows_386.exe ./cmd/frps
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_windows_amd64.exe ./cmd/frpc
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_windows_amd64.exe ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips64 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips64le ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips64le ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mipsle ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mipsle ./cmd/frps
temp:
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_amd64 ./cmd/frps

181
README.md
View File

@ -15,7 +15,7 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
* [Status](#status)
* [Architecture](#architecture)
* [Example Usage](#example-usage)
* [Communicate with your computer in LAN by SSH](#communicate-with-your-computer-in-lan-by-ssh)
* [Access your computer in LAN by SSH](#access-your-computer-in-lan-by-ssh)
* [Visit your web service in LAN by custom domains](#visit-your-web-service-in-lan-by-custom-domains)
* [Forward DNS query request](#forward-dns-query-request)
* [Features](#features)
@ -25,6 +25,7 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
* [Reload configures without frps stopped](#reload-configures-without-frps-stopped)
* [Privilege Mode](#privilege-mode)
* [Port White List](#port-white-list)
* [TCP Stream Multiplexing](#tcp-stream-multiplexing)
* [Connection Pool](#connection-pool)
* [Rewriting the Host Header](#rewriting-the-host-header)
* [Password protecting your web service](#password-protecting-your-web-service)
@ -36,15 +37,13 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
* [Donation](#donation)
* [AliPay](#alipay)
* [Paypal](#paypal)
* [Contributors](#contributors)
<!-- vim-markdown-toc -->
## What can I do with frp?
* Expose any http and https service behind a NAT or firewall to the internet by a server with public IP address(Name-based Virtual Host Support).
* Expose any tcp service behind a NAT or firewall to the internet by a server with public IP address.
* Inspect all http requests/responses that are transmitted over the tunnel(future).
* Expose any tcp or udp service behind a NAT or firewall to the internet by a server with public IP address.
## Status
@ -64,37 +63,33 @@ Put **frps** and **frps.ini** to your server with public IP.
Put **frpc** and **frpc.ini** to your server in LAN.
### Communicate with your computer in LAN by SSH
### Access your computer in LAN by SSH
1. Modify frps.ini, configure a reverse proxy named [ssh]:
1. Modify frps.ini:
```ini
# frps.ini
[common]
bind_port = 7000
[ssh]
listen_port = 6000
auth_token = 123
```
2. Start frps:
`./frps -c ./frps.ini`
3. Modify frpc.ini, set remote frps's server IP as x.x.x.x:
3. Modify frpc.ini, `server_addr` is your frps's server IP:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
auth_token = 123
[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000
```
4. Start frpc:
@ -111,18 +106,13 @@ Sometimes we want to expose a local web service behind a NAT network to others f
However, we can expose a http or https service using frp.
1. Modify frps.ini, configure a http reverse proxy named [web] and set http port as 8080, custom domain as `www.yourdomain.com`:
1. Modify frps.ini, configure http port 8080:
```ini
# frps.ini
[common]
bind_port = 7000
vhost_http_port = 8080
[web]
type = http
custom_domains = www.yourdomain.com
auth_token = 123
```
2. Start frps:
@ -136,11 +126,11 @@ However, we can expose a http or https service using frp.
[common]
server_addr = x.x.x.x
server_port = 7000
auth_token = 123
[web]
type = http
local_port = 80
custom_domains = www.yourdomain.com
```
4. Start frpc:
@ -159,11 +149,6 @@ However, we can expose a http or https service using frp.
# frps.ini
[common]
bind_port = 7000
[dns]
type = udp
listen_port = 6000
auth_token = 123
```
2. Start frps:
@ -177,12 +162,12 @@ However, we can expose a http or https service using frp.
[common]
server_addr = x.x.x.x
server_port = 7000
auth_token = 123
[dns]
type = udp
local_ip = 8.8.8.8
local_port = 53
remote_port = 6000
```
4. Start frpc:
@ -215,121 +200,58 @@ Then visit `http://[server_addr]:7500` to see dashboard, default username and pa
### Authentication
`auth_token` in frps.ini is configured for each proxy and check for authentication when frpc login in.
Since v0.10.0, you only need to set `privilege_token` in frps.ini and frpc.ini.
Client that want's to register must set a global `auth_token` equals to frps.ini.
Note that time duration between frpc and frps mustn't exceed 15 minutes because timestamp is used for authentication.
Note that time duration between server of frpc and frps mustn't exceed 15 minutes because timestamp is used for authentication.
Howerver, this timeout duration can be modified by setting `authentication_timeout` in frps's configure file. It's defalut value is 900, means 15 minutes. If it is equals 0, then frps will not check authentication timeout.
### Encryption and Compression
Defalut value is false, you could decide if the proxy will use encryption or compression whether the type is:
Defalut value is false, you could decide if the proxy will use encryption or compression:
```ini
# frpc.ini
[ssh]
type = tcp
listen_port = 6000
auth_token = 123
local_port = 22
remote_port = 6000
use_encryption = true
use_gzip = true
use_compression = true
```
### Reload configures without frps stopped
If you want to add a new reverse proxy and avoid restarting frps, you can use this function:
1. `dashboard_port` should be set in frps.ini:
```ini
# frps.ini
[common]
bind_port = 7000
dashboard_port = 7500
```
2. Start frps:
`./frps -c ./frps.ini`
3. Modify frps.ini to add a new proxy [new_ssh]:
```ini
# frps.ini
[common]
bind_port = 7000
dashboard_port = 7500
[new_ssh]
listen_port = 6001
auth_token = 123
```
4. Execute `reload` command:
`./frps -c ./frps.ini --reload`
5. Start frpc and [new_ssh] is available now.
This feature is removed since v0.10.0.
### Privilege Mode
Privilege mode is used for who don't want to do operations in frps everytime adding a new proxy.
All proxies's configurations are set in frpc.ini when privilege mode is enabled.
1. Enable privilege mode and set `privilege_token`.Client with the same `privilege_token` can create proxy automaticly:
```ini
# frps.ini
[common]
bind_port = 7000
privilege_mode = true
privilege_token = 1234
```
2. Start frps:
`./frps -c ./frps.ini`
3. Enable privilege mode for proxy [ssh]:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
privilege_token = 1234
[ssh]
privilege_mode = true
local_port = 22
remote_port = 6000
```
4. Start frpc:
`./frpc -c ./frpc.ini`
5. Connect to server in LAN by ssh assuming username is test:
`ssh -oPort=6000 test@x.x.x.x`
Privilege mode is the default and only mode support in frp since v0.10.0. All proxy configurations are set in client.
#### Port White List
`privilege_allow_ports` in frps.ini is used for preventing abuse of ports in privilege mode:
`privilege_allow_ports` in frps.ini is used for preventing abuse of ports:
```ini
# frps.ini
[common]
privilege_mode = true
privilege_token = 1234
privilege_allow_ports = 2000-3000,3001,3003,4000-50000
```
`privilege_allow_ports` consists of a specific port or a range of ports divided by `,`.
### TCP Stream Multiplexing
frp support tcp stream multiplexing since v0.10.0 like HTTP2 Multiplexing. All user requests to same frpc can use only one tcp connection.
You can disable this feature by modify frps.ini and frpc.ini:
```ini
# frps.ini and frpc.ini, must be same
[common]
tcp_mux = false
```
### Connection Pool
By default, frps send message to frpc for create a new connection to backward service when getting an user request.If a proxy's connection pool is enabled, there will be a specified number of connections pre-established.
@ -338,30 +260,27 @@ This feature is fit for a large number of short connections.
1. Configure the limit of pool count each proxy can use in frps.ini:
```ini
```ini
# frps.ini
[common]
max_pool_count = 50
max_pool_count = 5
```
2. Enable and specify the number of connection pool:
```ini
# frpc.ini
[ssh]
type = tcp
local_port = 22
pool_count = 10
[common]
pool_count = 1
```
### Rewriting the Host Header
When forwarding to a local port, frp does not modify the tunneled HTTP requests at all, they are copied to your server byte-for-byte as they are received. Some application servers use the Host header for determining which development site to display. For this reason, frp can rewrite your requests with a modified Host header. Use the `host_header_rewrite` switch to rewrite incoming HTTP requests.
```ini
# frpc.ini
```ini
# frpc.ini
[web]
privilege_mode = true
type = http
local_port = 80
custom_domains = test.yourdomain.com
@ -376,12 +295,11 @@ Anyone who can guess your tunnel URL can access your local web server unless you
This enforces HTTP Basic Auth on all requests with the username and password you specify in frpc's configure file.
It can be only enabled when proxy type is http.
It can only be enabled when proxy type is http.
```ini
# frpc.ini
[web]
privilege_mode = true
type = http
local_port = 80
custom_domains = test.yourdomain.com
@ -389,7 +307,7 @@ http_user = abc
http_pwd = abc
```
Visit `test.yourdomain.com` and now you need to input username and password.
Visit `http://test.yourdomain.com` and now you need to input username and password.
### Custom subdomain names
@ -405,7 +323,6 @@ Resolve `*.frps.com` to the frps server's IP.
```ini
# frpc.ini
[web]
privilege_mode = true
type = http
local_port = 80
subdomain = test
@ -424,14 +341,12 @@ frp support forward http requests to different backward web services by url rout
```ini
# frpc.ini
[web01]
privilege_mode = true
type = http
local_port = 80
custom_domains = web.yourdomain.com
locations = /
[web02]
privilege_mode = true
type = http
local_port = 81
custom_domains = web.yourdomain.com
@ -455,11 +370,12 @@ http_proxy = http://user:pwd@192.168.1.128:8080
* Log http request information in frps.
* Direct reverse proxy, like haproxy.
* Load balance to different service in frpc.
* Debug mode for frpc, prestent proxy status in terminal.
* Inspect all http requests/responses that are transmitted over the tunnel.
* Frpc can directly be a webserver for static files.
* Full control mode, dynamically modify frpc's configure with dashboard in frps.
* P2p communicate by make udp hole to penetrate NAT.
* Client Plugin (http proxy).
* kubernetes ingress support.
## Contributing
@ -485,16 +401,3 @@ frp QQ group: 606194980
### Paypal
Donate money by [paypal](https://www.paypal.me/fatedier) to my account **fatedier@gmail.com**.
## Contributors
* [fatedier](https://github.com/fatedier)
* [Hurricanezwf](https://github.com/Hurricanezwf)
* [Pan Hao](https://github.com/vashstorm)
* [Danping Mao](https://github.com/maodanp)
* [Eric Larssen](https://github.com/ericlarssen)
* [Damon Zhao](https://github.com/se77en)
* [Manfred Touron](https://github.com/moul)
* [xuebing1110](https://github.com/xuebing1110)
* [Anbitioner](https://github.com/bingtianbaihua)
* [LitleCarl](https://github.com/LitleCarl)

View File

@ -4,7 +4,7 @@
[README](README.md) | [中文文档](README_zh.md)
frp 是一个高性能的反向代理应用,可以帮助您轻松地进行内网穿透,对外网提供服务,支持 tcp, udp, http, https 协议类型,并且 web 服务支持根据域名进行路由转发
frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp, udp, http, https 协议。
## 目录
@ -23,33 +23,34 @@ frp 是一个高性能的反向代理应用,可以帮助您轻松地进行内
* [服务器端热加载配置文件](#服务器端热加载配置文件)
* [特权模式](#特权模式)
* [端口白名单](#端口白名单)
* [TCP 多路复用](#tcp-多路复用)
* [连接池](#连接池)
* [修改 Host Header](#修改-host-header)
* [通过密码保护你的 web 服务](#通过密码保护你的-web-服务)
* [自定义二级域名](#自定义二级域名)
* [URL 路由](#url-路由)
* [通过 HTTP PROXY 连接 frps](#通过-http-proxy-连接-frps)
* [通过代理连接 frps](#通过代理连接-frps)
* [开发计划](#开发计划)
* [为 frp 做贡献](#为-frp-做贡献)
* [捐助](#捐助)
* [支付宝扫码捐赠](#支付宝扫码捐赠)
* [Paypal 捐赠](#paypal-捐赠)
* [贡献者](#贡献者)
<!-- vim-markdown-toc -->
## frp 的作用
* 利用处于内网或防火墙后的机器,对外网环境提供 http 或 https 服务。
* 对于 http 服务支持基于域名的虚拟主机支持自定义域名绑定使多个域名可以共用一个80端口。
* 利用处于内网或防火墙后的机器,对外网环境提供 tcp 服务,例如在家里通过 ssh 访问处于公司内网环境内的主机。
* 可查看通过代理的所有 http 请求和响应的详细信息。(待开发)
* 对于 http, https 服务支持基于域名的虚拟主机支持自定义域名绑定使多个域名可以共用一个80端口。
* 利用处于内网或防火墙后的机器,对外网环境提供 tcp 和 udp 服务,例如在家里通过 ssh 访问处于公司内网环境内的主机。
## 开发状态
frp 目前正在前期开发阶段master 分支用于发布稳定版本dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试
frp 仍然处于前期开发阶段,未经充分测试与验证,不推荐用于生产环境
**目前的交互协议可能随时改变,不能保证向后兼容,升级新版本时需要注意公告说明。**
master 分支用于发布稳定版本dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。
**目前的交互协议可能随时改变,不保证向后兼容,升级新版本时需要注意公告说明同时升级服务端和客户端。**
## 架构
@ -59,40 +60,37 @@ frp 目前正在前期开发阶段master 分支用于发布稳定版本dev
根据对应的操作系统及架构,从 [Release](https://github.com/fatedier/frp/releases) 页面下载最新版本的程序。
**frps****frps.ini** 放到有公网 IP 的机器上。
**frps****frps.ini** 放到有公网 IP 的机器上。
**frpc****frpc.ini** 放到处于内网环境的机器上。
### 通过 ssh 访问公司内网机器
1. 修改 frps.ini 文件,配置一个名为 ssh 的反向代理
1. 修改 frps.ini 文件,这里使用了最简化的配置:
```ini
# frps.ini
[common]
bind_port = 7000
[ssh]
listen_port = 6000
auth_token = 123
```
2. 启动 frps
`./frps -c ./frps.ini`
3. 修改 frpc.ini 文件,设 frps 所在服务器的 IP 为 x.x.x.x
3. 修改 frpc.ini 文件,设 frps 所在服务器的公网 IP 为 x.x.x.x
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
auth_token = 123
[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000
```
4. 启动 frpc
@ -107,36 +105,31 @@ frp 目前正在前期开发阶段master 分支用于发布稳定版本dev
有时想要让其他人通过域名访问或者测试我们在本地搭建的 web 服务,但是由于本地机器没有公网 IP无法将域名解析到本地的机器通过 frp 就可以实现这一功能,以下示例为 http 服务https 服务配置方法相同, vhost_http_port 替换为 vhost_https_port type 设置为 https 即可。
1. 修改 frps.ini 文件,配置一个名为 web 的 http 反向代理,设置 http 访问端口为 8080,绑定自定义域名 `www.yourdomain.com`
1. 修改 frps.ini 文件,设置 http 访问端口为 8080
```ini
# frps.ini
[common]
bind_port = 7000
vhost_http_port = 8080
[web]
type = http
custom_domains = www.yourdomain.com
auth_token = 123
```
2. 启动 frps
`./frps -c ./frps.ini`
3. 修改 frpc.ini 文件,设 frps 所在的服务器的 IP 为 x.x.x.xlocal_port 为本地机器上 web 服务对应的端口
3. 修改 frpc.ini 文件,设 frps 所在的服务器的 IP 为 x.x.x.xlocal_port 为本地机器上 web 服务对应的端口, 绑定自定义域名 `www.yourdomain.com`:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
auth_token = 123
[web]
type = http
local_port = 80
custom_domains = www.yourdomain.com
```
4. 启动 frpc
@ -151,17 +144,12 @@ frp 目前正在前期开发阶段master 分支用于发布稳定版本dev
DNS 查询请求通常使用 UDP 协议frp 支持对内网 UDP 服务的穿透,配置方式和 TCP 基本一致。
1. 修改 frps.ini 文件,配置一个名为 dns 的反向代理
1. 修改 frps.ini 文件:
```ini
# frps.ini
[common]
bind_port = 7000
[dns]
type = udp
listen_port = 6000
auth_token = 123
```
2. 启动 frps
@ -175,12 +163,12 @@ DNS 查询请求通常使用 UDP 协议frp 支持对内网 UDP 服务的穿
[common]
server_addr = x.x.x.x
server_port = 7000
auth_token = 123
[dns]
type = udp
local_ip = 8.8.8.8
local_port = 53
remote_port = 6000
```
4. 启动 frpc
@ -202,7 +190,7 @@ DNS 查询请求通常使用 UDP 协议frp 支持对内网 UDP 服务的穿
```ini
[common]
dashboard_port = 7500
# dashboard 用户名密码可选,默认都为 admin
# dashboard 用户名密码,默认都为 admin
dashboard_user = admin
dashboard_pwd = admin
```
@ -213,9 +201,7 @@ dashboard_pwd = admin
### 身份验证
出于安全性的考虑,服务器端可以在 frps.ini 中为每一个代理设置一个 auth_token 用于对客户端连接进行身份验证,例如上文中的 [ssh] 和 [web] 两个代理的 auth_token 都为 123。
客户端需要在 frpc.ini 中配置自己的 auth_token与服务器中的配置一致才能正常运行。
从 v0.10.0 版本开始,所有 proxy 配置全部放在客户端(也就是之前版本的特权模式),服务端和客户端的 common 配置中的 `privilege_token` 参数一致则身份验证通过。
需要注意的是 frpc 所在机器和 frps 所在机器的时间相差不能超过 15 分钟,因为时间戳会被用于加密验证中,防止报文被劫持后被其他人利用。
@ -223,141 +209,74 @@ dashboard_pwd = admin
### 加密与压缩
这两个功能默认是不开启的,需要在 frpc.ini 中通过配置来为指定的代理启用加密与压缩的功能,无论类型是 tcp, http 还是 https
这两个功能默认是不开启的,需要在 frpc.ini 中通过配置来为指定的代理启用加密与压缩的功能,压缩算法使用 snappy
```ini
# frpc.ini
[ssh]
type = tcp
listen_port = 6000
auth_token = 123
local_port = 22
remote_port = 6000
use_encryption = true
use_gzip = true
use_compression = true
```
如果公司内网防火墙对外网访问进行了流量识别与屏蔽,例如禁止了 ssh 协议等,通过设置 `use_encryption = true`,将 frpc 与 frps 之间的通信内容加密传输,将会有效防止流量被拦截。
如果传输的报文长度较长,通过设置 `use_gzip = true` 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 cpu 资源。
如果传输的报文长度较长,通过设置 `use_compression = true` 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 cpu 资源。
### 服务器端热加载配置文件
当需要新增一个 frpc 客户端时,为了避免将 frps 重启,可以使用 reload 命令重新加载配置文件。
reload 命令仅能用于修改代理的配置内容,[common] 内的公共配置信息无法修改。
1. 首先需要在 frps.ini 中指定 dashboard_port
```ini
# frps.ini
[common]
bind_port = 7000
dashboard_port = 7500
```
2. 启动 frps
`./frps -c ./frps.ini`
3. 修改 frps.ini 增加一个新的代理 [new_ssh]:
```ini
# frps.ini
[common]
bind_port = 7000
dashboard_port = 7500
[new_ssh]
listen_port = 6001
auth_token = 123
```
4. 执行 reload 命令,使 frps 重新加载配置文件,实际上是通过 7500 端口发送了一个 http 请求
`./frps -c ./frps.ini --reload`
5. 之后启动 frpc[new_ssh] 代理已经可以使用。
由于从 v0.10.0 版本开始,所有 proxy 都在客户端配置,这个功能暂时移除。
### 特权模式
如果想要避免每次增加代理都需要操作服务器端,可以启用特权模式。
特权模式被启用后,代理的所有配置信息都可以在 frpc.ini 中配置,无需在服务器端做任何操作。
1. 在 frps.ini 中设置启用特权模式并设置 privilege_token客户端需要配置同样的 privilege_token 才能使用特权模式创建代理:
```ini
# frps.ini
[common]
bind_port = 7000
privilege_mode = true
privilege_token = 1234
```
2. 启动 frps
`./frps -c ./frps.ini`
3. 在 frpc.ini 配置代理 [ssh],使用特权模式创建,无需事先在服务器端配置:
```ini
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
privilege_token = 1234
[ssh]
privilege_mode = true
local_port = 22
remote_port = 6000
```
remote_port 即为原先在 frps.ini 的代理中配置的 listen_port 参数,使用特权模式后需要在 frpc 的配置文件中指定。
4. 启动 frpc
`./frpc -c ./frpc.ini`
5. 通过 ssh 访问内网机器,假设用户名为 test
`ssh -oPort=6000 test@x.x.x.x`
由于从 v0.10.0 版本开始,所有 proxy 都在客户端配置,原先的特权模式是目前唯一支持的模式。
#### 端口白名单
启用特权模式后为了防止端口被滥用,可以手动指定允许哪些端口被使用,在 frps.ini 中通过 privilege_allow_ports 来指定:
为了防止端口被滥用,可以手动指定允许哪些端口被使用,在 frps.ini 中通过 privilege_allow_ports 来指定:
```ini
# frps.ini
[common]
privilege_mode = true
privilege_token = 1234
privilege_allow_ports = 2000-3000,3001,3003,4000-50000
```
privilege_allow_ports 可以配置允许使用的某个指定端口或者是一个范围内的所有端口,以 `,` 分隔,指定的范围以 `-` 分隔。
### TCP 多路复用
从 v0.10.0 版本开始,客户端和服务器端之间的连接支持多路复用,不再需要为每一个用户请求创建一个连接,使连接建立的延迟降低,并且避免了大量文件描述符的占用,使 frp 可以承载更高的并发数。
该功能默认启用,如需关闭,可以在 frps.ini 和 frpc.ini 中配置,该配置项在服务端和客户端必须一致:
```ini
# frps.ini 和 frpc.ini 中
[common]
tcp_mux = false
```
### 连接池
默认情况下当用户请求建立连接后frps 才会请求 frpc 主动与后端服务建立一个连接。当为指定的代理启用连接池后frp 会预先和后端服务建立起指定数量的连接,每次接收到用户请求后,会从连接池中取出一个连接和用户连接关联起来,避免了等待与后端服务建立连接以及 frpc 和 frps 之间传递控制信息的时间。
这一功能比较适合有大量短连接请求时开启。
1. 首先可以在 frps.ini 中设置每个代理可以创建的连接池上限,避免大量资源占用,默认为 100客户端设置超过此配置后会被调整到当前值
1. 首先可以在 frps.ini 中设置每个代理可以创建的连接池上限,避免大量资源占用,客户端设置超过此配置后会被调整到当前值:
```ini
# frps.ini
[common]
max_pool_count = 50
max_pool_count = 5
```
2. 在 frpc.ini 中为指定代理启用连接池,指定预创建连接的数量:
2. 在 frpc.ini 中为客户端启用连接池,指定预创建连接的数量:
```ini
# frpc.ini
[ssh]
type = tcp
local_port = 22
pool_count = 10
[common]
pool_count = 1
```
### 修改 Host Header
@ -367,7 +286,6 @@ privilege_allow_ports 可以配置允许使用的某个指定端口或者是一
```ini
# frpc.ini
[web]
privilege_mode = true
type = http
local_port = 80
custom_domains = test.yourdomain.com
@ -387,7 +305,6 @@ frp 支持通过 HTTP Basic Auth 来保护你的 web 服务,使用户需要通
```ini
# frpc.ini
[web]
privilege_mode = true
type = http
local_port = 80
custom_domains = test.yourdomain.com
@ -395,7 +312,7 @@ http_user = abc
http_pwd = abc
```
通过浏览器访问 `test.yourdomain.com`,需要输入配置的用户名和密码才能访问。
通过浏览器访问 `http://test.yourdomain.com`,需要输入配置的用户名和密码才能访问。
### 自定义二级域名
@ -403,10 +320,11 @@ http_pwd = abc
通过在 frps 的配置文件中配置 `subdomain_host`,就可以启用该特性。之后在 frpc 的 http、https 类型的代理中可以不配置 `custom_domains`,而是配置一个 `subdomain` 参数。
只需要将 `*.subdomain_host` 解析到 frps 所在服务器。之后用户可以通过 `subdomain` 自行指定自己的 web 服务所需要使用的二级域名,通过 `{subdomain}.{subdomain_host}` 来访问自己的 web 服务。
只需要将 `*.{subdomain_host}` 解析到 frps 所在服务器。之后用户可以通过 `subdomain` 自行指定自己的 web 服务所需要使用的二级域名,通过 `{subdomain}.{subdomain_host}` 来访问自己的 web 服务。
```ini
# frps.ini
[common]
subdomain_host = frps.com
```
@ -415,7 +333,6 @@ subdomain_host = frps.com
```ini
# frpc.ini
[web]
privilege_mode = true
type = http
local_port = 80
subdomain = test
@ -436,14 +353,12 @@ frp 支持根据请求的 URL 路径路由转发到不同的后端服务。
```ini
# frpc.ini
[web01]
privilege_mode = true
type = http
local_port = 80
custom_domains = web.yourdomain.com
locations = /
[web02]
privilege_mode = true
type = http
local_port = 81
custom_domains = web.yourdomain.com
@ -452,7 +367,7 @@ locations = /news,/about
按照上述的示例配置后,`web.yourdomain.com` 这个域名下所有以 `/news` 以及 `/about` 作为前缀的 URL 请求都会被转发到 web02其余的请求会被转发到 web01。
### 通过 HTTP PROXY 连接 frps
### 通过代理连接 frps
在只能通过代理访问外网的环境内frpc 支持通过 HTTP PROXY 和 frps 进行通信。
@ -472,11 +387,11 @@ http_proxy = http://user:pwd@192.168.1.128:8080
* frps 记录 http 请求日志。
* frps 支持直接反向代理,类似 haproxy。
* frpc 支持负载均衡到后端不同服务。
* frpc debug 模式,控制台显示代理状态,类似 ngrok 启动后的界面。
* frpc http 请求及响应信息展示。
* frpc 支持直接作为 webserver 访问指定静态页面。
* frpc 完全控制模式,通过 dashboard 对 frpc 进行在线操作。
* 支持 udp 打洞的方式,提供两边内网机器直接通信,流量不经过服务器转发。
* 支持 pluginfrpc 获取到的连接可以交给指定 plugin 处理,例如 http 代理,简单的 web server。
* 集成对 k8s 等平台的支持。
## 为 frp 做贡献
@ -504,16 +419,3 @@ frp 交流群606194980 (QQ 群号)
### Paypal 捐赠
海外用户推荐通过 [Paypal](https://www.paypal.me/fatedier) 向我的账户 **fatedier@gmail.com** 进行捐赠。
## 贡献者
* [fatedier](https://github.com/fatedier)
* [Hurricanezwf](https://github.com/Hurricanezwf)
* [Pan Hao](https://github.com/vashstorm)
* [Danping Mao](https://github.com/maodanp)
* [Eric Larssen](https://github.com/ericlarssen)
* [Damon Zhao](https://github.com/se77en)
* [Manfred Touron](https://github.com/moul)
* [xuebing1110](https://github.com/xuebing1110)
* [Anbitioner](https://github.com/bingtianbaihua)
* [LitleCarl](https://github.com/LitleCarl)

View File

@ -25,7 +25,7 @@ import (
"github.com/rakyll/statik/fs"
_ "github.com/fatedier/frp/src/assets/statik"
_ "github.com/fatedier/frp/assets/statik"
)
var (

Binary file not shown.

BIN
assets/static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

1
assets/static/index.html Normal file
View File

@ -0,0 +1 @@
<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <title>frps dashboard</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?b52826060da73c6b5a10"></script><script type="text/javascript" src="vendor.js?66dfcf2d1c500e900413"></script><script type="text/javascript" src="index.js?ceb589f1be7a87112dbd"></script></body> </html>

25
assets/static/index.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(e){function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}var n=window.webpackJsonp;window.webpackJsonp=function(t,c,u){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in c)Object.prototype.hasOwnProperty.call(c,i)&&(e[i]=c[i]);for(n&&n(t,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=r(r.s=u[l]);return f};var t={},o={2:0};r.e=function(e){function n(){u.onerror=u.onload=null,clearTimeout(i);var r=o[e];0!==r&&(r&&r[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}if(0===o[e])return Promise.resolve();if(o[e])return o[e][2];var t=new Promise(function(r,n){o[e]=[r,n]});o[e][2]=t;var c=document.getElementsByTagName("head")[0],u=document.createElement("script");u.type="text/javascript",u.charset="utf-8",u.async=!0,u.timeout=12e4,r.nc&&u.setAttribute("nonce",r.nc),u.src=r.p+""+e+".js?"+{0:"ceb589f1be7a87112dbd",1:"66dfcf2d1c500e900413"}[e];var i=setTimeout(n,12e4);return u.onerror=u.onload=n,c.appendChild(u),t},r.m=e,r.c=t,r.i=function(e){return e},r.d=function(e,n,t){r.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:t})},r.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(n,"a",n),n},r.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},r.p="",r.oe=function(e){throw console.error(e),e}}([]);

6
assets/static/vendor.js Normal file

File diff suppressed because one or more lines are too long

10
assets/statik/statik.go Normal file

File diff suppressed because one or more lines are too long

432
client/control.go Normal file
View File

@ -0,0 +1,432 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"fmt"
"io"
"runtime"
"sync"
"time"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/crypto"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/util"
"github.com/fatedier/frp/utils/version"
"github.com/xtaci/smux"
)
const (
connReadTimeout time.Duration = 10 * time.Second
)
type Control struct {
// frpc service
svr *Service
// login message to server
loginMsg *msg.Login
// proxy configures
pxyCfgs map[string]config.ProxyConf
// proxies
proxies map[string]Proxy
// control connection
conn net.Conn
// tcp stream multiplexing, if enabled
session *smux.Session
// put a message in this channel to send it over control connection to server
sendCh chan (msg.Message)
// read from this channel to get the next message sent by server
readCh chan (msg.Message)
// run id got from server
runId string
// connection or other error happens , control will try to reconnect to server
closed int32
// goroutines can block by reading from this channel, it will be closed only in reader() when control connection is closed
closedCh chan int
// last time got the Pong message
lastPong time.Time
mu sync.RWMutex
log.Logger
}
func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf) *Control {
loginMsg := &msg.Login{
Arch: runtime.GOARCH,
Os: runtime.GOOS,
PoolCount: config.ClientCommonCfg.PoolCount,
User: config.ClientCommonCfg.User,
Version: version.Full(),
}
return &Control{
svr: svr,
loginMsg: loginMsg,
pxyCfgs: pxyCfgs,
proxies: make(map[string]Proxy),
sendCh: make(chan msg.Message, 10),
readCh: make(chan msg.Message, 10),
closedCh: make(chan int),
Logger: log.NewPrefixLogger(""),
}
}
// 1. login
// 2. start reader() writer() manager()
// 3. connection closed
// 4. In reader(): close closedCh and exit, controler() get it
// 5. In controler(): close readCh and sendCh, manager() and writer() will exit
// 6. In controler(): ini readCh, sendCh, closedCh
// 7. In controler(): start new reader(), writer(), manager()
// controler() will keep running
func (ctl *Control) Run() error {
err := ctl.login()
if err != nil {
return err
}
go ctl.controler()
go ctl.manager()
go ctl.writer()
go ctl.reader()
// send NewProxy message for all configured proxies
for _, cfg := range ctl.pxyCfgs {
var newProxyMsg msg.NewProxy
cfg.UnMarshalToMsg(&newProxyMsg)
ctl.sendCh <- &newProxyMsg
}
return nil
}
func (ctl *Control) NewWorkConn() {
var (
workConn net.Conn
err error
)
if config.ClientCommonCfg.TcpMux {
stream, err := ctl.session.OpenStream()
if err != nil {
ctl.Warn("start new work connection error: %v", err)
return
}
workConn = net.WrapConn(stream)
} else {
workConn, err = net.ConnectTcpServerByHttpProxy(config.ClientCommonCfg.HttpProxy,
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
if err != nil {
ctl.Warn("start new work connection error: %v", err)
return
}
}
m := &msg.NewWorkConn{
RunId: ctl.runId,
}
if err = msg.WriteMsg(workConn, m); err != nil {
ctl.Warn("work connection write to server error: %v", err)
workConn.Close()
return
}
var startMsg msg.StartWorkConn
if err = msg.ReadMsgInto(workConn, &startMsg); err != nil {
ctl.Error("work connection closed, %v", err)
workConn.Close()
return
}
workConn.AddLogPrefix(startMsg.ProxyName)
// dispatch this work connection to related proxy
if pxy, ok := ctl.proxies[startMsg.ProxyName]; ok {
workConn.Info("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
go pxy.InWorkConn(workConn)
} else {
workConn.Close()
}
}
func (ctl *Control) init() {
ctl.sendCh = make(chan msg.Message, 10)
ctl.readCh = make(chan msg.Message, 10)
ctl.closedCh = make(chan int)
}
// login send a login message to server and wait for a loginResp message.
func (ctl *Control) login() (err error) {
if ctl.conn != nil {
ctl.conn.Close()
}
if ctl.session != nil {
ctl.session.Close()
}
conn, err := net.ConnectTcpServerByHttpProxy(config.ClientCommonCfg.HttpProxy,
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
if err != nil {
return err
}
defer func() {
if err != nil {
conn.Close()
}
}()
if config.ClientCommonCfg.TcpMux {
session, errRet := smux.Client(conn, nil)
if errRet != nil {
return errRet
}
stream, errRet := session.OpenStream()
if errRet != nil {
session.Close()
return errRet
}
conn = net.WrapConn(stream)
ctl.session = session
}
now := time.Now().Unix()
ctl.loginMsg.PrivilegeKey = util.GetAuthKey(config.ClientCommonCfg.PrivilegeToken, now)
ctl.loginMsg.Timestamp = now
ctl.loginMsg.RunId = ctl.runId
if err = msg.WriteMsg(conn, ctl.loginMsg); err != nil {
return err
}
var loginRespMsg msg.LoginResp
conn.SetReadDeadline(time.Now().Add(connReadTimeout))
if err = msg.ReadMsgInto(conn, &loginRespMsg); err != nil {
return err
}
conn.SetReadDeadline(time.Time{})
if loginRespMsg.Error != "" {
err = fmt.Errorf("%s", loginRespMsg.Error)
ctl.Error("%s", loginRespMsg.Error)
return err
}
ctl.conn = conn
// update runId got from server
ctl.runId = loginRespMsg.RunId
ctl.ClearLogPrefix()
ctl.AddLogPrefix(loginRespMsg.RunId)
ctl.Info("login to server success, get run id [%s]", loginRespMsg.RunId)
// login success, so we let closedCh available again
ctl.closedCh = make(chan int)
ctl.lastPong = time.Now()
return nil
}
func (ctl *Control) reader() {
defer func() {
if err := recover(); err != nil {
ctl.Error("panic error: %v", err)
}
}()
defer close(ctl.closedCh)
encReader := crypto.NewReader(ctl.conn, []byte(config.ClientCommonCfg.PrivilegeToken))
for {
if m, err := msg.ReadMsg(encReader); err != nil {
if err == io.EOF {
ctl.Debug("read from control connection EOF")
return
} else {
ctl.Warn("read error: %v", err)
return
}
} else {
ctl.readCh <- m
}
}
}
func (ctl *Control) writer() {
encWriter, err := crypto.NewWriter(ctl.conn, []byte(config.ClientCommonCfg.PrivilegeToken))
if err != nil {
ctl.conn.Error("crypto new writer error: %v", err)
ctl.conn.Close()
return
}
for {
if m, ok := <-ctl.sendCh; !ok {
ctl.Info("control writer is closing")
return
} else {
if err := msg.WriteMsg(encWriter, m); err != nil {
ctl.Warn("write message to control connection error: %v", err)
return
}
}
}
}
func (ctl *Control) manager() {
defer func() {
if err := recover(); err != nil {
ctl.Error("panic error: %v", err)
}
}()
hbSend := time.NewTicker(time.Duration(config.ClientCommonCfg.HeartBeatInterval) * time.Second)
defer hbSend.Stop()
hbCheck := time.NewTicker(time.Second)
defer hbCheck.Stop()
for {
select {
case <-hbSend.C:
// send heartbeat to server
ctl.Debug("send heartbeat to server")
ctl.sendCh <- &msg.Ping{}
case <-hbCheck.C:
if time.Since(ctl.lastPong) > time.Duration(config.ClientCommonCfg.HeartBeatTimeout)*time.Second {
ctl.Warn("heartbeat timeout")
// let reader() stop
ctl.conn.Close()
return
}
case rawMsg, ok := <-ctl.readCh:
if !ok {
return
}
switch m := rawMsg.(type) {
case *msg.ReqWorkConn:
go ctl.NewWorkConn()
case *msg.NewProxyResp:
// Server will return NewProxyResp message to each NewProxy message.
// Start a new proxy handler if no error got
if m.Error != "" {
ctl.Warn("[%s] start error: %s", m.ProxyName, m.Error)
continue
}
cfg, ok := ctl.pxyCfgs[m.ProxyName]
if !ok {
// it will never go to this branch now
ctl.Warn("[%s] no proxy conf found", m.ProxyName)
continue
}
oldPxy, ok := ctl.proxies[m.ProxyName]
if ok {
oldPxy.Close()
}
pxy := NewProxy(ctl, cfg)
if err := pxy.Run(); err != nil {
ctl.Warn("[%s] proxy start running error: %v", m.ProxyName, err)
continue
}
ctl.proxies[m.ProxyName] = pxy
ctl.Info("[%s] start proxy success", m.ProxyName)
case *msg.Pong:
ctl.lastPong = time.Now()
ctl.Debug("receive heartbeat from server")
}
}
}
}
// control keep watching closedCh, start a new connection if previous control connection is closed
func (ctl *Control) controler() {
var err error
maxDelayTime := 30 * time.Second
delayTime := time.Second
checkInterval := 30 * time.Second
checkProxyTicker := time.NewTicker(checkInterval)
for {
select {
case <-checkProxyTicker.C:
// Every 30 seconds, check which proxy registered failed and reregister it to server.
for _, cfg := range ctl.pxyCfgs {
if _, exist := ctl.proxies[cfg.GetName()]; !exist {
ctl.Info("try to reregister proxy [%s]", cfg.GetName())
var newProxyMsg msg.NewProxy
cfg.UnMarshalToMsg(&newProxyMsg)
ctl.sendCh <- &newProxyMsg
}
}
case _, ok := <-ctl.closedCh:
// we won't get any variable from this channel
if !ok {
// close related channels
close(ctl.readCh)
close(ctl.sendCh)
for _, pxy := range ctl.proxies {
pxy.Close()
}
time.Sleep(time.Second)
// loop util reconnect to server success
for {
ctl.Info("try to reconnect to server...")
err = ctl.login()
if err != nil {
ctl.Warn("reconnect to server error: %v", err)
time.Sleep(delayTime)
delayTime = delayTime * 2
if delayTime > maxDelayTime {
delayTime = maxDelayTime
}
continue
}
// reconnect success, init the delayTime
delayTime = time.Second
break
}
// init related channels and variables
ctl.init()
// previous work goroutines should be closed and start them here
go ctl.manager()
go ctl.writer()
go ctl.reader()
// send NewProxy message for all configured proxies
for _, cfg := range ctl.pxyCfgs {
var newProxyMsg msg.NewProxy
cfg.UnMarshalToMsg(&newProxyMsg)
ctl.sendCh <- &newProxyMsg
}
checkProxyTicker.Stop()
checkProxyTicker = time.NewTicker(checkInterval)
}
}
}
}

266
client/proxy.go Normal file
View File

@ -0,0 +1,266 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"fmt"
"io"
"net"
"sync"
"time"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/models/proto/tcp"
"github.com/fatedier/frp/models/proto/udp"
"github.com/fatedier/frp/utils/errors"
"github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net"
)
// Proxy defines how to work for different proxy type.
type Proxy interface {
Run() error
// InWorkConn accept work connections registered to server.
InWorkConn(conn frpNet.Conn)
Close()
log.Logger
}
func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy) {
baseProxy := BaseProxy{
ctl: ctl,
Logger: log.NewPrefixLogger(pxyConf.GetName()),
}
switch cfg := pxyConf.(type) {
case *config.TcpProxyConf:
pxy = &TcpProxy{
BaseProxy: baseProxy,
cfg: cfg,
}
case *config.UdpProxyConf:
pxy = &UdpProxy{
BaseProxy: baseProxy,
cfg: cfg,
}
case *config.HttpProxyConf:
pxy = &HttpProxy{
BaseProxy: baseProxy,
cfg: cfg,
}
case *config.HttpsProxyConf:
pxy = &HttpsProxy{
BaseProxy: baseProxy,
cfg: cfg,
}
}
return
}
type BaseProxy struct {
ctl *Control
closed bool
mu sync.RWMutex
log.Logger
}
// TCP
type TcpProxy struct {
BaseProxy
cfg *config.TcpProxyConf
}
func (pxy *TcpProxy) Run() (err error) {
return
}
func (pxy *TcpProxy) Close() {
}
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) {
defer conn.Close()
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, &pxy.cfg.BaseProxyConf, conn)
}
// HTTP
type HttpProxy struct {
BaseProxy
cfg *config.HttpProxyConf
}
func (pxy *HttpProxy) Run() (err error) {
return
}
func (pxy *HttpProxy) Close() {
}
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) {
defer conn.Close()
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, &pxy.cfg.BaseProxyConf, conn)
}
// HTTPS
type HttpsProxy struct {
BaseProxy
cfg *config.HttpsProxyConf
}
func (pxy *HttpsProxy) Run() (err error) {
return
}
func (pxy *HttpsProxy) Close() {
}
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) {
defer conn.Close()
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, &pxy.cfg.BaseProxyConf, conn)
}
// UDP
type UdpProxy struct {
BaseProxy
cfg *config.UdpProxyConf
localAddr *net.UDPAddr
readCh chan *msg.UdpPacket
// include msg.UdpPacket and msg.Ping
sendCh chan msg.Message
workConn frpNet.Conn
}
func (pxy *UdpProxy) Run() (err error) {
pxy.localAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.cfg.LocalIp, pxy.cfg.LocalPort))
if err != nil {
return
}
return
}
func (pxy *UdpProxy) Close() {
pxy.mu.Lock()
defer pxy.mu.Unlock()
if !pxy.closed {
pxy.closed = true
if pxy.workConn != nil {
pxy.workConn.Close()
}
if pxy.readCh != nil {
close(pxy.readCh)
}
if pxy.sendCh != nil {
close(pxy.sendCh)
}
}
}
func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn) {
pxy.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
// close resources releated with old workConn
pxy.Close()
pxy.mu.Lock()
pxy.workConn = conn
pxy.readCh = make(chan *msg.UdpPacket, 1024)
pxy.sendCh = make(chan msg.Message, 1024)
pxy.closed = false
pxy.mu.Unlock()
workConnReaderFn := func(conn net.Conn, readCh chan *msg.UdpPacket) {
for {
var udpMsg msg.UdpPacket
if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
pxy.Warn("read from workConn for udp error: %v", errRet)
return
}
if errRet := errors.PanicToError(func() {
pxy.Trace("get udp package from workConn: %s", udpMsg.Content)
readCh <- &udpMsg
}); errRet != nil {
pxy.Info("reader goroutine for udp work connection closed: %v", errRet)
return
}
}
}
workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
defer func() {
pxy.Info("writer goroutine for udp work connection closed")
}()
var errRet error
for rawMsg := range sendCh {
switch m := rawMsg.(type) {
case *msg.UdpPacket:
pxy.Trace("send udp package to workConn: %s", m.Content)
case *msg.Ping:
pxy.Trace("send ping message to udp workConn")
}
if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
pxy.Error("udp work write error: %v", errRet)
return
}
}
}
heartbeatFn := func(conn net.Conn, sendCh chan msg.Message) {
var errRet error
for {
time.Sleep(time.Duration(30) * time.Second)
if errRet = errors.PanicToError(func() {
sendCh <- &msg.Ping{}
}); errRet != nil {
pxy.Trace("heartbeat goroutine for udp work connection closed")
break
}
}
}
go workConnSenderFn(pxy.workConn, pxy.sendCh)
go workConnReaderFn(pxy.workConn, pxy.readCh)
go heartbeatFn(pxy.workConn, pxy.sendCh)
udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh)
}
// Common handler for tcp work connections.
func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, baseInfo *config.BaseProxyConf, workConn frpNet.Conn) {
localConn, err := frpNet.ConnectTcpServer(fmt.Sprintf("%s:%d", localInfo.LocalIp, localInfo.LocalPort))
if err != nil {
workConn.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIp, localInfo.LocalPort, err)
return
}
var remote io.ReadWriteCloser
remote = workConn
if baseInfo.UseEncryption {
remote, err = tcp.WithEncryption(remote, []byte(config.ClientCommonCfg.PrivilegeToken))
if err != nil {
workConn.Error("create encryption stream error: %v", err)
return
}
}
if baseInfo.UseCompression {
remote = tcp.WithCompression(remote)
}
workConn.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
tcp.Join(localConn, remote)
workConn.Debug("join connections closed")
}

43
client/service.go Normal file
View File

@ -0,0 +1,43 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import "github.com/fatedier/frp/models/config"
type Service struct {
// manager control connection with server
ctl *Control
closedCh chan int
}
func NewService(pxyCfgs map[string]config.ProxyConf) (svr *Service) {
svr = &Service{
closedCh: make(chan int),
}
ctl := NewControl(svr, pxyCfgs)
svr.ctl = ctl
return
}
func (svr *Service) Run() error {
err := svr.ctl.Run()
if err != nil {
return err
}
<-svr.closedCh
return nil
}

View File

@ -19,13 +19,14 @@ import (
"os"
"strconv"
"strings"
"sync"
docopt "github.com/docopt/docopt-go"
ini "github.com/vaughan0/go-ini"
"github.com/fatedier/frp/src/models/client"
"github.com/fatedier/frp/src/utils/log"
"github.com/fatedier/frp/src/utils/version"
"github.com/fatedier/frp/client"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/version"
)
var (
@ -49,29 +50,38 @@ Options:
`
func main() {
var err error
confFile := "./frpc.ini"
// the configures parsed from file will be replaced by those from command line if exist
args, err := docopt.Parse(usage, nil, true, version.Full(), false)
if args["-c"] != nil {
configFile = args["-c"].(string)
confFile = args["-c"].(string)
}
err = client.LoadConf(configFile)
conf, err := ini.LoadFile(confFile)
if err != nil {
fmt.Println(err)
os.Exit(-1)
os.Exit(1)
}
config.ClientCommonCfg, err = config.LoadClientCommonConf(conf)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if args["-L"] != nil {
if args["-L"].(string) == "console" {
client.LogWay = "console"
config.ClientCommonCfg.LogWay = "console"
} else {
client.LogWay = "file"
client.LogFile = args["-L"].(string)
config.ClientCommonCfg.LogWay = "file"
config.ClientCommonCfg.LogFile = args["-L"].(string)
}
}
if args["--log-level"] != nil {
client.LogLevel = args["--log-level"].(string)
config.ClientCommonCfg.LogLevel = args["--log-level"].(string)
}
if args["--server-addr"] != nil {
@ -85,8 +95,8 @@ func main() {
fmt.Println("--server-addr format error, example 0.0.0.0:7000")
os.Exit(1)
}
client.ServerAddr = addr[0]
client.ServerPort = serverPort
config.ClientCommonCfg.ServerAddr = addr[0]
config.ClientCommonCfg.ServerPort = serverPort
}
if args["-v"] != nil {
@ -96,18 +106,19 @@ func main() {
}
}
log.InitLog(client.LogWay, client.LogFile, client.LogLevel, client.LogMaxDays)
// wait until all control goroutine exit
var wait sync.WaitGroup
wait.Add(len(client.ProxyClients))
for _, client := range client.ProxyClients {
go ControlProcess(client, &wait)
pxyCfgs, err := config.LoadProxyConfFromFile(conf)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
log.Info("Start frpc success")
log.InitLog(config.ClientCommonCfg.LogWay, config.ClientCommonCfg.LogFile,
config.ClientCommonCfg.LogLevel, config.ClientCommonCfg.LogMaxDays)
wait.Wait()
log.Warn("All proxy exit!")
svr := client.NewService(pxyCfgs)
err = svr.Run()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}

118
cmd/frps/main.go Normal file
View File

@ -0,0 +1,118 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"os"
"strconv"
"strings"
docopt "github.com/docopt/docopt-go"
ini "github.com/vaughan0/go-ini"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/server"
"github.com/fatedier/frp/utils/log"
"github.com/fatedier/frp/utils/version"
)
var usage string = `frps is the server of frp
Usage:
frps [-c config_file] [-L log_file] [--log-level=<log_level>] [--addr=<bind_addr>]
frps -h | --help
frps -v | --version
Options:
-c config_file set config file
-L log_file set output log file, including console
--log-level=<log_level> set log level: debug, info, warn, error
--addr=<bind_addr> listen addr for client, example: 0.0.0.0:7000
-h --help show this screen
-v --version show version
`
func main() {
var err error
confFile := "./frps.ini"
// the configures parsed from file will be replaced by those from command line if exist
args, err := docopt.Parse(usage, nil, true, version.Full(), false)
if args["-c"] != nil {
confFile = args["-c"].(string)
}
conf, err := ini.LoadFile(confFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
config.ServerCommonCfg, err = config.LoadServerCommonConf(conf)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if args["-L"] != nil {
if args["-L"].(string) == "console" {
config.ServerCommonCfg.LogWay = "console"
} else {
config.ServerCommonCfg.LogWay = "file"
config.ServerCommonCfg.LogFile = args["-L"].(string)
}
}
if args["--log-level"] != nil {
config.ServerCommonCfg.LogLevel = args["--log-level"].(string)
}
if args["--addr"] != nil {
addr := strings.Split(args["--addr"].(string), ":")
if len(addr) != 2 {
fmt.Println("--addr format error: example 0.0.0.0:7000")
os.Exit(1)
}
bindPort, err := strconv.ParseInt(addr[1], 10, 64)
if err != nil {
fmt.Println("--addr format error, example 0.0.0.0:7000")
os.Exit(1)
}
config.ServerCommonCfg.BindAddr = addr[0]
config.ServerCommonCfg.BindPort = bindPort
}
if args["-v"] != nil {
if args["-v"].(bool) {
fmt.Println(version.Full())
os.Exit(0)
}
}
log.InitLog(config.ServerCommonCfg.LogWay, config.ServerCommonCfg.LogFile,
config.ServerCommonCfg.LogLevel, config.ServerCommonCfg.LogMaxDays)
svr, err := server.NewService()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
log.Info("Start frps success")
if config.ServerCommonCfg.PrivilegeMode == true {
log.Info("PrivilegeMode is enabled, you should pay more attention to security issues")
}
server.ServerService = svr
svr.Run()
}

View File

@ -11,76 +11,73 @@ server_port = 7000
# console or real logFile path like ./frpc.log
log_file = ./frpc.log
# debug, info, warn, error
# trace, debug, info, warn, error
log_level = info
log_max_days = 3
# for authentication
auth_token = 123
# for privilege mode
privilege_token = 12345678
# connections will be established in advance, default value is zero
pool_count = 5
# if tcp stream multiplexing is used, default is true, it must be same with frps
tcp_mux = true
# your proxy name will be changed to {user}.{proxy}
user = your_name
# heartbeat configure, it's not recommended to modify the default value
# the default value of heartbeat_interval is 10 and heartbeat_timeout is 30
# heartbeat_interval = 10
# heartbeat_timeout = 30
# the default value of heartbeat_interval is 10 and heartbeat_timeout is 90
# heartbeat_interval = 30
# heartbeat_timeout = 90
# ssh is the proxy name same as server's configuration
# if user in [common] section is not empty, it will be changed to {user}.{proxy} such as your_name.ssh
[ssh]
# tcp | http, default is tcp
# tcp | udp | http | https, default is tcp
type = tcp
local_ip = 127.0.0.1
local_port = 22
# true or false, if true, messages between frps and frpc will be encrypted, default is false
use_encryption = false
# default is false
use_gzip = false
# if true, message will be compressed
use_compression = false
# remote port listen by frps
remote_port = 6001
[dns]
type = udp
local_ip = 114.114.114.114
local_port = 53
remote_port = 6002
use_encryption = false
use_compression = false
# Resolve your domain names to [server_addr] so you can use http://web01.yourdomain.com to browse web01 and http://web02.yourdomain.com to browse web02, the domains are set in frps.ini
# Resolve your domain names to [server_addr] so you can use http://web01.yourdomain.com to browse web01 and http://web02.yourdomain.com to browse web02
[web01]
type = http
local_ip = 127.0.0.1
local_port = 80
use_gzip = true
# connections will be established in advance, default value is zero
pool_count = 20
use_encryption = false
use_compression = true
# http username and password are safety certification for http protocol
# if not set, you can access this custom_domains without certification
http_user = admin
http_pwd = admin
# if domain for frps is frps.com, then you can access [web01] proxy by URL http://test.frps.com
subdomain = test
[web02]
type = http
local_ip = 127.0.0.1
local_port = 8000
[privilege_ssh]
# if privilege_mode is enabled, this proxy will be created automatically
privilege_mode = true
type = tcp
local_ip = 127.0.0.1
local_port = 22
use_encryption = true
use_gzip = false
remote_port = 6001
[privilege_web]
privilege_mode = true
type = http
local_ip = 127.0.0.1
local_port = 80
use_gzip = true
custom_domains = web03.yourdomain.com
subdomain = web01
custom_domains = web02.yourdomain.com
# locations is only useful for http type
locations = /,/pic
host_header_rewrite = example.com
subdomain = dev
[web02]
type = https
local_ip = 127.0.0.1
local_port = 8000
use_encryption = false
use_compression = false
subdomain = web01
custom_domains = web02.yourdomain.com

View File

@ -1,10 +1,10 @@
[common]
server_addr = 0.0.0.0
server_port = 7000
auth_token = 123
privilege_token = 12345678
#privilege_token = 12345678
[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000

View File

@ -21,24 +21,23 @@ dashboard_pwd = admin
# console or real logFile path like ./frps.log
log_file = ./frps.log
# debug, info, warn, error
# trace, debug, info, warn, error
log_level = info
log_max_days = 3
# if you enable privilege mode, frpc can create a proxy without pre-configure in frps when privilege_token is correct
privilege_mode = true
# privilege mode is the only supported mode since v0.10.0
privilege_token = 12345678
# heartbeat configure, it's not recommended to modify the default value
# the default value of heartbeat_timeout is 30
# heartbeat_timeout = 30
# the default value of heartbeat_timeout is 90
# heartbeat_timeout = 90
# only allow frpc to bind ports you list, if you set nothing, there won't be any limit
privilege_allow_ports = 2000-3000,3001,3003,4000-50000
# pool_count in each proxy will change to max_pool_count if they exceed the maximum value
max_pool_count = 100
max_pool_count = 5
# authentication_timeout means the timeout interval (seconds) when the frpc connects frps
# if authentication_timeout is zero, the time is not verified, default is 900s
@ -48,28 +47,5 @@ authentication_timeout = 900
# when subdomain is test, the host used by routing is test.frps.com
subdomain_host = frps.com
# ssh is the proxy name, client will use this name and auth_token to connect to server
[ssh]
type = tcp
auth_token = 123
bind_addr = 0.0.0.0
listen_port = 6000
[dns]
type = udp
auth_token = 123
bind_addr = 0.0.0.0
listen_port = 5353
[web01]
# if type equals http, vhost_http_port must be set
type = http
auth_token = 123
# if proxy type equals http, custom_domains must be set separated by commas
custom_domains = web01.yourdomain.com,web01.yourdomain2.com
[web02]
# if type equals https, vhost_https_port must be set
type = https
auth_token = 123
custom_domains = web02.yourdomain.com
# if tcp stream multiplexing is used, default is true
tcp_mux = true

View File

@ -4,11 +4,4 @@ bind_port = 7000
vhost_http_port = 80
vhost_https_port = 443
dashboard_port = 7500
privilege_mode = true
privilege_token = 12345678
[ssh]
type = tcp
auth_token = 123
bind_addr = 0.0.0.0
listen_port = 6000
#privilege_token = 12345678

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -0,0 +1,169 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"fmt"
"os"
"strconv"
ini "github.com/vaughan0/go-ini"
)
var ClientCommonCfg *ClientCommonConf
// client common config
type ClientCommonConf struct {
ConfigFile string
ServerAddr string
ServerPort int64
HttpProxy string
LogFile string
LogWay string
LogLevel string
LogMaxDays int64
PrivilegeToken string
PoolCount int
TcpMux bool
User string
HeartBeatInterval int64
HeartBeatTimeout int64
}
func GetDeaultClientCommonConf() *ClientCommonConf {
return &ClientCommonConf{
ConfigFile: "./frpc.ini",
ServerAddr: "0.0.0.0",
ServerPort: 7000,
HttpProxy: "",
LogFile: "console",
LogWay: "console",
LogLevel: "info",
LogMaxDays: 3,
PrivilegeToken: "",
PoolCount: 1,
TcpMux: true,
User: "",
HeartBeatInterval: 30,
HeartBeatTimeout: 90,
}
}
func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
var (
tmpStr string
ok bool
v int64
)
cfg = GetDeaultClientCommonConf()
tmpStr, ok = conf.Get("common", "server_addr")
if ok {
cfg.ServerAddr = tmpStr
}
tmpStr, ok = conf.Get("common", "server_port")
if ok {
cfg.ServerPort, _ = strconv.ParseInt(tmpStr, 10, 64)
}
tmpStr, ok = conf.Get("common", "http_proxy")
if ok {
cfg.HttpProxy = tmpStr
} else {
// get http_proxy from env
cfg.HttpProxy = os.Getenv("http_proxy")
}
tmpStr, ok = conf.Get("common", "log_file")
if ok {
cfg.LogFile = tmpStr
if cfg.LogFile == "console" {
cfg.LogWay = "console"
} else {
cfg.LogWay = "file"
}
}
tmpStr, ok = conf.Get("common", "log_level")
if ok {
cfg.LogLevel = tmpStr
}
tmpStr, ok = conf.Get("common", "log_max_days")
if ok {
cfg.LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64)
}
tmpStr, ok = conf.Get("common", "privilege_token")
if ok {
cfg.PrivilegeToken = tmpStr
}
tmpStr, ok = conf.Get("common", "pool_count")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
cfg.PoolCount = 1
} else {
cfg.PoolCount = int(v)
}
}
tmpStr, ok = conf.Get("common", "tcp_mux")
if ok && tmpStr == "false" {
cfg.TcpMux = false
} else {
cfg.TcpMux = true
}
tmpStr, ok = conf.Get("common", "user")
if ok {
cfg.User = tmpStr
}
tmpStr, ok = conf.Get("common", "heartbeat_timeout")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
return
} else {
cfg.HeartBeatTimeout = v
}
}
tmpStr, ok = conf.Get("common", "heartbeat_interval")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: heartbeat_interval is incorrect")
return
} else {
cfg.HeartBeatInterval = v
}
}
if cfg.HeartBeatInterval <= 0 {
err = fmt.Errorf("Parse conf error: heartbeat_interval is incorrect")
return
}
if cfg.HeartBeatTimeout < cfg.HeartBeatInterval {
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect, heartbeat_timeout is less than heartbeat_interval")
return
}
return
}

457
models/config/proxy.go Normal file
View File

@ -0,0 +1,457 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"fmt"
"reflect"
"strconv"
"strings"
"github.com/fatedier/frp/models/consts"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/util"
ini "github.com/vaughan0/go-ini"
)
var proxyConfTypeMap map[string]reflect.Type
func init() {
proxyConfTypeMap = make(map[string]reflect.Type)
proxyConfTypeMap[consts.TcpProxy] = reflect.TypeOf(TcpProxyConf{})
proxyConfTypeMap[consts.UdpProxy] = reflect.TypeOf(UdpProxyConf{})
proxyConfTypeMap[consts.HttpProxy] = reflect.TypeOf(HttpProxyConf{})
proxyConfTypeMap[consts.HttpsProxy] = reflect.TypeOf(HttpsProxyConf{})
}
// NewConfByType creates a empty ProxyConf object by proxyType.
// If proxyType isn't exist, return nil.
func NewConfByType(proxyType string) ProxyConf {
v, ok := proxyConfTypeMap[proxyType]
if !ok {
return nil
}
cfg := reflect.New(v).Interface().(ProxyConf)
return cfg
}
type ProxyConf interface {
GetName() string
GetBaseInfo() *BaseProxyConf
LoadFromMsg(pMsg *msg.NewProxy)
LoadFromFile(name string, conf ini.Section) error
UnMarshalToMsg(pMsg *msg.NewProxy)
Check() error
}
func NewProxyConf(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
if pMsg.ProxyType == "" {
pMsg.ProxyType = consts.TcpProxy
}
cfg = NewConfByType(pMsg.ProxyType)
if cfg == nil {
err = fmt.Errorf("proxy [%s] type [%s] error", pMsg.ProxyName, pMsg.ProxyType)
return
}
cfg.LoadFromMsg(pMsg)
err = cfg.Check()
return
}
func NewProxyConfFromFile(name string, section ini.Section) (cfg ProxyConf, err error) {
proxyType := section["type"]
if proxyType == "" {
proxyType = consts.TcpProxy
section["type"] = consts.TcpProxy
}
cfg = NewConfByType(proxyType)
if cfg == nil {
err = fmt.Errorf("proxy [%s] type [%s] error", name, proxyType)
return
}
err = cfg.LoadFromFile(name, section)
return
}
// BaseProxy info
type BaseProxyConf struct {
ProxyName string `json:"proxy_name"`
ProxyType string `json:"proxy_type"`
UseEncryption bool `json:"use_encryption"`
UseCompression bool `json:"use_compression"`
}
func (cfg *BaseProxyConf) GetName() string {
return cfg.ProxyName
}
func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf {
return cfg
}
func (cfg *BaseProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.ProxyName = pMsg.ProxyName
cfg.ProxyType = pMsg.ProxyType
cfg.UseEncryption = pMsg.UseEncryption
cfg.UseCompression = pMsg.UseCompression
}
func (cfg *BaseProxyConf) LoadFromFile(name string, section ini.Section) error {
var (
tmpStr string
ok bool
)
if ClientCommonCfg.User != "" {
cfg.ProxyName = ClientCommonCfg.User + "." + name
} else {
cfg.ProxyName = name
}
cfg.ProxyType = section["type"]
tmpStr, ok = section["use_encryption"]
if ok && tmpStr == "true" {
cfg.UseEncryption = true
}
tmpStr, ok = section["use_compression"]
if ok && tmpStr == "true" {
cfg.UseCompression = true
}
return nil
}
func (cfg *BaseProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
pMsg.ProxyName = cfg.ProxyName
pMsg.ProxyType = cfg.ProxyType
pMsg.UseEncryption = cfg.UseEncryption
pMsg.UseCompression = cfg.UseCompression
}
// Bind info
type BindInfoConf struct {
BindAddr string `json:"bind_addr"`
RemotePort int64 `json:"remote_port"`
}
func (cfg *BindInfoConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BindAddr = ServerCommonCfg.BindAddr
cfg.RemotePort = pMsg.RemotePort
}
func (cfg *BindInfoConf) LoadFromFile(name string, section ini.Section) (err error) {
var (
tmpStr string
ok bool
)
if tmpStr, ok = section["remote_port"]; ok {
if cfg.RemotePort, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
return fmt.Errorf("Parse conf error: proxy [%s] remote_port error", name)
}
} else {
return fmt.Errorf("Parse conf error: proxy [%s] remote_port not found", name)
}
return nil
}
func (cfg *BindInfoConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
pMsg.RemotePort = cfg.RemotePort
}
func (cfg *BindInfoConf) check() (err error) {
if len(ServerCommonCfg.PrivilegeAllowPorts) != 0 {
if ok := util.ContainsPort(ServerCommonCfg.PrivilegeAllowPorts, cfg.RemotePort); !ok {
return fmt.Errorf("remote port [%d] isn't allowed", cfg.RemotePort)
}
}
return nil
}
// Domain info
type DomainConf struct {
CustomDomains []string `json:"custom_domains"`
SubDomain string `json:"sub_domain"`
}
func (cfg *DomainConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.CustomDomains = pMsg.CustomDomains
cfg.SubDomain = pMsg.SubDomain
}
func (cfg *DomainConf) LoadFromFile(name string, section ini.Section) (err error) {
var (
tmpStr string
ok bool
)
if tmpStr, ok = section["custom_domains"]; ok {
cfg.CustomDomains = strings.Split(tmpStr, ",")
for i, domain := range cfg.CustomDomains {
cfg.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain))
}
}
if tmpStr, ok = section["subdomain"]; ok {
cfg.SubDomain = tmpStr
}
if len(cfg.CustomDomains) == 0 && cfg.SubDomain == "" {
return fmt.Errorf("Parse conf error: proxy [%s] custom_domains and subdomain should set at least one of them", name)
}
return
}
func (cfg *DomainConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
pMsg.CustomDomains = cfg.CustomDomains
pMsg.SubDomain = cfg.SubDomain
}
func (cfg *DomainConf) check() (err error) {
for _, domain := range cfg.CustomDomains {
if ServerCommonCfg.SubDomainHost != "" && len(strings.Split(ServerCommonCfg.SubDomainHost, ".")) < len(strings.Split(domain, ".")) {
if strings.Contains(domain, ServerCommonCfg.SubDomainHost) {
return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, ServerCommonCfg.SubDomainHost)
}
}
}
if cfg.SubDomain != "" {
if ServerCommonCfg.SubDomainHost == "" {
return fmt.Errorf("subdomain is not supported because this feature is not enabled by frps")
}
if strings.Contains(cfg.SubDomain, ".") || strings.Contains(cfg.SubDomain, "*") {
return fmt.Errorf("'.' and '*' is not supported in subdomain")
}
}
return nil
}
type LocalSvrConf struct {
LocalIp string `json:"-"`
LocalPort int `json:"-"`
}
func (cfg *LocalSvrConf) LoadFromFile(name string, section ini.Section) (err error) {
if cfg.LocalIp = section["local_ip"]; cfg.LocalIp == "" {
cfg.LocalIp = "127.0.0.1"
}
if tmpStr, ok := section["local_port"]; ok {
if cfg.LocalPort, err = strconv.Atoi(tmpStr); err != nil {
return fmt.Errorf("Parse conf error: proxy [%s] local_port error", name)
}
} else {
return fmt.Errorf("Parse conf error: proxy [%s] local_port not found", name)
}
return nil
}
// TCP
type TcpProxyConf struct {
BaseProxyConf
BindInfoConf
LocalSvrConf
}
func (cfg *TcpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg)
cfg.BindInfoConf.LoadFromMsg(pMsg)
}
func (cfg *TcpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.BindInfoConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
return
}
return
}
func (cfg *TcpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
cfg.BindInfoConf.UnMarshalToMsg(pMsg)
}
func (cfg *TcpProxyConf) Check() (err error) {
err = cfg.BindInfoConf.check()
return
}
// UDP
type UdpProxyConf struct {
BaseProxyConf
BindInfoConf
LocalSvrConf
}
func (cfg *UdpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg)
cfg.BindInfoConf.LoadFromMsg(pMsg)
}
func (cfg *UdpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.BindInfoConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
return
}
return
}
func (cfg *UdpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
cfg.BindInfoConf.UnMarshalToMsg(pMsg)
}
func (cfg *UdpProxyConf) Check() (err error) {
err = cfg.BindInfoConf.check()
return
}
// HTTP
type HttpProxyConf struct {
BaseProxyConf
DomainConf
LocalSvrConf
Locations []string `json:"locations"`
HostHeaderRewrite string `json:"host_header_rewrite"`
HttpUser string `json:"-"`
HttpPwd string `json:"-"`
}
func (cfg *HttpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg)
cfg.DomainConf.LoadFromMsg(pMsg)
cfg.Locations = pMsg.Locations
cfg.HostHeaderRewrite = pMsg.HostHeaderRewrite
cfg.HttpUser = pMsg.HttpUser
cfg.HttpPwd = pMsg.HttpPwd
}
func (cfg *HttpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.DomainConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
return
}
var (
tmpStr string
ok bool
)
if tmpStr, ok = section["locations"]; ok {
cfg.Locations = strings.Split(tmpStr, ",")
} else {
cfg.Locations = []string{""}
}
cfg.HostHeaderRewrite = section["host_header_rewrite"]
cfg.HttpUser = section["http_user"]
cfg.HttpPwd = section["http_pwd"]
return
}
func (cfg *HttpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
cfg.DomainConf.UnMarshalToMsg(pMsg)
pMsg.Locations = cfg.Locations
pMsg.HostHeaderRewrite = cfg.HostHeaderRewrite
pMsg.HttpUser = cfg.HttpUser
pMsg.HttpPwd = cfg.HttpPwd
}
func (cfg *HttpProxyConf) Check() (err error) {
if ServerCommonCfg.VhostHttpPort == 0 {
return fmt.Errorf("type [http] not support when vhost_http_port is not set")
}
err = cfg.DomainConf.check()
return
}
// HTTPS
type HttpsProxyConf struct {
BaseProxyConf
DomainConf
LocalSvrConf
}
func (cfg *HttpsProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.LoadFromMsg(pMsg)
cfg.DomainConf.LoadFromMsg(pMsg)
}
func (cfg *HttpsProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.DomainConf.LoadFromFile(name, section); err != nil {
return
}
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
return
}
return
}
func (cfg *HttpsProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
cfg.DomainConf.UnMarshalToMsg(pMsg)
}
func (cfg *HttpsProxyConf) Check() (err error) {
if ServerCommonCfg.VhostHttpsPort == 0 {
return fmt.Errorf("type [https] not support when vhost_https_port is not set")
}
err = cfg.DomainConf.check()
return
}
func LoadProxyConfFromFile(conf ini.File) (proxyConfs map[string]ProxyConf, err error) {
var prefix string
if ClientCommonCfg.User != "" {
prefix = ClientCommonCfg.User + "."
}
proxyConfs = make(map[string]ProxyConf)
for name, section := range conf {
if name != "common" {
cfg, err := NewProxyConfFromFile(name, section)
if err != nil {
return proxyConfs, err
}
proxyConfs[prefix+name] = cfg
}
}
return
}

View File

@ -0,0 +1,245 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"fmt"
"strconv"
"strings"
"github.com/fatedier/frp/utils/util"
ini "github.com/vaughan0/go-ini"
)
var ServerCommonCfg *ServerCommonConf
// common config
type ServerCommonConf struct {
ConfigFile string
BindAddr string
BindPort int64
// If VhostHttpPort equals 0, don't listen a public port for http protocol.
VhostHttpPort int64
// if VhostHttpsPort equals 0, don't listen a public port for https protocol
VhostHttpsPort int64
// if DashboardPort equals 0, dashboard is not available
DashboardPort int64
DashboardUser string
DashboardPwd string
AssetsDir string
LogFile string
LogWay string // console or file
LogLevel string
LogMaxDays int64
PrivilegeMode bool
PrivilegeToken string
AuthTimeout int64
SubDomainHost string
TcpMux bool
// if PrivilegeAllowPorts is not nil, tcp proxies which remote port exist in this map can be connected
PrivilegeAllowPorts [][2]int64
MaxPoolCount int64
HeartBeatTimeout int64
UserConnTimeout int64
}
func GetDefaultServerCommonConf() *ServerCommonConf {
return &ServerCommonConf{
ConfigFile: "./frps.ini",
BindAddr: "0.0.0.0",
BindPort: 7000,
VhostHttpPort: 0,
VhostHttpsPort: 0,
DashboardPort: 0,
DashboardUser: "admin",
DashboardPwd: "admin",
AssetsDir: "",
LogFile: "console",
LogWay: "console",
LogLevel: "info",
LogMaxDays: 3,
PrivilegeMode: true,
PrivilegeToken: "",
AuthTimeout: 900,
SubDomainHost: "",
TcpMux: true,
MaxPoolCount: 5,
HeartBeatTimeout: 90,
UserConnTimeout: 10,
}
}
// Load server common configure.
func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
var (
tmpStr string
ok bool
v int64
)
cfg = GetDefaultServerCommonConf()
tmpStr, ok = conf.Get("common", "bind_addr")
if ok {
cfg.BindAddr = tmpStr
}
tmpStr, ok = conf.Get("common", "bind_port")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err == nil {
cfg.BindPort = v
}
}
tmpStr, ok = conf.Get("common", "vhost_http_port")
if ok {
cfg.VhostHttpPort, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: vhost_http_port is incorrect")
return
}
} else {
cfg.VhostHttpPort = 0
}
tmpStr, ok = conf.Get("common", "vhost_https_port")
if ok {
cfg.VhostHttpsPort, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: vhost_https_port is incorrect")
return
}
} else {
cfg.VhostHttpsPort = 0
}
tmpStr, ok = conf.Get("common", "dashboard_port")
if ok {
cfg.DashboardPort, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
err = fmt.Errorf("Parse conf error: dashboard_port is incorrect")
return
}
} else {
cfg.DashboardPort = 0
}
tmpStr, ok = conf.Get("common", "dashboard_user")
if ok {
cfg.DashboardUser = tmpStr
}
tmpStr, ok = conf.Get("common", "dashboard_pwd")
if ok {
cfg.DashboardPwd = tmpStr
}
tmpStr, ok = conf.Get("common", "assets_dir")
if ok {
cfg.AssetsDir = tmpStr
}
tmpStr, ok = conf.Get("common", "log_file")
if ok {
cfg.LogFile = tmpStr
if cfg.LogFile == "console" {
cfg.LogWay = "console"
} else {
cfg.LogWay = "file"
}
}
tmpStr, ok = conf.Get("common", "log_level")
if ok {
cfg.LogLevel = tmpStr
}
tmpStr, ok = conf.Get("common", "log_max_days")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err == nil {
cfg.LogMaxDays = v
}
}
tmpStr, ok = conf.Get("common", "privilege_mode")
if ok {
if tmpStr == "true" {
cfg.PrivilegeMode = true
}
}
// PrivilegeMode configure
if cfg.PrivilegeMode == true {
cfg.PrivilegeToken, _ = conf.Get("common", "privilege_token")
allowPortsStr, ok := conf.Get("common", "privilege_allow_ports")
// TODO: check if conflicts exist in port ranges
if ok {
cfg.PrivilegeAllowPorts, err = util.GetPortRanges(allowPortsStr)
if err != nil {
err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", err)
return
}
}
}
tmpStr, ok = conf.Get("common", "max_pool_count")
if ok {
v, err = strconv.ParseInt(tmpStr, 10, 64)
if err == nil && v >= 0 {
cfg.MaxPoolCount = v
}
}
tmpStr, ok = conf.Get("common", "authentication_timeout")
if ok {
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
if errRet != nil {
err = fmt.Errorf("Parse conf error: authentication_timeout is incorrect")
return
} else {
cfg.AuthTimeout = v
}
}
tmpStr, ok = conf.Get("common", "subdomain_host")
if ok {
cfg.SubDomainHost = strings.ToLower(strings.TrimSpace(tmpStr))
}
tmpStr, ok = conf.Get("common", "tcp_mux")
if ok && tmpStr == "false" {
cfg.TcpMux = false
} else {
cfg.TcpMux = true
}
tmpStr, ok = conf.Get("common", "heartbeat_timeout")
if ok {
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
if errRet != nil {
err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
return
} else {
cfg.HeartBeatTimeout = v
}
}
return
}

View File

@ -12,19 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package config
package consts
type BaseConf struct {
Name string
AuthToken string
Type string
UseEncryption bool
UseGzip bool
PrivilegeMode bool
PrivilegeToken string
PoolCount int64
HostHeaderRewrite string
HttpUserName string
HttpPassWord string
SubDomain string
}
var (
// proxy status
Idle string = "idle"
Working string = "working"
Closed string = "closed"
Online string = "online"
Offline string = "offline"
// proxy type
TcpProxy string = "tcp"
UdpProxy string = "udp"
HttpProxy string = "http"
HttpsProxy string = "https"
)

View File

@ -12,30 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package consts
package errors
// server status
const (
Idle = iota
Working
Closed
)
import "errors"
var (
StatusStr = []string{
"idle",
"working",
"closed",
}
)
// msg type
const (
NewCtlConn = iota
NewWorkConn
NoticeUserConn
NewCtlConnRes
HeartbeatReq
HeartbeatRes
NewWorkConnUdp
ErrMsgType = errors.New("message type error")
)

129
models/msg/msg.go Normal file
View File

@ -0,0 +1,129 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
import (
"net"
"reflect"
)
const (
TypeLogin = 'o'
TypeLoginResp = '1'
TypeNewProxy = 'p'
TypeNewProxyResp = '2'
TypeNewWorkConn = 'w'
TypeReqWorkConn = 'r'
TypeStartWorkConn = 's'
TypePing = 'h'
TypePong = '4'
TypeUdpPacket = 'u'
)
var (
TypeMap map[byte]reflect.Type
TypeStringMap map[reflect.Type]byte
)
func init() {
TypeMap = make(map[byte]reflect.Type)
TypeStringMap = make(map[reflect.Type]byte)
TypeMap[TypeLogin] = reflect.TypeOf(Login{})
TypeMap[TypeLoginResp] = reflect.TypeOf(LoginResp{})
TypeMap[TypeNewProxy] = reflect.TypeOf(NewProxy{})
TypeMap[TypeNewProxyResp] = reflect.TypeOf(NewProxyResp{})
TypeMap[TypeNewWorkConn] = reflect.TypeOf(NewWorkConn{})
TypeMap[TypeReqWorkConn] = reflect.TypeOf(ReqWorkConn{})
TypeMap[TypeStartWorkConn] = reflect.TypeOf(StartWorkConn{})
TypeMap[TypePing] = reflect.TypeOf(Ping{})
TypeMap[TypePong] = reflect.TypeOf(Pong{})
TypeMap[TypeUdpPacket] = reflect.TypeOf(UdpPacket{})
for k, v := range TypeMap {
TypeStringMap[v] = k
}
}
// Message wraps socket packages for communicating between frpc and frps.
type Message interface{}
// When frpc start, client send this message to login to server.
type Login struct {
Version string `json:"version"`
Hostname string `json:"hostname"`
Os string `json:"os"`
Arch string `json:"arch"`
User string `json:"user"`
PrivilegeKey string `json:"privilege_key"`
Timestamp int64 `json:"timestamp"`
RunId string `json:"run_id"`
// Some global configures.
PoolCount int `json:"pool_count"`
}
type LoginResp struct {
Version string `json:"version"`
RunId string `json:"run_id"`
Error string `json:"error"`
}
// When frpc login success, send this message to frps for running a new proxy.
type NewProxy struct {
ProxyName string `json:"proxy_name"`
ProxyType string `json:"proxy_type"`
UseEncryption bool `json:"use_encryption"`
UseCompression bool `json:"use_compression"`
// tcp and udp only
RemotePort int64 `json:"remote_port"`
// http and https only
CustomDomains []string `json:"custom_domains"`
SubDomain string `json:"subdomain"`
Locations []string `json:"locations"`
HostHeaderRewrite string `json:"host_header_rewrite"`
HttpUser string `json:"http_user"`
HttpPwd string `json:"http_pwd"`
}
type NewProxyResp struct {
ProxyName string `json:"proxy_name"`
Error string `json:"error"`
}
type NewWorkConn struct {
RunId string `json:"run_id"`
}
type ReqWorkConn struct {
}
type StartWorkConn struct {
ProxyName string `json:"proxy_name"`
}
type Ping struct {
}
type Pong struct {
}
type UdpPacket struct {
Content string `json:"c"`
LocalAddr *net.UDPAddr `json:"l"`
RemoteAddr *net.UDPAddr `json:"r"`
}

69
models/msg/pack.go Normal file
View File

@ -0,0 +1,69 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"reflect"
"github.com/fatedier/frp/utils/errors"
)
func unpack(typeByte byte, buffer []byte, msgIn Message) (msg Message, err error) {
if msgIn == nil {
t, ok := TypeMap[typeByte]
if !ok {
err = fmt.Errorf("Unsupported message type %b", typeByte)
return
}
msg = reflect.New(t).Interface().(Message)
} else {
msg = msgIn
}
err = json.Unmarshal(buffer, &msg)
return
}
func UnPackInto(buffer []byte, msg Message) (err error) {
_, err = unpack(' ', buffer, msg)
return
}
func UnPack(typeByte byte, buffer []byte) (msg Message, err error) {
return unpack(typeByte, buffer, nil)
}
func Pack(msg Message) ([]byte, error) {
typeByte, ok := TypeStringMap[reflect.TypeOf(msg).Elem()]
if !ok {
return nil, errors.ErrMsgType
}
content, err := json.Marshal(msg)
if err != nil {
return nil, err
}
buffer := bytes.NewBuffer(nil)
buffer.WriteByte(typeByte)
binary.Write(buffer, binary.BigEndian, int64(len(content)))
buffer.Write(content)
return buffer.Bytes(), nil
}

87
models/msg/pack_test.go Normal file
View File

@ -0,0 +1,87 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
import (
"bytes"
"encoding/binary"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"github.com/fatedier/frp/utils/errors"
)
type TestStruct struct{}
func TestPack(t *testing.T) {
assert := assert.New(t)
var (
msg Message
buffer []byte
err error
)
// error type
msg = &TestStruct{}
buffer, err = Pack(msg)
assert.Error(err, errors.ErrMsgType.Error())
// correct
msg = &Ping{}
buffer, err = Pack(msg)
assert.NoError(err)
b := bytes.NewBuffer(nil)
b.WriteByte(TypePing)
binary.Write(b, binary.BigEndian, int64(2))
b.WriteString("{}")
assert.True(bytes.Equal(b.Bytes(), buffer))
}
func TestUnPack(t *testing.T) {
assert := assert.New(t)
var (
msg Message
err error
)
// error message type
msg, err = UnPack('-', []byte("{}"))
assert.Error(err)
// correct
msg, err = UnPack(TypePong, []byte("{}"))
assert.NoError(err)
assert.Equal(reflect.TypeOf(msg).Elem(), reflect.TypeOf(Pong{}))
}
func TestUnPackInto(t *testing.T) {
assert := assert.New(t)
var err error
// correct type
pongMsg := &Pong{}
err = UnPackInto([]byte("{}"), pongMsg)
assert.NoError(err)
// wrong type
loginMsg := &Login{}
err = UnPackInto([]byte(`{"version": 123}`), loginMsg)
assert.Error(err)
}

88
models/msg/process.go Normal file
View File

@ -0,0 +1,88 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
import (
"encoding/binary"
"fmt"
"io"
)
var (
MaxMsgLength int64 = 10240
)
func readMsg(c io.Reader) (typeByte byte, buffer []byte, err error) {
buffer = make([]byte, 1)
_, err = c.Read(buffer)
if err != nil {
return
}
typeByte = buffer[0]
if _, ok := TypeMap[typeByte]; !ok {
err = fmt.Errorf("Message type error")
return
}
var length int64
err = binary.Read(c, binary.BigEndian, &length)
if err != nil {
return
}
if length > MaxMsgLength {
err = fmt.Errorf("Message length exceed the limit")
return
}
buffer = make([]byte, length)
n, err := io.ReadFull(c, buffer)
if err != nil {
return
}
if int64(n) != length {
err = fmt.Errorf("Message format error")
}
return
}
func ReadMsg(c io.Reader) (msg Message, err error) {
typeByte, buffer, err := readMsg(c)
if err != nil {
return
}
return UnPack(typeByte, buffer)
}
func ReadMsgInto(c io.Reader, msg Message) (err error) {
_, buffer, err := readMsg(c)
if err != nil {
return
}
return UnPackInto(buffer, msg)
}
func WriteMsg(c io.Writer, msg interface{}) (err error) {
buffer, err := Pack(msg)
if err != nil {
return
}
if _, err = c.Write(buffer); err != nil {
return
}
return nil
}

View File

@ -0,0 +1,97 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
import (
"bytes"
"encoding/binary"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
)
func TestProcess(t *testing.T) {
assert := assert.New(t)
var (
msg Message
resMsg Message
err error
)
// empty struct
msg = &Ping{}
buffer := bytes.NewBuffer(nil)
err = WriteMsg(buffer, msg)
assert.NoError(err)
resMsg, err = ReadMsg(buffer)
assert.NoError(err)
assert.Equal(reflect.TypeOf(resMsg).Elem(), TypeMap[TypePing])
// normal message
msg = &StartWorkConn{
ProxyName: "test",
}
buffer = bytes.NewBuffer(nil)
err = WriteMsg(buffer, msg)
assert.NoError(err)
resMsg, err = ReadMsg(buffer)
assert.NoError(err)
assert.Equal(reflect.TypeOf(resMsg).Elem(), TypeMap[TypeStartWorkConn])
startWorkConnMsg, ok := resMsg.(*StartWorkConn)
assert.True(ok)
assert.Equal("test", startWorkConnMsg.ProxyName)
// ReadMsgInto correct
msg = &Pong{}
buffer = bytes.NewBuffer(nil)
err = WriteMsg(buffer, msg)
assert.NoError(err)
err = ReadMsgInto(buffer, msg)
assert.NoError(err)
// ReadMsgInto error type
content := []byte(`{"run_id": 123}`)
buffer = bytes.NewBuffer(nil)
buffer.WriteByte(TypeNewWorkConn)
binary.Write(buffer, binary.BigEndian, int64(len(content)))
buffer.Write(content)
resMsg = &NewWorkConn{}
err = ReadMsgInto(buffer, resMsg)
assert.Error(err)
// message format error
buffer = bytes.NewBuffer([]byte("1234"))
resMsg = &NewProxyResp{}
err = ReadMsgInto(buffer, resMsg)
assert.Error(err)
// MaxLength, real message length is 2
MaxMsgLength = 1
msg = &Ping{}
buffer = bytes.NewBuffer(nil)
err = WriteMsg(buffer, msg)
assert.NoError(err)
_, err = ReadMsg(buffer)
assert.Error(err)
return
}

View File

@ -0,0 +1,38 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tcp
import (
"io"
"sync"
)
// Join two io.ReadWriteCloser and do some operations.
func Join(c1 io.ReadWriteCloser, c2 io.ReadWriteCloser) (inCount int64, outCount int64) {
var wait sync.WaitGroup
pipe := func(to io.ReadWriteCloser, from io.ReadWriteCloser, count *int64) {
defer to.Close()
defer from.Close()
defer wait.Done()
*count, _ = io.Copy(to, from)
}
wait.Add(2)
go pipe(c1, c2, &inCount)
go pipe(c2, c1, &outCount)
wait.Wait()
return
}

View File

@ -0,0 +1,67 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tcp
import (
"io"
"testing"
"github.com/stretchr/testify/assert"
)
func TestJoin(t *testing.T) {
assert := assert.New(t)
var (
n int
err error
)
text1 := "A document that gives tips for writing clear, idiomatic Go code. A must read for any new Go programmer. It augments the tour and the language specification, both of which should be read first."
text2 := "A document that specifies the conditions under which reads of a variable in one goroutine can be guaranteed to observe values produced by writes to the same variable in a different goroutine."
// Forward bytes directly.
pr, pw := io.Pipe()
pr2, pw2 := io.Pipe()
pr3, pw3 := io.Pipe()
pr4, pw4 := io.Pipe()
conn1 := WrapReadWriteCloser(pr, pw2)
conn2 := WrapReadWriteCloser(pr2, pw)
conn3 := WrapReadWriteCloser(pr3, pw4)
conn4 := WrapReadWriteCloser(pr4, pw3)
go func() {
Join(conn2, conn3)
}()
buf1 := make([]byte, 1024)
buf2 := make([]byte, 1024)
conn1.Write([]byte(text1))
conn4.Write([]byte(text2))
n, err = conn4.Read(buf1)
assert.NoError(err)
assert.Equal(text1, string(buf1[:n]))
n, err = conn1.Read(buf2)
assert.NoError(err)
assert.Equal(text2, string(buf2[:n]))
conn1.Close()
conn2.Close()
conn3.Close()
conn4.Close()
}

73
models/proto/tcp/tcp.go Normal file
View File

@ -0,0 +1,73 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tcp
import (
"io"
"github.com/golang/snappy"
"github.com/fatedier/frp/utils/crypto"
)
func WithEncryption(rwc io.ReadWriteCloser, key []byte) (io.ReadWriteCloser, error) {
w, err := crypto.NewWriter(rwc, key)
if err != nil {
return nil, err
}
return WrapReadWriteCloser(crypto.NewReader(rwc, key), w), nil
}
func WithCompression(rwc io.ReadWriteCloser) io.ReadWriteCloser {
return WrapReadWriteCloser(snappy.NewReader(rwc), snappy.NewWriter(rwc))
}
func WrapReadWriteCloser(r io.Reader, w io.Writer) io.ReadWriteCloser {
return &ReadWriteCloser{
r: r,
w: w,
}
}
type ReadWriteCloser struct {
r io.Reader
w io.Writer
}
func (rwc *ReadWriteCloser) Read(p []byte) (n int, err error) {
return rwc.r.Read(p)
}
func (rwc *ReadWriteCloser) Write(p []byte) (n int, err error) {
return rwc.w.Write(p)
}
func (rwc *ReadWriteCloser) Close() (errRet error) {
var err error
if rc, ok := rwc.r.(io.Closer); ok {
err = rc.Close()
if err != nil {
errRet = err
}
}
if wc, ok := rwc.w.(io.Closer); ok {
err = wc.Close()
if err != nil {
errRet = err
}
}
return
}

View File

@ -0,0 +1,100 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tcp
import (
"io"
"testing"
"github.com/stretchr/testify/assert"
)
func TestWithCompression(t *testing.T) {
assert := assert.New(t)
// Forward compression bytes.
pr, pw := io.Pipe()
pr2, pw2 := io.Pipe()
conn1 := WrapReadWriteCloser(pr, pw2)
conn2 := WrapReadWriteCloser(pr2, pw)
compressionStream1 := WithCompression(conn1)
compressionStream2 := WithCompression(conn2)
var (
n int
err error
)
text := "1234567812345678"
buf := make([]byte, 256)
go compressionStream1.Write([]byte(text))
n, err = compressionStream2.Read(buf)
assert.NoError(err)
assert.Equal(text, string(buf[:n]))
go compressionStream2.Write([]byte(text))
n, err = compressionStream1.Read(buf)
assert.NoError(err)
assert.Equal(text, string(buf[:n]))
}
func TestWithEncryption(t *testing.T) {
assert := assert.New(t)
var (
n int
err error
)
text1 := "Go is expressive, concise, clean, and efficient. Its concurrency mechanisms make it easy to write programs that get the most out of multicore and networked machines, while its novel type system enables flexible and modular program construction. Go compiles quickly to machine code yet has the convenience of garbage collection and the power of run-time reflection. It's a fast, statically typed, compiled language that feels like a dynamically typed, interpreted language."
text2 := "An interactive introduction to Go in three sections. The first section covers basic syntax and data structures; the second discusses methods and interfaces; and the third introduces Go's concurrency primitives. Each section concludes with a few exercises so you can practice what you've learned. You can take the tour online or install it locally with"
key := "authkey"
// Forward enrypted bytes.
pr, pw := io.Pipe()
pr2, pw2 := io.Pipe()
pr3, pw3 := io.Pipe()
pr4, pw4 := io.Pipe()
pr5, pw5 := io.Pipe()
pr6, pw6 := io.Pipe()
conn1 := WrapReadWriteCloser(pr, pw2)
conn2 := WrapReadWriteCloser(pr2, pw)
conn3 := WrapReadWriteCloser(pr3, pw4)
conn4 := WrapReadWriteCloser(pr4, pw3)
conn5 := WrapReadWriteCloser(pr5, pw6)
conn6 := WrapReadWriteCloser(pr6, pw5)
encryptStream1, err := WithEncryption(conn3, []byte(key))
assert.NoError(err)
encryptStream2, err := WithEncryption(conn4, []byte(key))
assert.NoError(err)
go Join(conn2, encryptStream1)
go Join(encryptStream2, conn5)
buf := make([]byte, 1024)
conn1.Write([]byte(text1))
conn6.Write([]byte(text2))
n, err = conn6.Read(buf)
assert.NoError(err)
assert.Equal(text1, string(buf[:n]))
n, err = conn1.Read(buf)
assert.NoError(err)
}

135
models/proto/udp/udp.go Normal file
View File

@ -0,0 +1,135 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package udp
import (
"encoding/base64"
"net"
"sync"
"time"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/errors"
"github.com/fatedier/frp/utils/pool"
)
func NewUdpPacket(buf []byte, laddr, raddr *net.UDPAddr) *msg.UdpPacket {
return &msg.UdpPacket{
Content: base64.StdEncoding.EncodeToString(buf),
LocalAddr: laddr,
RemoteAddr: raddr,
}
}
func GetContent(m *msg.UdpPacket) (buf []byte, err error) {
buf, err = base64.StdEncoding.DecodeString(m.Content)
return
}
func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UdpPacket, sendCh chan<- *msg.UdpPacket) {
// read
go func() {
for udpMsg := range readCh {
buf, err := GetContent(udpMsg)
if err != nil {
continue
}
udpConn.WriteToUDP(buf, udpMsg.RemoteAddr)
}
}()
// write
buf := pool.GetBuf(1500)
defer pool.PutBuf(buf)
for {
n, remoteAddr, err := udpConn.ReadFromUDP(buf)
if err != nil {
udpConn.Close()
return
}
// buf[:n] will be encoded to string, so the bytes can be reused
udpMsg := NewUdpPacket(buf[:n], nil, remoteAddr)
select {
case sendCh <- udpMsg:
default:
}
}
return
}
func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<- msg.Message) {
var (
mu sync.RWMutex
)
udpConnMap := make(map[string]*net.UDPConn)
// read from dstAddr and write to sendCh
writerFn := func(raddr *net.UDPAddr, udpConn *net.UDPConn) {
addr := raddr.String()
defer func() {
mu.Lock()
delete(udpConnMap, addr)
mu.Unlock()
}()
buf := pool.GetBuf(1500)
for {
udpConn.SetReadDeadline(time.Now().Add(30 * time.Second))
n, _, err := udpConn.ReadFromUDP(buf)
if err != nil {
return
}
udpMsg := NewUdpPacket(buf[:n], nil, raddr)
if err = errors.PanicToError(func() {
select {
case sendCh <- udpMsg:
default:
}
}); err != nil {
return
}
}
}
// read from readCh
go func() {
for udpMsg := range readCh {
buf, err := GetContent(udpMsg)
if err != nil {
continue
}
mu.Lock()
udpConn, ok := udpConnMap[udpMsg.RemoteAddr.String()]
if !ok {
udpConn, err = net.DialUDP("udp", nil, dstAddr)
if err != nil {
continue
}
udpConnMap[udpMsg.RemoteAddr.String()] = udpConn
}
mu.Unlock()
_, err = udpConn.Write(buf)
if err != nil {
udpConn.Close()
}
if !ok {
go writerFn(udpMsg.RemoteAddr, udpConn)
}
}
}()
}

View File

@ -0,0 +1,18 @@
package udp
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestUdpPacket(t *testing.T) {
assert := assert.New(t)
buf := []byte("hello world")
udpMsg := NewUdpPacket(buf, nil, nil)
newBuf, err := GetContent(udpMsg)
assert.NoError(err)
assert.EqualValues(buf, newBuf)
}

View File

@ -15,7 +15,7 @@ rm -rf ./packages
mkdir ./packages
os_all='linux windows darwin'
arch_all='386 amd64 arm mips64 mips64le'
arch_all='386 amd64 arm mips64 mips64le mips mipsle'
for os in $os_all; do
for arch in $arch_all; do

360
server/control.go Normal file
View File

@ -0,0 +1,360 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"fmt"
"io"
"sync"
"time"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/crypto"
"github.com/fatedier/frp/utils/errors"
"github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/shutdown"
"github.com/fatedier/frp/utils/version"
)
type Control struct {
// frps service
svr *Service
// login message
loginMsg *msg.Login
// control connection
conn net.Conn
// put a message in this channel to send it over control connection to client
sendCh chan (msg.Message)
// read from this channel to get the next message sent by client
readCh chan (msg.Message)
// work connections
workConnCh chan net.Conn
// proxies in one client
proxies []Proxy
// pool count
poolCount int
// last time got the Ping message
lastPing time.Time
// A new run id will be generated when a new client login.
// If run id got from login message has same run id, it means it's the same client, so we can
// replace old controller instantly.
runId string
// control status
status string
readerShutdown *shutdown.Shutdown
writerShutdown *shutdown.Shutdown
managerShutdown *shutdown.Shutdown
allShutdown *shutdown.Shutdown
mu sync.RWMutex
}
func NewControl(svr *Service, ctlConn net.Conn, loginMsg *msg.Login) *Control {
return &Control{
svr: svr,
conn: ctlConn,
loginMsg: loginMsg,
sendCh: make(chan msg.Message, 10),
readCh: make(chan msg.Message, 10),
workConnCh: make(chan net.Conn, loginMsg.PoolCount+10),
proxies: make([]Proxy, 0),
poolCount: loginMsg.PoolCount,
lastPing: time.Now(),
runId: loginMsg.RunId,
status: consts.Working,
readerShutdown: shutdown.New(),
writerShutdown: shutdown.New(),
managerShutdown: shutdown.New(),
allShutdown: shutdown.New(),
}
}
// Start send a login success message to client and start working.
func (ctl *Control) Start() {
loginRespMsg := &msg.LoginResp{
Version: version.Full(),
RunId: ctl.runId,
Error: "",
}
msg.WriteMsg(ctl.conn, loginRespMsg)
go ctl.writer()
for i := 0; i < ctl.poolCount; i++ {
ctl.sendCh <- &msg.ReqWorkConn{}
}
go ctl.manager()
go ctl.reader()
go ctl.stoper()
}
func (ctl *Control) RegisterWorkConn(conn net.Conn) {
defer func() {
if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err)
}
}()
select {
case ctl.workConnCh <- conn:
ctl.conn.Debug("new work connection registered")
default:
ctl.conn.Debug("work connection pool is full, discarding")
conn.Close()
}
}
// When frps get one user connection, we get one work connection from the pool and return it.
// If no workConn available in the pool, send message to frpc to get one or more
// and wait until it is available.
// return an error if wait timeout
func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
defer func() {
if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err)
}
}()
var ok bool
// get a work connection from the pool
select {
case workConn, ok = <-ctl.workConnCh:
if !ok {
err = errors.ErrCtlClosed
return
}
ctl.conn.Debug("get work connection from pool")
default:
// no work connections available in the poll, send message to frpc to get more
err = errors.PanicToError(func() {
ctl.sendCh <- &msg.ReqWorkConn{}
})
if err != nil {
ctl.conn.Error("%v", err)
return
}
select {
case workConn, ok = <-ctl.workConnCh:
if !ok {
err = errors.ErrCtlClosed
ctl.conn.Warn("no work connections avaiable, %v", err)
return
}
case <-time.After(time.Duration(config.ServerCommonCfg.UserConnTimeout) * time.Second):
err = fmt.Errorf("timeout trying to get work connection")
ctl.conn.Warn("%v", err)
return
}
}
// When we get a work connection from pool, replace it with a new one.
errors.PanicToError(func() {
ctl.sendCh <- &msg.ReqWorkConn{}
})
return
}
func (ctl *Control) Replaced(newCtl *Control) {
ctl.conn.Info("Replaced by client [%s]", newCtl.runId)
ctl.runId = ""
ctl.allShutdown.Start()
}
func (ctl *Control) writer() {
defer func() {
if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err)
}
}()
defer ctl.allShutdown.Start()
defer ctl.writerShutdown.Done()
encWriter, err := crypto.NewWriter(ctl.conn, []byte(config.ServerCommonCfg.PrivilegeToken))
if err != nil {
ctl.conn.Error("crypto new writer error: %v", err)
ctl.allShutdown.Start()
return
}
for {
if m, ok := <-ctl.sendCh; !ok {
ctl.conn.Info("control writer is closing")
return
} else {
if err := msg.WriteMsg(encWriter, m); err != nil {
ctl.conn.Warn("write message to control connection error: %v", err)
return
}
}
}
}
func (ctl *Control) reader() {
defer func() {
if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err)
}
}()
defer ctl.allShutdown.Start()
defer ctl.readerShutdown.Done()
encReader := crypto.NewReader(ctl.conn, []byte(config.ServerCommonCfg.PrivilegeToken))
for {
if m, err := msg.ReadMsg(encReader); err != nil {
if err == io.EOF {
ctl.conn.Debug("control connection closed")
return
} else {
ctl.conn.Warn("read error: %v", err)
return
}
} else {
ctl.readCh <- m
}
}
}
func (ctl *Control) stoper() {
defer func() {
if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err)
}
}()
ctl.allShutdown.WaitStart()
close(ctl.readCh)
ctl.managerShutdown.WaitDown()
close(ctl.sendCh)
ctl.writerShutdown.WaitDown()
ctl.conn.Close()
ctl.readerShutdown.WaitDown()
close(ctl.workConnCh)
for workConn := range ctl.workConnCh {
workConn.Close()
}
for _, pxy := range ctl.proxies {
pxy.Close()
ctl.svr.DelProxy(pxy.GetName())
StatsCloseProxy(pxy.GetConf().GetBaseInfo().ProxyType)
}
ctl.allShutdown.Done()
ctl.conn.Info("client exit success")
StatsCloseClient()
}
func (ctl *Control) manager() {
defer func() {
if err := recover(); err != nil {
ctl.conn.Error("panic error: %v", err)
}
}()
defer ctl.allShutdown.Start()
defer ctl.managerShutdown.Done()
heartbeat := time.NewTicker(time.Second)
defer heartbeat.Stop()
for {
select {
case <-heartbeat.C:
if time.Since(ctl.lastPing) > time.Duration(config.ServerCommonCfg.HeartBeatTimeout)*time.Second {
ctl.conn.Warn("heartbeat timeout")
ctl.allShutdown.Start()
}
case rawMsg, ok := <-ctl.readCh:
if !ok {
return
}
switch m := rawMsg.(type) {
case *msg.NewProxy:
// register proxy in this control
err := ctl.RegisterProxy(m)
resp := &msg.NewProxyResp{
ProxyName: m.ProxyName,
}
if err != nil {
resp.Error = err.Error()
ctl.conn.Warn("new proxy [%s] error: %v", m.ProxyName, err)
} else {
ctl.conn.Info("new proxy [%s] success", m.ProxyName)
StatsNewProxy(m.ProxyName, m.ProxyType)
}
ctl.sendCh <- resp
case *msg.Ping:
ctl.lastPing = time.Now()
ctl.conn.Debug("receive heartbeat")
ctl.sendCh <- &msg.Pong{}
}
}
}
}
func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (err error) {
var pxyConf config.ProxyConf
// Load configures from NewProxy message and check.
pxyConf, err = config.NewProxyConf(pxyMsg)
if err != nil {
return err
}
// NewProxy will return a interface Proxy.
// In fact it create different proxies by different proxy type, we just call run() here.
pxy, err := NewProxy(ctl, pxyConf)
if err != nil {
return err
}
err = pxy.Run()
if err != nil {
return err
}
defer func() {
if err != nil {
pxy.Close()
}
}()
err = ctl.svr.RegisterProxy(pxyMsg.ProxyName, pxy)
if err != nil {
return err
}
ctl.proxies = append(ctl.proxies, pxy)
return nil
}

127
server/dashboard.go Normal file
View File

@ -0,0 +1,127 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"fmt"
"net"
"net/http"
"time"
"github.com/fatedier/frp/assets"
"github.com/fatedier/frp/models/config"
"github.com/julienschmidt/httprouter"
)
var (
httpServerReadTimeout = 10 * time.Second
httpServerWriteTimeout = 10 * time.Second
)
func RunDashboardServer(addr string, port int64) (err error) {
// url router
router := httprouter.New()
// api, see dashboard_api.go
router.GET("/api/serverinfo", httprouterBasicAuth(apiServerInfo))
router.GET("/api/proxy/tcp", httprouterBasicAuth(apiProxyTcp))
router.GET("/api/proxy/udp", httprouterBasicAuth(apiProxyUdp))
router.GET("/api/proxy/http", httprouterBasicAuth(apiProxyHttp))
router.GET("/api/proxy/https", httprouterBasicAuth(apiProxyHttps))
router.GET("/api/proxy/traffic/:name", httprouterBasicAuth(apiProxyTraffic))
// view
router.Handler("GET", "/favicon.ico", http.FileServer(assets.FileSystem))
router.Handler("GET", "/static/*filepath", basicAuthWraper(http.StripPrefix("/static/", http.FileServer(assets.FileSystem))))
router.HandlerFunc("GET", "/", basicAuth(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
}))
address := fmt.Sprintf("%s:%d", addr, port)
server := &http.Server{
Addr: address,
Handler: router,
ReadTimeout: httpServerReadTimeout,
WriteTimeout: httpServerWriteTimeout,
}
if address == "" {
address = ":http"
}
ln, err := net.Listen("tcp", address)
if err != nil {
return err
}
go server.Serve(ln)
return
}
func use(h http.HandlerFunc, middleware ...func(http.HandlerFunc) http.HandlerFunc) http.HandlerFunc {
for _, m := range middleware {
h = m(h)
}
return h
}
type AuthWraper struct {
h http.Handler
user string
passwd string
}
func (aw *AuthWraper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
user, passwd, hasAuth := r.BasicAuth()
if (aw.user == "" && aw.passwd == "") || (hasAuth && user == aw.user || passwd == aw.passwd) {
aw.h.ServeHTTP(w, r)
} else {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
}
func basicAuthWraper(h http.Handler) http.Handler {
return &AuthWraper{
h: h,
user: config.ServerCommonCfg.DashboardUser,
passwd: config.ServerCommonCfg.DashboardPwd,
}
}
func basicAuth(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user, passwd, hasAuth := r.BasicAuth()
if (config.ServerCommonCfg.DashboardUser == "" && config.ServerCommonCfg.DashboardPwd == "") ||
(hasAuth && user == config.ServerCommonCfg.DashboardUser || passwd == config.ServerCommonCfg.DashboardPwd) {
h.ServeHTTP(w, r)
} else {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
}
}
func httprouterBasicAuth(h httprouter.Handle) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
user, passwd, hasAuth := r.BasicAuth()
if (config.ServerCommonCfg.DashboardUser == "" && config.ServerCommonCfg.DashboardPwd == "") ||
(hasAuth && user == config.ServerCommonCfg.DashboardUser || passwd == config.ServerCommonCfg.DashboardPwd) {
h(w, r, ps)
} else {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
}
}

218
server/dashboard_api.go Normal file
View File

@ -0,0 +1,218 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"encoding/json"
"net/http"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/consts"
"github.com/fatedier/frp/utils/log"
"github.com/julienschmidt/httprouter"
)
type GeneralResponse struct {
Code int64 `json:"code"`
Msg string `json:"msg"`
}
// api/serverinfo
type ServerInfoResp struct {
GeneralResponse
VhostHttpPort int64 `json:"vhost_http_port"`
VhostHttpsPort int64 `json:"vhost_https_port"`
AuthTimeout int64 `json:"auth_timeout"`
SubdomainHost string `json:"subdomain_host"`
MaxPoolCount int64 `json:"max_pool_count"`
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
TotalTrafficIn int64 `json:"total_traffic_in"`
TotalTrafficOut int64 `json:"total_traffic_out"`
CurConns int64 `json:"cur_conns"`
ClientCounts int64 `json:"client_counts"`
ProxyTypeCounts map[string]int64 `json:"proxy_type_count"`
}
func apiServerInfo(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var (
buf []byte
res ServerInfoResp
)
defer func() {
log.Info("Http response [/api/serverinfo]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/serverinfo]")
cfg := config.ServerCommonCfg
serverStats := StatsGetServer()
res = ServerInfoResp{
VhostHttpPort: cfg.VhostHttpPort,
VhostHttpsPort: cfg.VhostHttpsPort,
AuthTimeout: cfg.AuthTimeout,
SubdomainHost: cfg.SubDomainHost,
MaxPoolCount: cfg.MaxPoolCount,
HeartBeatTimeout: cfg.HeartBeatTimeout,
TotalTrafficIn: serverStats.TotalTrafficIn,
TotalTrafficOut: serverStats.TotalTrafficOut,
CurConns: serverStats.CurConns,
ClientCounts: serverStats.ClientCounts,
ProxyTypeCounts: serverStats.ProxyTypeCounts,
}
buf, _ = json.Marshal(&res)
w.Write(buf)
}
// Get proxy info.
type ProxyStatsInfo struct {
Name string `json:"name"`
Conf config.ProxyConf `json:"conf"`
TodayTrafficIn int64 `json:"today_traffic_in"`
TodayTrafficOut int64 `json:"today_traffic_out"`
CurConns int64 `json:"cur_conns"`
Status string `json:"status"`
}
type GetProxyInfoResp struct {
GeneralResponse
Proxies []*ProxyStatsInfo `json:"proxies"`
}
// api/proxy/tcp
func apiProxyTcp(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var (
buf []byte
res GetProxyInfoResp
)
defer func() {
log.Info("Http response [/api/proxy/tcp]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/proxy/tcp]")
res.Proxies = getProxyStatsByType(consts.TcpProxy)
buf, _ = json.Marshal(&res)
w.Write(buf)
}
// api/proxy/udp
func apiProxyUdp(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var (
buf []byte
res GetProxyInfoResp
)
defer func() {
log.Info("Http response [/api/proxy/udp]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/proxy/udp]")
res.Proxies = getProxyStatsByType(consts.UdpProxy)
buf, _ = json.Marshal(&res)
w.Write(buf)
}
// api/proxy/http
func apiProxyHttp(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var (
buf []byte
res GetProxyInfoResp
)
defer func() {
log.Info("Http response [/api/proxy/http]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/proxy/http]")
res.Proxies = getProxyStatsByType(consts.HttpProxy)
buf, _ = json.Marshal(&res)
w.Write(buf)
}
// api/proxy/https
func apiProxyHttps(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var (
buf []byte
res GetProxyInfoResp
)
defer func() {
log.Info("Http response [/api/proxy/https]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/proxy/https]")
res.Proxies = getProxyStatsByType(consts.HttpsProxy)
buf, _ = json.Marshal(&res)
w.Write(buf)
}
func getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
proxyStats := StatsGetProxiesByType(proxyType)
proxyInfos = make([]*ProxyStatsInfo, 0, len(proxyStats))
for _, ps := range proxyStats {
proxyInfo := &ProxyStatsInfo{}
if pxy, ok := ServerService.pxyManager.GetByName(ps.Name); ok {
proxyInfo.Conf = pxy.GetConf()
proxyInfo.Status = consts.Online
} else {
proxyInfo.Status = consts.Offline
}
proxyInfo.TodayTrafficIn = ps.TodayTrafficIn
proxyInfo.TodayTrafficOut = ps.TodayTrafficOut
proxyInfo.CurConns = ps.CurConns
proxyInfo.Name = ps.Name
proxyInfos = append(proxyInfos, proxyInfo)
}
return
}
// api/proxy/traffic/:name
type GetProxyTrafficResp struct {
GeneralResponse
Name string `json:"name"`
TrafficIn []int64 `json:"traffic_in"`
TrafficOut []int64 `json:"traffic_out"`
}
func apiProxyTraffic(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
var (
buf []byte
res GetProxyTrafficResp
)
name := params.ByName("name")
defer func() {
log.Info("Http response [/api/proxy/traffic/:name]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/proxy/traffic/:name]")
res.Name = name
proxyTrafficInfo := StatsGetProxyTraffic(name)
if proxyTrafficInfo == nil {
res.Code = 1
res.Msg = "no proxy info found"
} else {
res.TrafficIn = proxyTrafficInfo.TrafficIn
res.TrafficOut = proxyTrafficInfo.TrafficOut
}
buf, _ = json.Marshal(&res)
w.Write(buf)
}

89
server/manager.go Normal file
View File

@ -0,0 +1,89 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"fmt"
"sync"
)
type ControlManager struct {
// controls indexed by run id
ctlsByRunId map[string]*Control
mu sync.RWMutex
}
func NewControlManager() *ControlManager {
return &ControlManager{
ctlsByRunId: make(map[string]*Control),
}
}
func (cm *ControlManager) Add(runId string, ctl *Control) (oldCtl *Control) {
cm.mu.Lock()
defer cm.mu.Unlock()
oldCtl, ok := cm.ctlsByRunId[runId]
if ok {
oldCtl.Replaced(ctl)
}
cm.ctlsByRunId[runId] = ctl
return
}
func (cm *ControlManager) GetById(runId string) (ctl *Control, ok bool) {
cm.mu.RLock()
defer cm.mu.RUnlock()
ctl, ok = cm.ctlsByRunId[runId]
return
}
type ProxyManager struct {
// proxies indexed by proxy name
pxys map[string]Proxy
mu sync.RWMutex
}
func NewProxyManager() *ProxyManager {
return &ProxyManager{
pxys: make(map[string]Proxy),
}
}
func (pm *ProxyManager) Add(name string, pxy Proxy) error {
pm.mu.Lock()
defer pm.mu.Unlock()
if _, ok := pm.pxys[name]; ok {
return fmt.Errorf("proxy name [%s] is already in use", name)
}
pm.pxys[name] = pxy
return nil
}
func (pm *ProxyManager) Del(name string) {
pm.mu.Lock()
defer pm.mu.Unlock()
delete(pm.pxys, name)
}
func (pm *ProxyManager) GetByName(name string) (pxy Proxy, ok bool) {
pm.mu.RLock()
defer pm.mu.RUnlock()
pxy, ok = pm.pxys[name]
return
}

241
server/metric.go Normal file
View File

@ -0,0 +1,241 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"sync"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/utils/metric"
)
const (
ReserveDays = 7
)
var globalStats *ServerStatistics
type ServerStatistics struct {
TotalTrafficIn metric.DateCounter
TotalTrafficOut metric.DateCounter
CurConns metric.Counter
ClientCounts metric.Counter
ProxyTypeCounts map[string]metric.Counter
ProxyStatistics map[string]*ProxyStatistics
mu sync.Mutex
}
type ProxyStatistics struct {
ProxyType string
TrafficIn metric.DateCounter
TrafficOut metric.DateCounter
CurConns metric.Counter
}
func init() {
globalStats = &ServerStatistics{
TotalTrafficIn: metric.NewDateCounter(ReserveDays),
TotalTrafficOut: metric.NewDateCounter(ReserveDays),
CurConns: metric.NewCounter(),
ClientCounts: metric.NewCounter(),
ProxyTypeCounts: make(map[string]metric.Counter),
ProxyStatistics: make(map[string]*ProxyStatistics),
}
}
func StatsNewClient() {
if config.ServerCommonCfg.DashboardPort != 0 {
globalStats.ClientCounts.Inc(1)
}
}
func StatsCloseClient() {
if config.ServerCommonCfg.DashboardPort != 0 {
globalStats.ClientCounts.Dec(1)
}
}
func StatsNewProxy(name string, proxyType string) {
if config.ServerCommonCfg.DashboardPort != 0 {
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
counter, ok := globalStats.ProxyTypeCounts[proxyType]
if !ok {
counter = metric.NewCounter()
}
counter.Inc(1)
globalStats.ProxyTypeCounts[proxyType] = counter
proxyStats, ok := globalStats.ProxyStatistics[name]
if !ok {
proxyStats = &ProxyStatistics{
ProxyType: proxyType,
CurConns: metric.NewCounter(),
TrafficIn: metric.NewDateCounter(ReserveDays),
TrafficOut: metric.NewDateCounter(ReserveDays),
}
globalStats.ProxyStatistics[name] = proxyStats
}
}
}
func StatsCloseProxy(proxyType string) {
if config.ServerCommonCfg.DashboardPort != 0 {
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
if counter, ok := globalStats.ProxyTypeCounts[proxyType]; ok {
counter.Dec(1)
}
}
}
func StatsOpenConnection(name string) {
if config.ServerCommonCfg.DashboardPort != 0 {
globalStats.CurConns.Inc(1)
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
proxyStats, ok := globalStats.ProxyStatistics[name]
if ok {
proxyStats.CurConns.Inc(1)
globalStats.ProxyStatistics[name] = proxyStats
}
}
}
func StatsCloseConnection(name string) {
if config.ServerCommonCfg.DashboardPort != 0 {
globalStats.CurConns.Dec(1)
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
proxyStats, ok := globalStats.ProxyStatistics[name]
if ok {
proxyStats.CurConns.Dec(1)
globalStats.ProxyStatistics[name] = proxyStats
}
}
}
func StatsAddTrafficIn(name string, trafficIn int64) {
if config.ServerCommonCfg.DashboardPort != 0 {
globalStats.TotalTrafficIn.Inc(trafficIn)
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
proxyStats, ok := globalStats.ProxyStatistics[name]
if ok {
proxyStats.TrafficIn.Inc(trafficIn)
globalStats.ProxyStatistics[name] = proxyStats
}
}
}
func StatsAddTrafficOut(name string, trafficOut int64) {
if config.ServerCommonCfg.DashboardPort != 0 {
globalStats.TotalTrafficOut.Inc(trafficOut)
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
proxyStats, ok := globalStats.ProxyStatistics[name]
if ok {
proxyStats.TrafficOut.Inc(trafficOut)
globalStats.ProxyStatistics[name] = proxyStats
}
}
}
// Functions for getting server stats.
type ServerStats struct {
TotalTrafficIn int64
TotalTrafficOut int64
CurConns int64
ClientCounts int64
ProxyTypeCounts map[string]int64
}
func StatsGetServer() *ServerStats {
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
s := &ServerStats{
TotalTrafficIn: globalStats.TotalTrafficIn.TodayCount(),
TotalTrafficOut: globalStats.TotalTrafficOut.TodayCount(),
CurConns: globalStats.CurConns.Count(),
ClientCounts: globalStats.ClientCounts.Count(),
ProxyTypeCounts: make(map[string]int64),
}
for k, v := range globalStats.ProxyTypeCounts {
s.ProxyTypeCounts[k] = v.Count()
}
return s
}
type ProxyStats struct {
Name string
Type string
TodayTrafficIn int64
TodayTrafficOut int64
CurConns int64
}
func StatsGetProxiesByType(proxyType string) []*ProxyStats {
res := make([]*ProxyStats, 0)
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
for name, proxyStats := range globalStats.ProxyStatistics {
if proxyStats.ProxyType != proxyType {
continue
}
ps := &ProxyStats{
Name: name,
Type: proxyStats.ProxyType,
TodayTrafficIn: proxyStats.TrafficIn.TodayCount(),
TodayTrafficOut: proxyStats.TrafficOut.TodayCount(),
CurConns: proxyStats.CurConns.Count(),
}
res = append(res, ps)
}
return res
}
type ProxyTrafficInfo struct {
Name string
TrafficIn []int64
TrafficOut []int64
}
func StatsGetProxyTraffic(name string) (res *ProxyTrafficInfo) {
globalStats.mu.Lock()
defer globalStats.mu.Unlock()
proxyStats, ok := globalStats.ProxyStatistics[name]
if ok {
res = &ProxyTrafficInfo{
Name: name,
}
res.TrafficIn = proxyStats.TrafficIn.GetLastDaysCount(ReserveDays)
res.TrafficOut = proxyStats.TrafficOut.GetLastDaysCount(ReserveDays)
}
return
}

482
server/proxy.go Normal file
View File

@ -0,0 +1,482 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"context"
"fmt"
"io"
"net"
"sync"
"time"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/models/proto/tcp"
"github.com/fatedier/frp/models/proto/udp"
"github.com/fatedier/frp/utils/errors"
"github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/vhost"
)
type Proxy interface {
Run() error
GetControl() *Control
GetName() string
GetConf() config.ProxyConf
GetWorkConnFromPool() (workConn frpNet.Conn, err error)
Close()
log.Logger
}
type BaseProxy struct {
name string
ctl *Control
listeners []frpNet.Listener
mu sync.RWMutex
log.Logger
}
func (pxy *BaseProxy) GetName() string {
return pxy.name
}
func (pxy *BaseProxy) GetControl() *Control {
return pxy.ctl
}
func (pxy *BaseProxy) Close() {
pxy.Info("proxy closing")
for _, l := range pxy.listeners {
l.Close()
}
}
func (pxy *BaseProxy) GetWorkConnFromPool() (workConn frpNet.Conn, err error) {
ctl := pxy.GetControl()
// try all connections from the pool
for i := 0; i < ctl.poolCount+1; i++ {
if workConn, err = ctl.GetWorkConn(); err != nil {
pxy.Warn("failed to get work connection: %v", err)
return
}
pxy.Info("get a new work connection: [%s]", workConn.RemoteAddr().String())
workConn.AddLogPrefix(pxy.GetName())
err := msg.WriteMsg(workConn, &msg.StartWorkConn{
ProxyName: pxy.GetName(),
})
if err != nil {
workConn.Warn("failed to send message to work connection from pool: %v, times: %d", err, i)
workConn.Close()
} else {
break
}
}
if err != nil {
pxy.Error("try to get work connection failed in the end")
return
}
return
}
// startListenHandler start a goroutine handler for each listener.
// p: p will just be passed to handler(Proxy, frpNet.Conn).
// handler: each proxy type can set different handler function to deal with connections accepted from listeners.
func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Conn)) {
for _, listener := range pxy.listeners {
go func(l frpNet.Listener) {
for {
// block
// if listener is closed, err returned
c, err := l.Accept()
if err != nil {
pxy.Info("listener is closed")
return
}
pxy.Debug("get a user connection [%s]", c.RemoteAddr().String())
go handler(p, c)
}
}(listener)
}
}
func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy, err error) {
basePxy := BaseProxy{
name: pxyConf.GetName(),
ctl: ctl,
listeners: make([]frpNet.Listener, 0),
Logger: log.NewPrefixLogger(ctl.runId),
}
switch cfg := pxyConf.(type) {
case *config.TcpProxyConf:
pxy = &TcpProxy{
BaseProxy: basePxy,
cfg: cfg,
}
case *config.HttpProxyConf:
pxy = &HttpProxy{
BaseProxy: basePxy,
cfg: cfg,
}
case *config.HttpsProxyConf:
pxy = &HttpsProxy{
BaseProxy: basePxy,
cfg: cfg,
}
case *config.UdpProxyConf:
pxy = &UdpProxy{
BaseProxy: basePxy,
cfg: cfg,
}
default:
return pxy, fmt.Errorf("proxy type not support")
}
pxy.AddLogPrefix(pxy.GetName())
return
}
type TcpProxy struct {
BaseProxy
cfg *config.TcpProxyConf
}
func (pxy *TcpProxy) Run() error {
listener, err := frpNet.ListenTcp(config.ServerCommonCfg.BindAddr, pxy.cfg.RemotePort)
if err != nil {
return err
}
listener.AddLogPrefix(pxy.name)
pxy.listeners = append(pxy.listeners, listener)
pxy.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort)
pxy.startListenHandler(pxy, HandleUserTcpConnection)
return nil
}
func (pxy *TcpProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *TcpProxy) Close() {
pxy.BaseProxy.Close()
}
type HttpProxy struct {
BaseProxy
cfg *config.HttpProxyConf
}
func (pxy *HttpProxy) Run() (err error) {
routeConfig := &vhost.VhostRouteConfig{
RewriteHost: pxy.cfg.HostHeaderRewrite,
Username: pxy.cfg.HttpUser,
Password: pxy.cfg.HttpPwd,
}
locations := pxy.cfg.Locations
if len(locations) == 0 {
locations = []string{""}
}
for _, domain := range pxy.cfg.CustomDomains {
routeConfig.Domain = domain
for _, location := range locations {
routeConfig.Location = location
l, err := pxy.ctl.svr.VhostHttpMuxer.Listen(routeConfig)
if err != nil {
return err
}
l.AddLogPrefix(pxy.name)
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
pxy.listeners = append(pxy.listeners, l)
}
}
if pxy.cfg.SubDomain != "" {
routeConfig.Domain = pxy.cfg.SubDomain + "." + config.ServerCommonCfg.SubDomainHost
for _, location := range locations {
routeConfig.Location = location
l, err := pxy.ctl.svr.VhostHttpMuxer.Listen(routeConfig)
if err != nil {
return err
}
l.AddLogPrefix(pxy.name)
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
pxy.listeners = append(pxy.listeners, l)
}
}
pxy.startListenHandler(pxy, HandleUserTcpConnection)
return
}
func (pxy *HttpProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *HttpProxy) Close() {
pxy.BaseProxy.Close()
}
type HttpsProxy struct {
BaseProxy
cfg *config.HttpsProxyConf
}
func (pxy *HttpsProxy) Run() (err error) {
routeConfig := &vhost.VhostRouteConfig{}
for _, domain := range pxy.cfg.CustomDomains {
routeConfig.Domain = domain
l, err := pxy.ctl.svr.VhostHttpsMuxer.Listen(routeConfig)
if err != nil {
return err
}
l.AddLogPrefix(pxy.name)
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
pxy.listeners = append(pxy.listeners, l)
}
if pxy.cfg.SubDomain != "" {
routeConfig.Domain = pxy.cfg.SubDomain + "." + config.ServerCommonCfg.SubDomainHost
l, err := pxy.ctl.svr.VhostHttpsMuxer.Listen(routeConfig)
if err != nil {
return err
}
l.AddLogPrefix(pxy.name)
pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
pxy.listeners = append(pxy.listeners, l)
}
pxy.startListenHandler(pxy, HandleUserTcpConnection)
return
}
func (pxy *HttpsProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *HttpsProxy) Close() {
pxy.BaseProxy.Close()
}
type UdpProxy struct {
BaseProxy
cfg *config.UdpProxyConf
// udpConn is the listener of udp packages
udpConn *net.UDPConn
// there are always only one workConn at the same time
// get another one if it closed
workConn net.Conn
// sendCh is used for sending packages to workConn
sendCh chan *msg.UdpPacket
// readCh is used for reading packages from workConn
readCh chan *msg.UdpPacket
// checkCloseCh is used for watching if workConn is closed
checkCloseCh chan int
isClosed bool
}
func (pxy *UdpProxy) Run() (err error) {
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", config.ServerCommonCfg.BindAddr, pxy.cfg.RemotePort))
if err != nil {
return err
}
udpConn, err := net.ListenUDP("udp", addr)
if err != nil {
pxy.Warn("listen udp port error: %v", err)
return err
}
pxy.Info("udp proxy listen port [%d]", pxy.cfg.RemotePort)
pxy.udpConn = udpConn
pxy.sendCh = make(chan *msg.UdpPacket, 1024)
pxy.readCh = make(chan *msg.UdpPacket, 1024)
pxy.checkCloseCh = make(chan int)
// read message from workConn, if it returns any error, notify proxy to start a new workConn
workConnReaderFn := func(conn net.Conn) {
for {
var (
rawMsg msg.Message
errRet error
)
pxy.Trace("loop waiting message from udp workConn")
// client will send heartbeat in workConn for keeping alive
conn.SetReadDeadline(time.Now().Add(time.Duration(60) * time.Second))
if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil {
pxy.Warn("read from workConn for udp error: %v", errRet)
conn.Close()
// notify proxy to start a new work connection
// ignore error here, it means the proxy is closed
errors.PanicToError(func() {
pxy.checkCloseCh <- 1
})
return
}
conn.SetReadDeadline(time.Time{})
switch m := rawMsg.(type) {
case *msg.Ping:
pxy.Trace("udp work conn get ping message")
continue
case *msg.UdpPacket:
if errRet := errors.PanicToError(func() {
pxy.Trace("get udp message from workConn: %s", m.Content)
pxy.readCh <- m
StatsAddTrafficOut(pxy.GetName(), int64(len(m.Content)))
}); errRet != nil {
conn.Close()
pxy.Info("reader goroutine for udp work connection closed")
return
}
}
}
}
// send message to workConn
workConnSenderFn := func(conn net.Conn, ctx context.Context) {
var errRet error
for {
select {
case udpMsg, ok := <-pxy.sendCh:
if !ok {
pxy.Info("sender goroutine for udp work connection closed")
return
}
if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil {
pxy.Info("sender goroutine for udp work connection closed: %v", errRet)
conn.Close()
return
} else {
pxy.Trace("send message to udp workConn: %s", udpMsg.Content)
StatsAddTrafficIn(pxy.GetName(), int64(len(udpMsg.Content)))
continue
}
case <-ctx.Done():
pxy.Info("sender goroutine for udp work connection closed")
return
}
}
}
go func() {
// Sleep a while for waiting control send the NewProxyResp to client.
time.Sleep(500 * time.Millisecond)
for {
workConn, err := pxy.GetWorkConnFromPool()
if err != nil {
time.Sleep(1 * time.Second)
// check if proxy is closed
select {
case _, ok := <-pxy.checkCloseCh:
if !ok {
return
}
default:
}
continue
}
// close the old workConn and replac it with a new one
if pxy.workConn != nil {
pxy.workConn.Close()
}
pxy.workConn = workConn
ctx, cancel := context.WithCancel(context.Background())
go workConnReaderFn(workConn)
go workConnSenderFn(workConn, ctx)
_, ok := <-pxy.checkCloseCh
cancel()
if !ok {
return
}
}
}()
// Read from user connections and send wrapped udp message to sendCh (forwarded by workConn).
// Client will transfor udp message to local udp service and waiting for response for a while.
// Response will be wrapped to be forwarded by work connection to server.
// Close readCh and sendCh at the end.
go func() {
udp.ForwardUserConn(udpConn, pxy.readCh, pxy.sendCh)
pxy.Close()
}()
return nil
}
func (pxy *UdpProxy) GetConf() config.ProxyConf {
return pxy.cfg
}
func (pxy *UdpProxy) Close() {
pxy.mu.Lock()
defer pxy.mu.Unlock()
if !pxy.isClosed {
pxy.isClosed = true
pxy.BaseProxy.Close()
if pxy.workConn != nil {
pxy.workConn.Close()
}
pxy.udpConn.Close()
// all channels only closed here
close(pxy.checkCloseCh)
close(pxy.readCh)
close(pxy.sendCh)
}
}
// HandleUserTcpConnection is used for incoming tcp user connections.
// It can be used for tcp, http, https type.
func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn) {
defer userConn.Close()
// try all connections from the pool
workConn, err := pxy.GetWorkConnFromPool()
if err != nil {
return
}
defer workConn.Close()
var local io.ReadWriteCloser = workConn
cfg := pxy.GetConf().GetBaseInfo()
if cfg.UseEncryption {
local, err = tcp.WithEncryption(local, []byte(config.ServerCommonCfg.PrivilegeToken))
if err != nil {
pxy.Error("create encryption stream error: %v", err)
return
}
}
if cfg.UseCompression {
local = tcp.WithCompression(local)
}
pxy.Debug("join connections, workConn(l[%s] r[%s]) userConn(l[%s] r[%s])", workConn.LocalAddr().String(),
workConn.RemoteAddr().String(), userConn.LocalAddr().String(), userConn.RemoteAddr().String())
StatsOpenConnection(pxy.GetName())
inCount, outCount := tcp.Join(local, userConn)
StatsCloseConnection(pxy.GetName())
StatsAddTrafficIn(pxy.GetName(), inCount)
StatsAddTrafficOut(pxy.GetName(), outCount)
pxy.Debug("join connections closed")
}

247
server/service.go Normal file
View File

@ -0,0 +1,247 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"fmt"
"time"
"github.com/fatedier/frp/assets"
"github.com/fatedier/frp/models/config"
"github.com/fatedier/frp/models/msg"
"github.com/fatedier/frp/utils/log"
frpNet "github.com/fatedier/frp/utils/net"
"github.com/fatedier/frp/utils/util"
"github.com/fatedier/frp/utils/version"
"github.com/fatedier/frp/utils/vhost"
"github.com/xtaci/smux"
)
const (
connReadTimeout time.Duration = 10 * time.Second
)
var ServerService *Service
// Server service.
type Service struct {
// Accept connections from client.
listener frpNet.Listener
// For http proxies, route requests to different clients by hostname and other infomation.
VhostHttpMuxer *vhost.HttpMuxer
// For https proxies, route requests to different clients by hostname and other infomation.
VhostHttpsMuxer *vhost.HttpsMuxer
// Manage all controllers.
ctlManager *ControlManager
// Manage all proxies.
pxyManager *ProxyManager
}
func NewService() (svr *Service, err error) {
svr = &Service{
ctlManager: NewControlManager(),
pxyManager: NewProxyManager(),
}
// Init assets.
err = assets.Load(config.ServerCommonCfg.AssetsDir)
if err != nil {
err = fmt.Errorf("Load assets error: %v", err)
return
}
// Listen for accepting connections from client.
svr.listener, err = frpNet.ListenTcp(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.BindPort)
if err != nil {
err = fmt.Errorf("Create server listener error, %v", err)
return
}
// Create http vhost muxer.
if config.ServerCommonCfg.VhostHttpPort != 0 {
var l frpNet.Listener
l, err = frpNet.ListenTcp(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.VhostHttpPort)
if err != nil {
err = fmt.Errorf("Create vhost http listener error, %v", err)
return
}
svr.VhostHttpMuxer, err = vhost.NewHttpMuxer(l, 30*time.Second)
if err != nil {
err = fmt.Errorf("Create vhost httpMuxer error, %v", err)
return
}
}
// Create https vhost muxer.
if config.ServerCommonCfg.VhostHttpsPort != 0 {
var l frpNet.Listener
l, err = frpNet.ListenTcp(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.VhostHttpsPort)
if err != nil {
err = fmt.Errorf("Create vhost https listener error, %v", err)
return
}
svr.VhostHttpsMuxer, err = vhost.NewHttpsMuxer(l, 30*time.Second)
if err != nil {
err = fmt.Errorf("Create vhost httpsMuxer error, %v", err)
return
}
}
// Create dashboard web server.
if config.ServerCommonCfg.DashboardPort != 0 {
err = RunDashboardServer(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.DashboardPort)
if err != nil {
err = fmt.Errorf("Create dashboard web server error, %v", err)
return
}
}
return
}
func (svr *Service) Run() {
// Listen for incoming connections from client.
for {
c, err := svr.listener.Accept()
if err != nil {
log.Warn("Listener for incoming connections from client closed")
return
}
// Start a new goroutine for dealing connections.
go func(frpConn frpNet.Conn) {
dealFn := func(conn frpNet.Conn) {
var rawMsg msg.Message
conn.SetReadDeadline(time.Now().Add(connReadTimeout))
if rawMsg, err = msg.ReadMsg(conn); err != nil {
log.Warn("Failed to read message: %v", err)
conn.Close()
return
}
conn.SetReadDeadline(time.Time{})
switch m := rawMsg.(type) {
case *msg.Login:
err = svr.RegisterControl(conn, m)
// If login failed, send error message there.
// Otherwise send success message in control's work goroutine.
if err != nil {
conn.Warn("%v", err)
msg.WriteMsg(conn, &msg.LoginResp{
Version: version.Full(),
Error: err.Error(),
})
conn.Close()
}
case *msg.NewWorkConn:
svr.RegisterWorkConn(conn, m)
default:
log.Warn("Error message type for the new connection [%s]", conn.RemoteAddr().String())
conn.Close()
}
}
if config.ServerCommonCfg.TcpMux {
session, err := smux.Server(frpConn, nil)
if err != nil {
log.Warn("Failed to create mux connection: %v", err)
frpConn.Close()
return
}
for {
stream, err := session.AcceptStream()
if err != nil {
log.Warn("Accept new mux stream error: %v", err)
session.Close()
return
}
wrapConn := frpNet.WrapConn(stream)
go dealFn(wrapConn)
}
} else {
dealFn(frpConn)
}
}(c)
}
}
func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (err error) {
ctlConn.Info("client login info: ip [%s] version [%s] hostname [%s] os [%s] arch [%s]",
ctlConn.RemoteAddr().String(), loginMsg.Version, loginMsg.Hostname, loginMsg.Os, loginMsg.Arch)
// Check client version.
if ok, msg := version.Compat(loginMsg.Version); !ok {
err = fmt.Errorf("%s", msg)
return
}
// Check auth.
nowTime := time.Now().Unix()
if config.ServerCommonCfg.AuthTimeout != 0 && nowTime-loginMsg.Timestamp > config.ServerCommonCfg.AuthTimeout {
err = fmt.Errorf("authorization timeout")
return
}
if util.GetAuthKey(config.ServerCommonCfg.PrivilegeToken, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
err = fmt.Errorf("authorization failed")
return
}
// If client's RunId is empty, it's a new client, we just create a new controller.
// Otherwise, we check if there is one controller has the same run id. If so, we release previous controller and start new one.
if loginMsg.RunId == "" {
loginMsg.RunId, err = util.RandId()
if err != nil {
return
}
}
ctl := NewControl(svr, ctlConn, loginMsg)
if oldCtl := svr.ctlManager.Add(loginMsg.RunId, ctl); oldCtl != nil {
oldCtl.allShutdown.WaitDown()
}
ctlConn.AddLogPrefix(loginMsg.RunId)
ctl.Start()
// for statistics
StatsNewClient()
return
}
// RegisterWorkConn register a new work connection to control and proxies need it.
func (svr *Service) RegisterWorkConn(workConn frpNet.Conn, newMsg *msg.NewWorkConn) {
ctl, exist := svr.ctlManager.GetById(newMsg.RunId)
if !exist {
workConn.Warn("No client control found for run id [%s]", newMsg.RunId)
return
}
ctl.RegisterWorkConn(workConn)
return
}
func (svr *Service) RegisterProxy(name string, pxy Proxy) error {
err := svr.pxyManager.Add(name, pxy)
return err
}
func (svr *Service) DelProxy(name string) {
svr.pxyManager.Del(name)
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,18 +0,0 @@
@font-face {font-family: "iconfont";
src: url('/static/font/iconfont.eot'); /* IE9*/
src: url('/static/font/iconfont.eot#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/static/font/iconfont.woff') format('woff'), /* chrome, firefox */
url('/static/font/iconfont.ttf') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
url('/static/font/iconfont.svg#iconfont') format('svg'); /* iOS 4.1- */
}
.iconfont {
font-family:"iconfont" !important;
font-size:16px;
font-style:normal;
-webkit-font-smoothing: antialiased;
-webkit-text-stroke-width: 0.2px;
-moz-osx-font-smoothing: grayscale;
}
.icon-sort:before { content: "\e66d"; }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

View File

@ -1,39 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>
Created by FontForge 20120731 at Thu Aug 4 18:45:29 2016
By admin
</metadata>
<defs>
<font id="iconfont" horiz-adv-x="374" >
<font-face
font-family="iconfont"
font-weight="500"
font-stretch="normal"
units-per-em="1024"
panose-1="2 0 6 3 0 0 0 0 0 0"
ascent="896"
descent="-128"
x-height="792"
bbox="34 -83 956 792"
underline-thickness="50"
underline-position="-100"
unicode-range="U+0078-E66D"
/>
<missing-glyph
d="M34 0v682h272v-682h-272zM68 34h204v614h-204v-614z" />
<glyph glyph-name=".notdef"
d="M34 0v682h272v-682h-272zM68 34h204v614h-204v-614z" />
<glyph glyph-name=".null" horiz-adv-x="0"
/>
<glyph glyph-name="nonmarkingreturn" horiz-adv-x="341"
/>
<glyph glyph-name="x" unicode="x" horiz-adv-x="1001"
d="M281 543q-27 -1 -53 -1h-83q-18 0 -36.5 -6t-32.5 -18.5t-23 -32t-9 -45.5v-76h912v41q0 16 -0.5 30t-0.5 18q0 13 -5 29t-17 29.5t-31.5 22.5t-49.5 9h-133v-97h-438v97zM955 310v-52q0 -23 0.5 -52t0.5 -58t-10.5 -47.5t-26 -30t-33 -16t-31.5 -4.5q-14 -1 -29.5 -0.5
t-29.5 0.5h-32l-45 128h-439l-44 -128h-29h-34q-20 0 -45 1q-25 0 -41 9.5t-25.5 23t-13.5 29.5t-4 30v167h911zM163 247q-12 0 -21 -8.5t-9 -21.5t9 -21.5t21 -8.5q13 0 22 8.5t9 21.5t-9 21.5t-22 8.5zM316 123q-8 -26 -14 -48q-5 -19 -10.5 -37t-7.5 -25t-3 -15t1 -14.5
t9.5 -10.5t21.5 -4h37h67h81h80h64h36q23 0 34 12t2 38q-5 13 -9.5 30.5t-9.5 34.5q-5 19 -11 39h-368zM336 498v228q0 11 2.5 23t10 21.5t20.5 15.5t34 6h188q31 0 51.5 -14.5t20.5 -52.5v-227h-327z" />
<glyph glyph-name="uniE66D" unicode="&#xe66d;" horiz-adv-x="1024"
d="M922 440l-39 -39l-207 200v-684h-55v766h54zM349 -83l-247 243l39 39l207 -200v684h55v-766h-54z" />
</font>
</defs></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Binary file not shown.

View File

@ -1,310 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>frp</title>
<link href="static/css/bootstrap.min.css" rel="stylesheet">
<link href="static/css/iconfont.css" rel="stylesheet">
<script src="static/js/jquery.min.js"></script>
<script src="static/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container" style="margin-top: 80px">
<div class="row">
<div class="col-md-7">
<div class="panel panel-default">
<div ng-app="myTable" class="panel-body">
<table class="table table-bordered" ng-app="myTable" ng-controller="myCtrl">
<thead>
<tr>
<th class="tab_info" ng-click="col='name';desc=!desc">Server<i class="iconfont pull-right">&#xe66d;</i></th>
<th class="tab_info" ng-click="col='type';desc=!desc">Type<i class="iconfont pull-right">&#xe66d;</i></th>
<th class="tab_info" ng-click="col='listen_port';desc=!desc">Port<i class="iconfont pull-right">&#xe66d;</i></th>
<th class="tab_info" ng-click="col='status';desc=!desc">Status<i class="iconfont pull-right">&#xe66d;</i></th>
<th class="tab_info" ng-click="col='current_conns';desc=!desc">CurCon<i class="iconfont pull-right">&#xe66d;</i></th>
<th class="tab_info" ng-click="col='daily[daily.length-1].flow_out';desc=!desc">FlowOut<i class="iconfont pull-right">&#xe66d;</i></th>
<th class="tab_info" ng-click="col='daily[daily.length-1].flow_in';desc=!desc">FlowIn<i class="iconfont pull-right">&#xe66d;</i></th>
<th class="tab_info" ng-click="col='daily[daily.length-1].total_accept_conns';desc=!desc">TotalAcceptConns<i class="iconfont pull-right">&#xe66d;</i></th>
</tr>
</thead>
<tbody id="tab_body">
<tr ng-repeat="x in proxies|orderBy:col:desc">
<td>
<button class="btn btn-xs btn-block btn-success center-block" onclick="changeit(this)"><span ng-bind="x.name"></span></button>
</td>
<td><span ng-bind="x.type"></span></td>
<td><span ng-bind="x.listen_port"></span></td>
<td><span ng-bind="x.status"></span></td>
<td><span ng-bind="x.current_conns"></span></td>
<td><span ng-bind="x.daily[x.daily.length-1].flow_out"></span></td>
<td><span ng-bind="x.daily[x.daily.length-1].flow_in"></span></td>
<td><span ng-bind="x.daily[x.daily.length-1].total_accept_conns"></span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-5">
<div id="view1" style="height: 300pt"></div>
<div id="view2" style="height: 300pt"></div>
</div>
</div>
</div>
<script src="static/js/angular.min.js"></script>
<script type="text/javascript" src="static/js/echarts.min.js"></script>
<script>
var alldata = new Array();
var index = null;
<<< range .>>>
alldata["<<< .Name >>>"] = {
name: "<<< .Name >>>",
type: "<<< .Type >>>",
bind_addr: "<<< .BindAddr >>>",
listen_port: "<<< .ListenPort >>>",
current_conns: <<< .CurrentConns >>> ,
domains: [ <<< range.CustomDomains >>> "<<< . >>>", <<< end >>> ],
locations: [ <<< range.Locations >>> "<<< . >>>", <<< end >>> ],
stat: "<<< .Status>>>",
use_encryption: "<<< .UseEncryption >>>",
use_gzip: "<<< .UseGzip >>>",
privilege_mode: "<<< .PrivilegeMode >>>",
times: [],
ins: [],
outs: [],
conns: [],
};
<<< end >>>
var newproxies = <<< . >>>;
var dom1 = document.getElementById("view1");
var dom2 = document.getElementById("view2");
var myChart1 = echarts.init(dom1);
var myChart2 = echarts.init(dom2);
var option1 = null;
var option2 = null;
var maxval = 0;
var dw = " B";
var step = 1;
function reloadview() {
window.maxval = 0;
window.dw = " B";
window.step = 1;
for (var val1 in alldata[index].ins) {
if (maxval < alldata[index].ins[val1]) {
window.maxval = alldata[index].outs[val1];
}
}
for (var val2 in alldata[index].outs) {
if (maxval < alldata[index].outs[val2]) {
window.maxval = alldata[index].outs[val2]
}
}
if (maxval > 1024 * 1024) {
window.dw = " MB";
window.step = 1024 * 1024;
} else if (maxval > 1024) {
window.dw = " KB";
window.step = 1024;
}
window.option1 = {
title: {
text: alldata[index].name
},
tooltip: {
trigger: 'axis'
},
legend: {
data: ['flow_in', 'flow_out']
},
grid: {
left: '3%',
right: '6%',
bottom: '3%',
containLabel: true
},
toolbox: {
feature: {
saveAsImage: {}
}
},
xAxis: {
type: 'category',
data: alldata[index].times
},
yAxis: {
type: 'value',
axisLabel: {
formatter: function(value) {
var data = (value / step).toFixed(2);
return data + dw;
}
}
},
series: [{
name: 'flow_in',
type: 'bar',
stack: '总量',
data: alldata[index].ins
}, {
name: 'flow_out',
type: 'bar',
stack: '总量',
data: alldata[index].outs
}]
};
window.option2 = {
title: {
text: ""
},
tooltip: {
trigger: 'axis'
},
legend: {
data: ['total_accept_conns']
},
grid: {
left: '3%',
right: '6%',
bottom: '3%',
containLabel: true
},
toolbox: {
feature: {
saveAsImage: {}
}
},
xAxis: {
type: 'category',
data: alldata[index].times
},
yAxis: {
type: 'value'
},
series: [{
name: 'total_accept_conns',
type: 'bar',
stack: '总量',
data: alldata[index].conns
}]
};
myChart1.setOption(option1, true);
myChart2.setOption(option2, true);
};;
var showdetail = false;
var newindex = 0;
function cleandetail() {
$(".info_detail").remove();
showdetail = false;
};
function changeit(id) {
newindex = id.firstElementChild.innerHTML;
if (index == newindex) {
if (showdetail) {
cleandetail();
return;
}
}
index = newindex;
cleandetail();
window.showdetail = true;
var newrow = "<tr class='info_detail'><th colspan='4'>Key</th><th colspan='4'>Val</th><tr>";
for (var domainindex in alldata[index].domains) {
newrow += "<tr class='info_detail'><td colspan='4'>Domains</td><td colspan='4'>" +
alldata[index].domains[domainindex] + "</td><tr>";
}
for (var locindex in alldata[index].locations) {
newrow += "<tr class='info_detail'><td colspan='4'>Locations</td><td colspan='4'>" +
alldata[index].locations[locindex] + "</td><tr>";
}
newrow += "<tr class='info_detail'><td colspan='4'>Ip</td><td colspan='4'>" + alldata[index].bind_addr + "</td><tr>";
newrow += "<tr class='info_detail'><td colspan='4'>Status</td><td colspan='4'>" + alldata[index].stat + "</td><tr>";
newrow += "<tr class='info_detail'><td colspan='4'>Encryption</td><td colspan='4'>" + alldata[index].use_encryption + "</td><tr>";
newrow += "<tr class='info_detail'><td colspan='4'>Gzip</td><td colspan='4'>" + alldata[index].use_gzip + "</td><tr>";
newrow += "<tr class='info_detail'><td colspan='4'>Privilege</td><td colspan='4'>" + alldata[index].privilege_mode + "</td><tr>";
var hehe = $(id.parentNode.parentNode);
$(hehe).after(newrow);
reloadview();
};
// add somedata
{
var ttdy = new Date();
var today = ttdy.getFullYear() * 10000 + (1 + ttdy.getMonth()) * 100 + ttdy.getDate();
for (var inx in newproxies) {
if (newproxies[inx].current_conns == undefined) {
newproxies[inx].current_conns = 0;
alldata[newproxies[inx].name].current_conns = 0;
}
if (newproxies[inx].daily == undefined ) {
newproxies[inx].daily = [];
}
newproxies[inx].daily.sort(function (a, b) {
return a.time > b.time;
});
for (var iinnx in newproxies[inx].daily) {
alldata[newproxies[inx].name].times.push(newproxies[inx].daily[iinnx].time);
alldata[newproxies[inx].name].ins.push(newproxies[inx].daily[iinnx].flow_in);
alldata[newproxies[inx].name].outs.push(newproxies[inx].daily[iinnx].flow_out);
alldata[newproxies[inx].name].conns.push(newproxies[inx].daily[iinnx].total_accept_conns);
}
if (newproxies[inx].daily.length == 0 || newproxies[inx].daily[newproxies[inx].daily.length-1].time != today) {
alldata[newproxies[inx].name].times.push(today);
alldata[newproxies[inx].name].ins.push(0);
alldata[newproxies[inx].name].outs.push(0);
alldata[newproxies[inx].name].conns.push(0);
newproxies[inx].daily.push({
time: today,
flow_in: 0,
flow_out: 0,
total_accept_conns: 0
});
}
}
}
var app = angular.module('myTable', []);
app.controller('myCtrl', function($scope) {
$scope.col = 'name';
$scope.desc = 0;
$scope.proxies = newproxies;
});
$(".tab_info").hover(
function() {
$(this).css("color", "orange");
},
function() {
$(this).css("color", "black");
});
// set default index
for (var inx in alldata) {
if (window.index == null || window.index > inx) {
window.index = inx;
}
}
reloadview();
</script>
</body>
</html>

View File

@ -1,317 +0,0 @@
/*
AngularJS v1.5.8
(c) 2010-2016 Google, Inc. http://angularjs.org
License: MIT
*/
(function(C){'use strict';function N(a){return function(){var b=arguments[0],d;d="["+(a?a+":":"")+b+"] http://errors.angularjs.org/1.5.8/"+(a?a+"/":"")+b;for(b=1;b<arguments.length;b++){d=d+(1==b?"?":"&")+"p"+(b-1)+"=";var c=encodeURIComponent,e;e=arguments[b];e="function"==typeof e?e.toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof e?"undefined":"string"!=typeof e?JSON.stringify(e):e;d+=c(e)}return Error(d)}}function ta(a){if(null==a||Va(a))return!1;if(L(a)||G(a)||F&&a instanceof F)return!0;
var b="length"in Object(a)&&a.length;return T(b)&&(0<=b&&(b-1 in a||a instanceof Array)||"function"==typeof a.item)}function q(a,b,d){var c,e;if(a)if(z(a))for(c in a)"prototype"==c||"length"==c||"name"==c||a.hasOwnProperty&&!a.hasOwnProperty(c)||b.call(d,a[c],c,a);else if(L(a)||ta(a)){var f="object"!==typeof a;c=0;for(e=a.length;c<e;c++)(f||c in a)&&b.call(d,a[c],c,a)}else if(a.forEach&&a.forEach!==q)a.forEach(b,d,a);else if(sc(a))for(c in a)b.call(d,a[c],c,a);else if("function"===typeof a.hasOwnProperty)for(c in a)a.hasOwnProperty(c)&&
b.call(d,a[c],c,a);else for(c in a)ua.call(a,c)&&b.call(d,a[c],c,a);return a}function tc(a,b,d){for(var c=Object.keys(a).sort(),e=0;e<c.length;e++)b.call(d,a[c[e]],c[e]);return c}function uc(a){return function(b,d){a(d,b)}}function Yd(){return++pb}function Pb(a,b,d){for(var c=a.$$hashKey,e=0,f=b.length;e<f;++e){var g=b[e];if(D(g)||z(g))for(var h=Object.keys(g),k=0,l=h.length;k<l;k++){var m=h[k],n=g[m];d&&D(n)?da(n)?a[m]=new Date(n.valueOf()):Wa(n)?a[m]=new RegExp(n):n.nodeName?a[m]=n.cloneNode(!0):
Qb(n)?a[m]=n.clone():(D(a[m])||(a[m]=L(n)?[]:{}),Pb(a[m],[n],!0)):a[m]=n}}c?a.$$hashKey=c:delete a.$$hashKey;return a}function S(a){return Pb(a,va.call(arguments,1),!1)}function Zd(a){return Pb(a,va.call(arguments,1),!0)}function Z(a){return parseInt(a,10)}function Rb(a,b){return S(Object.create(a),b)}function A(){}function Xa(a){return a}function ha(a){return function(){return a}}function vc(a){return z(a.toString)&&a.toString!==ma}function y(a){return"undefined"===typeof a}function w(a){return"undefined"!==
typeof a}function D(a){return null!==a&&"object"===typeof a}function sc(a){return null!==a&&"object"===typeof a&&!wc(a)}function G(a){return"string"===typeof a}function T(a){return"number"===typeof a}function da(a){return"[object Date]"===ma.call(a)}function z(a){return"function"===typeof a}function Wa(a){return"[object RegExp]"===ma.call(a)}function Va(a){return a&&a.window===a}function Ya(a){return a&&a.$evalAsync&&a.$watch}function Ga(a){return"boolean"===typeof a}function $d(a){return a&&T(a.length)&&
ae.test(ma.call(a))}function Qb(a){return!(!a||!(a.nodeName||a.prop&&a.attr&&a.find))}function be(a){var b={};a=a.split(",");var d;for(d=0;d<a.length;d++)b[a[d]]=!0;return b}function wa(a){return Q(a.nodeName||a[0]&&a[0].nodeName)}function Za(a,b){var d=a.indexOf(b);0<=d&&a.splice(d,1);return d}function pa(a,b){function d(a,b){var d=b.$$hashKey,e;if(L(a)){e=0;for(var f=a.length;e<f;e++)b.push(c(a[e]))}else if(sc(a))for(e in a)b[e]=c(a[e]);else if(a&&"function"===typeof a.hasOwnProperty)for(e in a)a.hasOwnProperty(e)&&
(b[e]=c(a[e]));else for(e in a)ua.call(a,e)&&(b[e]=c(a[e]));d?b.$$hashKey=d:delete b.$$hashKey;return b}function c(a){if(!D(a))return a;var b=f.indexOf(a);if(-1!==b)return g[b];if(Va(a)||Ya(a))throw xa("cpws");var b=!1,c=e(a);void 0===c&&(c=L(a)?[]:Object.create(wc(a)),b=!0);f.push(a);g.push(c);return b?d(a,c):c}function e(a){switch(ma.call(a)){case "[object Int8Array]":case "[object Int16Array]":case "[object Int32Array]":case "[object Float32Array]":case "[object Float64Array]":case "[object Uint8Array]":case "[object Uint8ClampedArray]":case "[object Uint16Array]":case "[object Uint32Array]":return new a.constructor(c(a.buffer),
a.byteOffset,a.length);case "[object ArrayBuffer]":if(!a.slice){var b=new ArrayBuffer(a.byteLength);(new Uint8Array(b)).set(new Uint8Array(a));return b}return a.slice(0);case "[object Boolean]":case "[object Number]":case "[object String]":case "[object Date]":return new a.constructor(a.valueOf());case "[object RegExp]":return b=new RegExp(a.source,a.toString().match(/[^\/]*$/)[0]),b.lastIndex=a.lastIndex,b;case "[object Blob]":return new a.constructor([a],{type:a.type})}if(z(a.cloneNode))return a.cloneNode(!0)}
var f=[],g=[];if(b){if($d(b)||"[object ArrayBuffer]"===ma.call(b))throw xa("cpta");if(a===b)throw xa("cpi");L(b)?b.length=0:q(b,function(a,d){"$$hashKey"!==d&&delete b[d]});f.push(a);g.push(b);return d(a,b)}return c(a)}function na(a,b){if(a===b)return!0;if(null===a||null===b)return!1;if(a!==a&&b!==b)return!0;var d=typeof a,c;if(d==typeof b&&"object"==d)if(L(a)){if(!L(b))return!1;if((d=a.length)==b.length){for(c=0;c<d;c++)if(!na(a[c],b[c]))return!1;return!0}}else{if(da(a))return da(b)?na(a.getTime(),
b.getTime()):!1;if(Wa(a))return Wa(b)?a.toString()==b.toString():!1;if(Ya(a)||Ya(b)||Va(a)||Va(b)||L(b)||da(b)||Wa(b))return!1;d=U();for(c in a)if("$"!==c.charAt(0)&&!z(a[c])){if(!na(a[c],b[c]))return!1;d[c]=!0}for(c in b)if(!(c in d)&&"$"!==c.charAt(0)&&w(b[c])&&!z(b[c]))return!1;return!0}return!1}function $a(a,b,d){return a.concat(va.call(b,d))}function ab(a,b){var d=2<arguments.length?va.call(arguments,2):[];return!z(b)||b instanceof RegExp?b:d.length?function(){return arguments.length?b.apply(a,
$a(d,arguments,0)):b.apply(a,d)}:function(){return arguments.length?b.apply(a,arguments):b.call(a)}}function ce(a,b){var d=b;"string"===typeof a&&"$"===a.charAt(0)&&"$"===a.charAt(1)?d=void 0:Va(b)?d="$WINDOW":b&&C.document===b?d="$DOCUMENT":Ya(b)&&(d="$SCOPE");return d}function bb(a,b){if(!y(a))return T(b)||(b=b?2:null),JSON.stringify(a,ce,b)}function xc(a){return G(a)?JSON.parse(a):a}function yc(a,b){a=a.replace(de,"");var d=Date.parse("Jan 01, 1970 00:00:00 "+a)/6E4;return isNaN(d)?b:d}function Sb(a,
b,d){d=d?-1:1;var c=a.getTimezoneOffset();b=yc(b,c);d*=b-c;a=new Date(a.getTime());a.setMinutes(a.getMinutes()+d);return a}function ya(a){a=F(a).clone();try{a.empty()}catch(b){}var d=F("<div>").append(a).html();try{return a[0].nodeType===Ma?Q(d):d.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+Q(b)})}catch(c){return Q(d)}}function zc(a){try{return decodeURIComponent(a)}catch(b){}}function Ac(a){var b={};q((a||"").split("&"),function(a){var c,e,f;a&&(e=a=a.replace(/\+/g,"%20"),
c=a.indexOf("="),-1!==c&&(e=a.substring(0,c),f=a.substring(c+1)),e=zc(e),w(e)&&(f=w(f)?zc(f):!0,ua.call(b,e)?L(b[e])?b[e].push(f):b[e]=[b[e],f]:b[e]=f))});return b}function Tb(a){var b=[];q(a,function(a,c){L(a)?q(a,function(a){b.push(ea(c,!0)+(!0===a?"":"="+ea(a,!0)))}):b.push(ea(c,!0)+(!0===a?"":"="+ea(a,!0)))});return b.length?b.join("&"):""}function qb(a){return ea(a,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function ea(a,b){return encodeURIComponent(a).replace(/%40/gi,
"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,b?"%20":"+")}function ee(a,b){var d,c,e=Na.length;for(c=0;c<e;++c)if(d=Na[c]+b,G(d=a.getAttribute(d)))return d;return null}function fe(a,b){var d,c,e={};q(Na,function(b){b+="app";!d&&a.hasAttribute&&a.hasAttribute(b)&&(d=a,c=a.getAttribute(b))});q(Na,function(b){b+="app";var e;!d&&(e=a.querySelector("["+b.replace(":","\\:")+"]"))&&(d=e,c=e.getAttribute(b))});d&&(e.strictDi=null!==ee(d,"strict-di"),
b(d,c?[c]:[],e))}function Bc(a,b,d){D(d)||(d={});d=S({strictDi:!1},d);var c=function(){a=F(a);if(a.injector()){var c=a[0]===C.document?"document":ya(a);throw xa("btstrpd",c.replace(/</,"&lt;").replace(/>/,"&gt;"));}b=b||[];b.unshift(["$provide",function(b){b.value("$rootElement",a)}]);d.debugInfoEnabled&&b.push(["$compileProvider",function(a){a.debugInfoEnabled(!0)}]);b.unshift("ng");c=cb(b,d.strictDi);c.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",
d);c(b)(a)})}]);return c},e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;C&&e.test(C.name)&&(d.debugInfoEnabled=!0,C.name=C.name.replace(e,""));if(C&&!f.test(C.name))return c();C.name=C.name.replace(f,"");ca.resumeBootstrap=function(a){q(a,function(a){b.push(a)});return c()};z(ca.resumeDeferredBootstrap)&&ca.resumeDeferredBootstrap()}function ge(){C.name="NG_ENABLE_DEBUG_INFO!"+C.name;C.location.reload()}function he(a){a=ca.element(a).injector();if(!a)throw xa("test");return a.get("$$testability")}
function Cc(a,b){b=b||"_";return a.replace(ie,function(a,c){return(c?b:"")+a.toLowerCase()})}function je(){var a;if(!Dc){var b=rb();(qa=y(b)?C.jQuery:b?C[b]:void 0)&&qa.fn.on?(F=qa,S(qa.fn,{scope:Oa.scope,isolateScope:Oa.isolateScope,controller:Oa.controller,injector:Oa.injector,inheritedData:Oa.inheritedData}),a=qa.cleanData,qa.cleanData=function(b){for(var c,e=0,f;null!=(f=b[e]);e++)(c=qa._data(f,"events"))&&c.$destroy&&qa(f).triggerHandler("$destroy");a(b)}):F=O;ca.element=F;Dc=!0}}function sb(a,
b,d){if(!a)throw xa("areq",b||"?",d||"required");return a}function Pa(a,b,d){d&&L(a)&&(a=a[a.length-1]);sb(z(a),b,"not a function, got "+(a&&"object"===typeof a?a.constructor.name||"Object":typeof a));return a}function Qa(a,b){if("hasOwnProperty"===a)throw xa("badname",b);}function Ec(a,b,d){if(!b)return a;b=b.split(".");for(var c,e=a,f=b.length,g=0;g<f;g++)c=b[g],a&&(a=(e=a)[c]);return!d&&z(a)?ab(e,a):a}function tb(a){for(var b=a[0],d=a[a.length-1],c,e=1;b!==d&&(b=b.nextSibling);e++)if(c||a[e]!==
b)c||(c=F(va.call(a,0,e))),c.push(b);return c||a}function U(){return Object.create(null)}function ke(a){function b(a,b,c){return a[b]||(a[b]=c())}var d=N("$injector"),c=N("ng");a=b(a,"angular",Object);a.$$minErr=a.$$minErr||N;return b(a,"module",function(){var a={};return function(f,g,h){if("hasOwnProperty"===f)throw c("badname","module");g&&a.hasOwnProperty(f)&&(a[f]=null);return b(a,f,function(){function a(b,d,e,f){f||(f=c);return function(){f[e||"push"]([b,d,arguments]);return R}}function b(a,
d){return function(b,e){e&&z(e)&&(e.$$moduleName=f);c.push([a,d,arguments]);return R}}if(!g)throw d("nomod",f);var c=[],e=[],p=[],u=a("$injector","invoke","push",e),R={_invokeQueue:c,_configBlocks:e,_runBlocks:p,requires:g,name:f,provider:b("$provide","provider"),factory:b("$provide","factory"),service:b("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),decorator:b("$provide","decorator"),animation:b("$animateProvider","register"),filter:b("$filterProvider",
"register"),controller:b("$controllerProvider","register"),directive:b("$compileProvider","directive"),component:b("$compileProvider","component"),config:u,run:function(a){p.push(a);return this}};h&&u(h);return R})}})}function ia(a,b){if(L(a)){b=b||[];for(var d=0,c=a.length;d<c;d++)b[d]=a[d]}else if(D(a))for(d in b=b||{},a)if("$"!==d.charAt(0)||"$"!==d.charAt(1))b[d]=a[d];return b||a}function le(a){S(a,{bootstrap:Bc,copy:pa,extend:S,merge:Zd,equals:na,element:F,forEach:q,injector:cb,noop:A,bind:ab,
toJson:bb,fromJson:xc,identity:Xa,isUndefined:y,isDefined:w,isString:G,isFunction:z,isObject:D,isNumber:T,isElement:Qb,isArray:L,version:me,isDate:da,lowercase:Q,uppercase:ub,callbacks:{$$counter:0},getTestability:he,$$minErr:N,$$csp:Ba,reloadWithDebugInfo:ge});Ub=ke(C);Ub("ng",["ngLocale"],["$provide",function(a){a.provider({$$sanitizeUri:ne});a.provider("$compile",Fc).directive({a:oe,input:Gc,textarea:Gc,form:pe,script:qe,select:re,style:se,option:te,ngBind:ue,ngBindHtml:ve,ngBindTemplate:we,ngClass:xe,
ngClassEven:ye,ngClassOdd:ze,ngCloak:Ae,ngController:Be,ngForm:Ce,ngHide:De,ngIf:Ee,ngInclude:Fe,ngInit:Ge,ngNonBindable:He,ngPluralize:Ie,ngRepeat:Je,ngShow:Ke,ngStyle:Le,ngSwitch:Me,ngSwitchWhen:Ne,ngSwitchDefault:Oe,ngOptions:Pe,ngTransclude:Qe,ngModel:Re,ngList:Se,ngChange:Te,pattern:Hc,ngPattern:Hc,required:Ic,ngRequired:Ic,minlength:Jc,ngMinlength:Jc,maxlength:Kc,ngMaxlength:Kc,ngValue:Ue,ngModelOptions:Ve}).directive({ngInclude:We}).directive(vb).directive(Lc);a.provider({$anchorScroll:Xe,
$animate:Ye,$animateCss:Ze,$$animateJs:$e,$$animateQueue:af,$$AnimateRunner:bf,$$animateAsyncRun:cf,$browser:df,$cacheFactory:ef,$controller:ff,$document:gf,$exceptionHandler:hf,$filter:Mc,$$forceReflow:jf,$interpolate:kf,$interval:lf,$http:mf,$httpParamSerializer:nf,$httpParamSerializerJQLike:of,$httpBackend:pf,$xhrFactory:qf,$jsonpCallbacks:rf,$location:sf,$log:tf,$parse:uf,$rootScope:vf,$q:wf,$$q:xf,$sce:yf,$sceDelegate:zf,$sniffer:Af,$templateCache:Bf,$templateRequest:Cf,$$testability:Df,$timeout:Ef,
$window:Ff,$$rAF:Gf,$$jqLite:Hf,$$HashMap:If,$$cookieReader:Jf})}])}function db(a){return a.replace(Kf,function(a,d,c,e){return e?c.toUpperCase():c}).replace(Lf,"Moz$1")}function Nc(a){a=a.nodeType;return 1===a||!a||9===a}function Oc(a,b){var d,c,e=b.createDocumentFragment(),f=[];if(Vb.test(a)){d=e.appendChild(b.createElement("div"));c=(Mf.exec(a)||["",""])[1].toLowerCase();c=ja[c]||ja._default;d.innerHTML=c[1]+a.replace(Nf,"<$1></$2>")+c[2];for(c=c[0];c--;)d=d.lastChild;f=$a(f,d.childNodes);d=e.firstChild;
d.textContent=""}else f.push(b.createTextNode(a));e.textContent="";e.innerHTML="";q(f,function(a){e.appendChild(a)});return e}function Pc(a,b){var d=a.parentNode;d&&d.replaceChild(b,a);b.appendChild(a)}function O(a){if(a instanceof O)return a;var b;G(a)&&(a=W(a),b=!0);if(!(this instanceof O)){if(b&&"<"!=a.charAt(0))throw Wb("nosel");return new O(a)}if(b){b=C.document;var d;a=(d=Of.exec(a))?[b.createElement(d[1])]:(d=Oc(a,b))?d.childNodes:[]}Qc(this,a)}function Xb(a){return a.cloneNode(!0)}function wb(a,
b){b||eb(a);if(a.querySelectorAll)for(var d=a.querySelectorAll("*"),c=0,e=d.length;c<e;c++)eb(d[c])}function Rc(a,b,d,c){if(w(c))throw Wb("offargs");var e=(c=xb(a))&&c.events,f=c&&c.handle;if(f)if(b){var g=function(b){var c=e[b];w(d)&&Za(c||[],d);w(d)&&c&&0<c.length||(a.removeEventListener(b,f,!1),delete e[b])};q(b.split(" "),function(a){g(a);yb[a]&&g(yb[a])})}else for(b in e)"$destroy"!==b&&a.removeEventListener(b,f,!1),delete e[b]}function eb(a,b){var d=a.ng339,c=d&&fb[d];c&&(b?delete c.data[b]:
(c.handle&&(c.events.$destroy&&c.handle({},"$destroy"),Rc(a)),delete fb[d],a.ng339=void 0))}function xb(a,b){var d=a.ng339,d=d&&fb[d];b&&!d&&(a.ng339=d=++Pf,d=fb[d]={events:{},data:{},handle:void 0});return d}function Yb(a,b,d){if(Nc(a)){var c=w(d),e=!c&&b&&!D(b),f=!b;a=(a=xb(a,!e))&&a.data;if(c)a[b]=d;else{if(f)return a;if(e)return a&&a[b];S(a,b)}}}function zb(a,b){return a.getAttribute?-1<(" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").indexOf(" "+b+" "):!1}function Ab(a,b){b&&a.setAttribute&&
q(b.split(" "),function(b){a.setAttribute("class",W((" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").replace(" "+W(b)+" "," ")))})}function Bb(a,b){if(b&&a.setAttribute){var d=(" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ");q(b.split(" "),function(a){a=W(a);-1===d.indexOf(" "+a+" ")&&(d+=a+" ")});a.setAttribute("class",W(d))}}function Qc(a,b){if(b)if(b.nodeType)a[a.length++]=b;else{var d=b.length;if("number"===typeof d&&b.window!==b){if(d)for(var c=0;c<d;c++)a[a.length++]=
b[c]}else a[a.length++]=b}}function Sc(a,b){return Cb(a,"$"+(b||"ngController")+"Controller")}function Cb(a,b,d){9==a.nodeType&&(a=a.documentElement);for(b=L(b)?b:[b];a;){for(var c=0,e=b.length;c<e;c++)if(w(d=F.data(a,b[c])))return d;a=a.parentNode||11===a.nodeType&&a.host}}function Tc(a){for(wb(a,!0);a.firstChild;)a.removeChild(a.firstChild)}function Db(a,b){b||wb(a);var d=a.parentNode;d&&d.removeChild(a)}function Qf(a,b){b=b||C;if("complete"===b.document.readyState)b.setTimeout(a);else F(b).on("load",
a)}function Uc(a,b){var d=Eb[b.toLowerCase()];return d&&Vc[wa(a)]&&d}function Rf(a,b){var d=function(c,d){c.isDefaultPrevented=function(){return c.defaultPrevented};var f=b[d||c.type],g=f?f.length:0;if(g){if(y(c.immediatePropagationStopped)){var h=c.stopImmediatePropagation;c.stopImmediatePropagation=function(){c.immediatePropagationStopped=!0;c.stopPropagation&&c.stopPropagation();h&&h.call(c)}}c.isImmediatePropagationStopped=function(){return!0===c.immediatePropagationStopped};var k=f.specialHandlerWrapper||
Sf;1<g&&(f=ia(f));for(var l=0;l<g;l++)c.isImmediatePropagationStopped()||k(a,c,f[l])}};d.elem=a;return d}function Sf(a,b,d){d.call(a,b)}function Tf(a,b,d){var c=b.relatedTarget;c&&(c===a||Uf.call(a,c))||d.call(a,b)}function Hf(){this.$get=function(){return S(O,{hasClass:function(a,b){a.attr&&(a=a[0]);return zb(a,b)},addClass:function(a,b){a.attr&&(a=a[0]);return Bb(a,b)},removeClass:function(a,b){a.attr&&(a=a[0]);return Ab(a,b)}})}}function Ca(a,b){var d=a&&a.$$hashKey;if(d)return"function"===typeof d&&
(d=a.$$hashKey()),d;d=typeof a;return d="function"==d||"object"==d&&null!==a?a.$$hashKey=d+":"+(b||Yd)():d+":"+a}function Ra(a,b){if(b){var d=0;this.nextUid=function(){return++d}}q(a,this.put,this)}function Wc(a){a=(Function.prototype.toString.call(a)+" ").replace(Vf,"");return a.match(Wf)||a.match(Xf)}function Yf(a){return(a=Wc(a))?"function("+(a[1]||"").replace(/[\s\r\n]+/," ")+")":"fn"}function cb(a,b){function d(a){return function(b,c){if(D(b))q(b,uc(a));else return a(b,c)}}function c(a,b){Qa(a,
"service");if(z(b)||L(b))b=p.instantiate(b);if(!b.$get)throw Ha("pget",a);return n[a+"Provider"]=b}function e(a,b){return function(){var c=B.invoke(b,this);if(y(c))throw Ha("undef",a);return c}}function f(a,b,d){return c(a,{$get:!1!==d?e(a,b):b})}function g(a){sb(y(a)||L(a),"modulesToLoad","not an array");var b=[],c;q(a,function(a){function d(a){var b,c;b=0;for(c=a.length;b<c;b++){var e=a[b],f=p.get(e[0]);f[e[1]].apply(f,e[2])}}if(!m.get(a)){m.put(a,!0);try{G(a)?(c=Ub(a),b=b.concat(g(c.requires)).concat(c._runBlocks),
d(c._invokeQueue),d(c._configBlocks)):z(a)?b.push(p.invoke(a)):L(a)?b.push(p.invoke(a)):Pa(a,"module")}catch(e){throw L(a)&&(a=a[a.length-1]),e.message&&e.stack&&-1==e.stack.indexOf(e.message)&&(e=e.message+"\n"+e.stack),Ha("modulerr",a,e.stack||e.message||e);}}});return b}function h(a,c){function d(b,e){if(a.hasOwnProperty(b)){if(a[b]===k)throw Ha("cdep",b+" <- "+l.join(" <- "));return a[b]}try{return l.unshift(b),a[b]=k,a[b]=c(b,e)}catch(f){throw a[b]===k&&delete a[b],f;}finally{l.shift()}}function e(a,
c,f){var g=[];a=cb.$$annotate(a,b,f);for(var h=0,k=a.length;h<k;h++){var l=a[h];if("string"!==typeof l)throw Ha("itkn",l);g.push(c&&c.hasOwnProperty(l)?c[l]:d(l,f))}return g}return{invoke:function(a,b,c,d){"string"===typeof c&&(d=c,c=null);c=e(a,c,d);L(a)&&(a=a[a.length-1]);d=11>=Ea?!1:"function"===typeof a&&/^(?:class\b|constructor\()/.test(Function.prototype.toString.call(a)+" ");return d?(c.unshift(null),new (Function.prototype.bind.apply(a,c))):a.apply(b,c)},instantiate:function(a,b,c){var d=
L(a)?a[a.length-1]:a;a=e(a,b,c);a.unshift(null);return new (Function.prototype.bind.apply(d,a))},get:d,annotate:cb.$$annotate,has:function(b){return n.hasOwnProperty(b+"Provider")||a.hasOwnProperty(b)}}}b=!0===b;var k={},l=[],m=new Ra([],!0),n={$provide:{provider:d(c),factory:d(f),service:d(function(a,b){return f(a,["$injector",function(a){return a.instantiate(b)}])}),value:d(function(a,b){return f(a,ha(b),!1)}),constant:d(function(a,b){Qa(a,"constant");n[a]=b;u[a]=b}),decorator:function(a,b){var c=
p.get(a+"Provider"),d=c.$get;c.$get=function(){var a=B.invoke(d,c);return B.invoke(b,null,{$delegate:a})}}}},p=n.$injector=h(n,function(a,b){ca.isString(b)&&l.push(b);throw Ha("unpr",l.join(" <- "));}),u={},R=h(u,function(a,b){var c=p.get(a+"Provider",b);return B.invoke(c.$get,c,void 0,a)}),B=R;n.$injectorProvider={$get:ha(R)};var r=g(a),B=R.get("$injector");B.strictDi=b;q(r,function(a){a&&B.invoke(a)});return B}function Xe(){var a=!0;this.disableAutoScrolling=function(){a=!1};this.$get=["$window",
"$location","$rootScope",function(b,d,c){function e(a){var b=null;Array.prototype.some.call(a,function(a){if("a"===wa(a))return b=a,!0});return b}function f(a){if(a){a.scrollIntoView();var c;c=g.yOffset;z(c)?c=c():Qb(c)?(c=c[0],c="fixed"!==b.getComputedStyle(c).position?0:c.getBoundingClientRect().bottom):T(c)||(c=0);c&&(a=a.getBoundingClientRect().top,b.scrollBy(0,a-c))}else b.scrollTo(0,0)}function g(a){a=G(a)?a:d.hash();var b;a?(b=h.getElementById(a))?f(b):(b=e(h.getElementsByName(a)))?f(b):"top"===
a&&f(null):f(null)}var h=b.document;a&&c.$watch(function(){return d.hash()},function(a,b){a===b&&""===a||Qf(function(){c.$evalAsync(g)})});return g}]}function gb(a,b){if(!a&&!b)return"";if(!a)return b;if(!b)return a;L(a)&&(a=a.join(" "));L(b)&&(b=b.join(" "));return a+" "+b}function Zf(a){G(a)&&(a=a.split(" "));var b=U();q(a,function(a){a.length&&(b[a]=!0)});return b}function Ia(a){return D(a)?a:{}}function $f(a,b,d,c){function e(a){try{a.apply(null,va.call(arguments,1))}finally{if(R--,0===R)for(;B.length;)try{B.pop()()}catch(b){d.error(b)}}}
function f(){t=null;g();h()}function g(){r=K();r=y(r)?null:r;na(r,E)&&(r=E);E=r}function h(){if(v!==k.url()||J!==r)v=k.url(),J=r,q(M,function(a){a(k.url(),r)})}var k=this,l=a.location,m=a.history,n=a.setTimeout,p=a.clearTimeout,u={};k.isMock=!1;var R=0,B=[];k.$$completeOutstandingRequest=e;k.$$incOutstandingRequestCount=function(){R++};k.notifyWhenNoOutstandingRequests=function(a){0===R?a():B.push(a)};var r,J,v=l.href,fa=b.find("base"),t=null,K=c.history?function(){try{return m.state}catch(a){}}:
A;g();J=r;k.url=function(b,d,e){y(e)&&(e=null);l!==a.location&&(l=a.location);m!==a.history&&(m=a.history);if(b){var f=J===e;if(v===b&&(!c.history||f))return k;var h=v&&Ja(v)===Ja(b);v=b;J=e;!c.history||h&&f?(h||(t=b),d?l.replace(b):h?(d=l,e=b.indexOf("#"),e=-1===e?"":b.substr(e),d.hash=e):l.href=b,l.href!==b&&(t=b)):(m[d?"replaceState":"pushState"](e,"",b),g(),J=r);t&&(t=b);return k}return t||l.href.replace(/%27/g,"'")};k.state=function(){return r};var M=[],H=!1,E=null;k.onUrlChange=function(b){if(!H){if(c.history)F(a).on("popstate",
f);F(a).on("hashchange",f);H=!0}M.push(b);return b};k.$$applicationDestroyed=function(){F(a).off("hashchange popstate",f)};k.$$checkUrlChange=h;k.baseHref=function(){var a=fa.attr("href");return a?a.replace(/^(https?\:)?\/\/[^\/]*/,""):""};k.defer=function(a,b){var c;R++;c=n(function(){delete u[c];e(a)},b||0);u[c]=!0;return c};k.defer.cancel=function(a){return u[a]?(delete u[a],p(a),e(A),!0):!1}}function df(){this.$get=["$window","$log","$sniffer","$document",function(a,b,d,c){return new $f(a,c,b,
d)}]}function ef(){this.$get=function(){function a(a,c){function e(a){a!=n&&(p?p==a&&(p=a.n):p=a,f(a.n,a.p),f(a,n),n=a,n.n=null)}function f(a,b){a!=b&&(a&&(a.p=b),b&&(b.n=a))}if(a in b)throw N("$cacheFactory")("iid",a);var g=0,h=S({},c,{id:a}),k=U(),l=c&&c.capacity||Number.MAX_VALUE,m=U(),n=null,p=null;return b[a]={put:function(a,b){if(!y(b)){if(l<Number.MAX_VALUE){var c=m[a]||(m[a]={key:a});e(c)}a in k||g++;k[a]=b;g>l&&this.remove(p.key);return b}},get:function(a){if(l<Number.MAX_VALUE){var b=m[a];
if(!b)return;e(b)}return k[a]},remove:function(a){if(l<Number.MAX_VALUE){var b=m[a];if(!b)return;b==n&&(n=b.p);b==p&&(p=b.n);f(b.n,b.p);delete m[a]}a in k&&(delete k[a],g--)},removeAll:function(){k=U();g=0;m=U();n=p=null},destroy:function(){m=h=k=null;delete b[a]},info:function(){return S({},h,{size:g})}}}var b={};a.info=function(){var a={};q(b,function(b,e){a[e]=b.info()});return a};a.get=function(a){return b[a]};return a}}function Bf(){this.$get=["$cacheFactory",function(a){return a("templates")}]}
function Fc(a,b){function d(a,b,c){var d=/^\s*([@&<]|=(\*?))(\??)\s*(\w*)\s*$/,e=U();q(a,function(a,f){if(a in n)e[f]=n[a];else{var g=a.match(d);if(!g)throw ga("iscp",b,f,a,c?"controller bindings definition":"isolate scope definition");e[f]={mode:g[1][0],collection:"*"===g[2],optional:"?"===g[3],attrName:g[4]||f};g[4]&&(n[a]=e[f])}});return e}function c(a){var b=a.charAt(0);if(!b||b!==Q(b))throw ga("baddir",a);if(a!==a.trim())throw ga("baddir",a);}function e(a){var b=a.require||a.controller&&a.name;
!L(b)&&D(b)&&q(b,function(a,c){var d=a.match(l);a.substring(d[0].length)||(b[c]=d[0]+c)});return b}var f={},g=/^\s*directive\:\s*([\w\-]+)\s+(.*)$/,h=/(([\w\-]+)(?:\:([^;]+))?;?)/,k=be("ngSrc,ngSrcset,src,srcset"),l=/^(?:(\^\^?)?(\?)?(\^\^?)?)?/,m=/^(on[a-z]+|formaction)$/,n=U();this.directive=function B(b,d){Qa(b,"directive");G(b)?(c(b),sb(d,"directiveFactory"),f.hasOwnProperty(b)||(f[b]=[],a.factory(b+"Directive",["$injector","$exceptionHandler",function(a,c){var d=[];q(f[b],function(f,g){try{var h=
a.invoke(f);z(h)?h={compile:ha(h)}:!h.compile&&h.link&&(h.compile=ha(h.link));h.priority=h.priority||0;h.index=g;h.name=h.name||b;h.require=e(h);h.restrict=h.restrict||"EA";h.$$moduleName=f.$$moduleName;d.push(h)}catch(k){c(k)}});return d}])),f[b].push(d)):q(b,uc(B));return this};this.component=function(a,b){function c(a){function e(b){return z(b)||L(b)?function(c,d){return a.invoke(b,this,{$element:c,$attrs:d})}:b}var f=b.template||b.templateUrl?b.template:"",g={controller:d,controllerAs:Xc(b.controller)||
b.controllerAs||"$ctrl",template:e(f),templateUrl:e(b.templateUrl),transclude:b.transclude,scope:{},bindToController:b.bindings||{},restrict:"E",require:b.require};q(b,function(a,b){"$"===b.charAt(0)&&(g[b]=a)});return g}var d=b.controller||function(){};q(b,function(a,b){"$"===b.charAt(0)&&(c[b]=a,z(d)&&(d[b]=a))});c.$inject=["$injector"];return this.directive(a,c)};this.aHrefSanitizationWhitelist=function(a){return w(a)?(b.aHrefSanitizationWhitelist(a),this):b.aHrefSanitizationWhitelist()};this.imgSrcSanitizationWhitelist=
function(a){return w(a)?(b.imgSrcSanitizationWhitelist(a),this):b.imgSrcSanitizationWhitelist()};var p=!0;this.debugInfoEnabled=function(a){return w(a)?(p=a,this):p};var u=10;this.onChangesTtl=function(a){return arguments.length?(u=a,this):u};this.$get=["$injector","$interpolate","$exceptionHandler","$templateRequest","$parse","$controller","$rootScope","$sce","$animate","$$sanitizeUri",function(a,b,c,e,n,t,K,M,H,E){function I(){try{if(!--qa)throw Y=void 0,ga("infchng",u);K.$apply(function(){for(var a=
[],b=0,c=Y.length;b<c;++b)try{Y[b]()}catch(d){a.push(d)}Y=void 0;if(a.length)throw a;})}finally{qa++}}function Da(a,b){if(b){var c=Object.keys(b),d,e,f;d=0;for(e=c.length;d<e;d++)f=c[d],this[f]=b[f]}else this.$attr={};this.$$element=a}function P(a,b,c){pa.innerHTML="<span "+b+">";b=pa.firstChild.attributes;var d=b[0];b.removeNamedItem(d.name);d.value=c;a.attributes.setNamedItem(d)}function x(a,b){try{a.addClass(b)}catch(c){}}function aa(a,b,c,d,e){a instanceof F||(a=F(a));for(var f=/\S+/,g=0,h=a.length;g<
h;g++){var k=a[g];k.nodeType===Ma&&k.nodeValue.match(f)&&Pc(k,a[g]=C.document.createElement("span"))}var l=s(a,b,a,c,d,e);aa.$$addScopeClass(a);var m=null;return function(b,c,d){sb(b,"scope");e&&e.needsNewScope&&(b=b.$parent.$new());d=d||{};var f=d.parentBoundTranscludeFn,g=d.transcludeControllers;d=d.futureParentElement;f&&f.$$boundTransclude&&(f=f.$$boundTransclude);m||(m=(d=d&&d[0])?"foreignobject"!==wa(d)&&ma.call(d).match(/SVG/)?"svg":"html":"html");d="html"!==m?F(da(m,F("<div>").append(a).html())):
c?Oa.clone.call(a):a;if(g)for(var h in g)d.data("$"+h+"Controller",g[h].instance);aa.$$addScopeInfo(d,b);c&&c(d,b);l&&l(b,d,d,f);return d}}function s(a,b,c,d,e,f){function g(a,c,d,e){var f,k,l,m,p,r,v;if(n)for(v=Array(c.length),m=0;m<h.length;m+=3)f=h[m],v[f]=c[f];else v=c;m=0;for(p=h.length;m<p;)k=v[h[m++]],c=h[m++],f=h[m++],c?(c.scope?(l=a.$new(),aa.$$addScopeInfo(F(k),l)):l=a,r=c.transcludeOnThisElement?za(a,c.transclude,e):!c.templateOnThisElement&&e?e:!e&&b?za(a,b):null,c(f,l,k,d,r)):f&&f(a,
k.childNodes,void 0,e)}for(var h=[],k,l,m,p,n,r=0;r<a.length;r++){k=new Da;l=$b(a[r],[],k,0===r?d:void 0,e);(f=l.length?oa(l,a[r],k,b,c,null,[],[],f):null)&&f.scope&&aa.$$addScopeClass(k.$$element);k=f&&f.terminal||!(m=a[r].childNodes)||!m.length?null:s(m,f?(f.transcludeOnThisElement||!f.templateOnThisElement)&&f.transclude:b);if(f||k)h.push(r,f,k),p=!0,n=n||f;f=null}return p?g:null}function za(a,b,c){function d(e,f,g,h,k){e||(e=a.$new(!1,k),e.$$transcluded=!0);return b(e,f,{parentBoundTranscludeFn:c,
transcludeControllers:g,futureParentElement:h})}var e=d.$$slots=U(),f;for(f in b.$$slots)e[f]=b.$$slots[f]?za(a,b.$$slots[f],c):null;return d}function $b(a,b,c,d,e){var f=c.$attr;switch(a.nodeType){case 1:O(b,Aa(wa(a)),"E",d,e);for(var g,k,l,m,p=a.attributes,n=0,r=p&&p.length;n<r;n++){var v=!1,u=!1;g=p[n];k=g.name;l=W(g.value);g=Aa(k);if(m=Ba.test(g))k=k.replace(Yc,"").substr(8).replace(/_(.)/g,function(a,b){return b.toUpperCase()});(g=g.match(Ca))&&V(g[1])&&(v=k,u=k.substr(0,k.length-5)+"end",k=
k.substr(0,k.length-6));g=Aa(k.toLowerCase());f[g]=k;if(m||!c.hasOwnProperty(g))c[g]=l,Uc(a,g)&&(c[g]=!0);ia(a,b,l,g,m);O(b,g,"A",d,e,v,u)}f=a.className;D(f)&&(f=f.animVal);if(G(f)&&""!==f)for(;a=h.exec(f);)g=Aa(a[2]),O(b,g,"C",d,e)&&(c[g]=W(a[3])),f=f.substr(a.index+a[0].length);break;case Ma:if(11===Ea)for(;a.parentNode&&a.nextSibling&&a.nextSibling.nodeType===Ma;)a.nodeValue+=a.nextSibling.nodeValue,a.parentNode.removeChild(a.nextSibling);ca(b,a.nodeValue);break;case 8:hb(a,b,c,d,e)}b.sort(Z);
return b}function hb(a,b,c,d,e){try{var f=g.exec(a.nodeValue);if(f){var h=Aa(f[1]);O(b,h,"M",d,e)&&(c[h]=W(f[2]))}}catch(k){}}function N(a,b,c){var d=[],e=0;if(b&&a.hasAttribute&&a.hasAttribute(b)){do{if(!a)throw ga("uterdir",b,c);1==a.nodeType&&(a.hasAttribute(b)&&e++,a.hasAttribute(c)&&e--);d.push(a);a=a.nextSibling}while(0<e)}else d.push(a);return F(d)}function Zc(a,b,c){return function(d,e,f,g,h){e=N(e[0],b,c);return a(d,e,f,g,h)}}function ac(a,b,c,d,e,f){var g;return a?aa(b,c,d,e,f):function(){g||
(g=aa(b,c,d,e,f),b=c=f=null);return g.apply(this,arguments)}}function oa(a,b,d,e,f,g,h,k,l){function m(a,b,c,d){if(a){c&&(a=Zc(a,c,d));a.require=x.require;a.directiveName=I;if(u===x||x.$$isolateScope)a=ja(a,{isolateScope:!0});h.push(a)}if(b){c&&(b=Zc(b,c,d));b.require=x.require;b.directiveName=I;if(u===x||x.$$isolateScope)b=ja(b,{isolateScope:!0});k.push(b)}}function p(a,e,f,g,l){function m(a,b,c,d){var e;Ya(a)||(d=c,c=b,b=a,a=void 0);fa&&(e=t);c||(c=fa?I.parent():I);if(d){var f=l.$$slots[d];if(f)return f(a,
b,e,c,s);if(y(f))throw ga("noslot",d,ya(I));}else return l(a,b,e,c,s)}var n,E,x,M,B,t,P,I;b===f?(g=d,I=d.$$element):(I=F(f),g=new Da(I,d));B=e;u?M=e.$new(!0):r&&(B=e.$parent);l&&(P=m,P.$$boundTransclude=l,P.isSlotFilled=function(a){return!!l.$$slots[a]});v&&(t=ag(I,g,P,v,M,e,u));u&&(aa.$$addScopeInfo(I,M,!0,!(H&&(H===u||H===u.$$originalDirective))),aa.$$addScopeClass(I,!0),M.$$isolateBindings=u.$$isolateBindings,E=ka(e,g,M,M.$$isolateBindings,u),E.removeWatches&&M.$on("$destroy",E.removeWatches));
for(n in t){E=v[n];x=t[n];var Zb=E.$$bindings.bindToController;x.bindingInfo=x.identifier&&Zb?ka(B,g,x.instance,Zb,E):{};var K=x();K!==x.instance&&(x.instance=K,I.data("$"+E.name+"Controller",K),x.bindingInfo.removeWatches&&x.bindingInfo.removeWatches(),x.bindingInfo=ka(B,g,x.instance,Zb,E))}q(v,function(a,b){var c=a.require;a.bindToController&&!L(c)&&D(c)&&S(t[b].instance,ib(b,c,I,t))});q(t,function(a){var b=a.instance;if(z(b.$onChanges))try{b.$onChanges(a.bindingInfo.initialChanges)}catch(d){c(d)}if(z(b.$onInit))try{b.$onInit()}catch(e){c(e)}z(b.$doCheck)&&
(B.$watch(function(){b.$doCheck()}),b.$doCheck());z(b.$onDestroy)&&B.$on("$destroy",function(){b.$onDestroy()})});n=0;for(E=h.length;n<E;n++)x=h[n],la(x,x.isolateScope?M:e,I,g,x.require&&ib(x.directiveName,x.require,I,t),P);var s=e;u&&(u.template||null===u.templateUrl)&&(s=M);a&&a(s,f.childNodes,void 0,l);for(n=k.length-1;0<=n;n--)x=k[n],la(x,x.isolateScope?M:e,I,g,x.require&&ib(x.directiveName,x.require,I,t),P);q(t,function(a){a=a.instance;z(a.$postLink)&&a.$postLink()})}l=l||{};for(var n=-Number.MAX_VALUE,
r=l.newScopeDirective,v=l.controllerDirectives,u=l.newIsolateScopeDirective,H=l.templateDirective,E=l.nonTlbTranscludeDirective,M=!1,B=!1,fa=l.hasElementTranscludeDirective,t=d.$$element=F(b),x,I,P,K=e,s,Fa=!1,za=!1,w,A=0,C=a.length;A<C;A++){x=a[A];var G=x.$$start,hb=x.$$end;G&&(t=N(b,G,hb));P=void 0;if(n>x.priority)break;if(w=x.scope)x.templateUrl||(D(w)?(X("new/isolated scope",u||r,x,t),u=x):X("new/isolated scope",u,x,t)),r=r||x;I=x.name;if(!Fa&&(x.replace&&(x.templateUrl||x.template)||x.transclude&&
!x.$$tlb)){for(w=A+1;Fa=a[w++];)if(Fa.transclude&&!Fa.$$tlb||Fa.replace&&(Fa.templateUrl||Fa.template)){za=!0;break}Fa=!0}!x.templateUrl&&x.controller&&(w=x.controller,v=v||U(),X("'"+I+"' controller",v[I],x,t),v[I]=x);if(w=x.transclude)if(M=!0,x.$$tlb||(X("transclusion",E,x,t),E=x),"element"==w)fa=!0,n=x.priority,P=t,t=d.$$element=F(aa.$$createComment(I,d[I])),b=t[0],ea(f,va.call(P,0),b),P[0].$$parentNode=P[0].parentNode,K=ac(za,P,e,n,g&&g.name,{nonTlbTranscludeDirective:E});else{var oa=U();P=F(Xb(b)).contents();
if(D(w)){P=[];var Q=U(),O=U();q(w,function(a,b){var c="?"===a.charAt(0);a=c?a.substring(1):a;Q[a]=b;oa[b]=null;O[b]=c});q(t.contents(),function(a){var b=Q[Aa(wa(a))];b?(O[b]=!0,oa[b]=oa[b]||[],oa[b].push(a)):P.push(a)});q(O,function(a,b){if(!a)throw ga("reqslot",b);});for(var V in oa)oa[V]&&(oa[V]=ac(za,oa[V],e))}t.empty();K=ac(za,P,e,void 0,void 0,{needsNewScope:x.$$isolateScope||x.$$newScope});K.$$slots=oa}if(x.template)if(B=!0,X("template",H,x,t),H=x,w=z(x.template)?x.template(t,d):x.template,
w=xa(w),x.replace){g=x;P=Vb.test(w)?$c(da(x.templateNamespace,W(w))):[];b=P[0];if(1!=P.length||1!==b.nodeType)throw ga("tplrt",I,"");ea(f,t,b);C={$attr:{}};w=$b(b,[],C);var Z=a.splice(A+1,a.length-(A+1));(u||r)&&T(w,u,r);a=a.concat(w).concat(Z);$(d,C);C=a.length}else t.html(w);if(x.templateUrl)B=!0,X("template",H,x,t),H=x,x.replace&&(g=x),p=ba(a.splice(A,a.length-A),t,d,f,M&&K,h,k,{controllerDirectives:v,newScopeDirective:r!==x&&r,newIsolateScopeDirective:u,templateDirective:H,nonTlbTranscludeDirective:E}),
C=a.length;else if(x.compile)try{s=x.compile(t,d,K);var Y=x.$$originalDirective||x;z(s)?m(null,ab(Y,s),G,hb):s&&m(ab(Y,s.pre),ab(Y,s.post),G,hb)}catch(ca){c(ca,ya(t))}x.terminal&&(p.terminal=!0,n=Math.max(n,x.priority))}p.scope=r&&!0===r.scope;p.transcludeOnThisElement=M;p.templateOnThisElement=B;p.transclude=K;l.hasElementTranscludeDirective=fa;return p}function ib(a,b,c,d){var e;if(G(b)){var f=b.match(l);b=b.substring(f[0].length);var g=f[1]||f[3],f="?"===f[2];"^^"===g?c=c.parent():e=(e=d&&d[b])&&
e.instance;if(!e){var h="$"+b+"Controller";e=g?c.inheritedData(h):c.data(h)}if(!e&&!f)throw ga("ctreq",b,a);}else if(L(b))for(e=[],g=0,f=b.length;g<f;g++)e[g]=ib(a,b[g],c,d);else D(b)&&(e={},q(b,function(b,f){e[f]=ib(a,b,c,d)}));return e||null}function ag(a,b,c,d,e,f,g){var h=U(),k;for(k in d){var l=d[k],m={$scope:l===g||l.$$isolateScope?e:f,$element:a,$attrs:b,$transclude:c},p=l.controller;"@"==p&&(p=b[l.name]);m=t(p,m,!0,l.controllerAs);h[l.name]=m;a.data("$"+l.name+"Controller",m.instance)}return h}
function T(a,b,c){for(var d=0,e=a.length;d<e;d++)a[d]=Rb(a[d],{$$isolateScope:b,$$newScope:c})}function O(b,e,g,h,k,l,m){if(e===k)return null;k=null;if(f.hasOwnProperty(e)){var p;e=a.get(e+"Directive");for(var n=0,r=e.length;n<r;n++)try{if(p=e[n],(y(h)||h>p.priority)&&-1!=p.restrict.indexOf(g)){l&&(p=Rb(p,{$$start:l,$$end:m}));if(!p.$$bindings){var u=p,v=p,x=p.name,H={isolateScope:null,bindToController:null};D(v.scope)&&(!0===v.bindToController?(H.bindToController=d(v.scope,x,!0),H.isolateScope={}):
H.isolateScope=d(v.scope,x,!1));D(v.bindToController)&&(H.bindToController=d(v.bindToController,x,!0));if(D(H.bindToController)){var E=v.controller,M=v.controllerAs;if(!E)throw ga("noctrl",x);if(!Xc(E,M))throw ga("noident",x);}var t=u.$$bindings=H;D(t.isolateScope)&&(p.$$isolateBindings=t.isolateScope)}b.push(p);k=p}}catch(I){c(I)}}return k}function V(b){if(f.hasOwnProperty(b))for(var c=a.get(b+"Directive"),d=0,e=c.length;d<e;d++)if(b=c[d],b.multiElement)return!0;return!1}function $(a,b){var c=b.$attr,
d=a.$attr;q(a,function(d,e){"$"!=e.charAt(0)&&(b[e]&&b[e]!==d&&(d+=("style"===e?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});q(b,function(b,e){a.hasOwnProperty(e)||"$"===e.charAt(0)||(a[e]=b,"class"!==e&&"style"!==e&&(d[e]=c[e]))})}function ba(a,b,c,d,f,g,h,k){var l=[],m,p,n=b[0],r=a.shift(),u=Rb(r,{templateUrl:null,transclude:null,replace:null,$$originalDirective:r}),H=z(r.templateUrl)?r.templateUrl(b,c):r.templateUrl,E=r.templateNamespace;b.empty();e(H).then(function(e){var v,M;e=xa(e);if(r.replace){e=
Vb.test(e)?$c(da(E,W(e))):[];v=e[0];if(1!=e.length||1!==v.nodeType)throw ga("tplrt",r.name,H);e={$attr:{}};ea(d,b,v);var B=$b(v,[],e);D(r.scope)&&T(B,!0);a=B.concat(a);$(c,e)}else v=n,b.html(e);a.unshift(u);m=oa(a,v,c,f,b,r,g,h,k);q(d,function(a,c){a==v&&(d[c]=b[0])});for(p=s(b[0].childNodes,f);l.length;){e=l.shift();M=l.shift();var t=l.shift(),I=l.shift(),B=b[0];if(!e.$$destroyed){if(M!==n){var P=M.className;k.hasElementTranscludeDirective&&r.replace||(B=Xb(v));ea(t,F(M),B);x(F(B),P)}M=m.transcludeOnThisElement?
za(e,m.transclude,I):I;m(p,e,B,d,M)}}l=null});return function(a,b,c,d,e){a=e;b.$$destroyed||(l?l.push(b,c,d,a):(m.transcludeOnThisElement&&(a=za(b,m.transclude,e)),m(p,b,c,d,a)))}}function Z(a,b){var c=b.priority-a.priority;return 0!==c?c:a.name!==b.name?a.name<b.name?-1:1:a.index-b.index}function X(a,b,c,d){function e(a){return a?" (module: "+a+")":""}if(b)throw ga("multidir",b.name,e(b.$$moduleName),c.name,e(c.$$moduleName),a,ya(d));}function ca(a,c){var d=b(c,!0);d&&a.push({priority:0,compile:function(a){a=
a.parent();var b=!!a.length;b&&aa.$$addBindingClass(a);return function(a,c){var e=c.parent();b||aa.$$addBindingClass(e);aa.$$addBindingInfo(e,d.expressions);a.$watch(d,function(a){c[0].nodeValue=a})}}})}function da(a,b){a=Q(a||"html");switch(a){case "svg":case "math":var c=C.document.createElement("div");c.innerHTML="<"+a+">"+b+"</"+a+">";return c.childNodes[0].childNodes;default:return b}}function ha(a,b){if("srcdoc"==b)return M.HTML;var c=wa(a);if("xlinkHref"==b||"form"==c&&"action"==b||"img"!=
c&&("src"==b||"ngSrc"==b))return M.RESOURCE_URL}function ia(a,c,d,e,f){var g=ha(a,e);f=k[e]||f;var h=b(d,!0,g,f);if(h){if("multiple"===e&&"select"===wa(a))throw ga("selmulti",ya(a));c.push({priority:100,compile:function(){return{pre:function(a,c,k){c=k.$$observers||(k.$$observers=U());if(m.test(e))throw ga("nodomevents");var l=k[e];l!==d&&(h=l&&b(l,!0,g,f),d=l);h&&(k[e]=h(a),(c[e]||(c[e]=[])).$$inter=!0,(k.$$observers&&k.$$observers[e].$$scope||a).$watch(h,function(a,b){"class"===e&&a!=b?k.$updateClass(a,
b):k.$set(e,a)}))}}}})}}function ea(a,b,c){var d=b[0],e=b.length,f=d.parentNode,g,h;if(a)for(g=0,h=a.length;g<h;g++)if(a[g]==d){a[g++]=c;h=g+e-1;for(var k=a.length;g<k;g++,h++)h<k?a[g]=a[h]:delete a[g];a.length-=e-1;a.context===d&&(a.context=c);break}f&&f.replaceChild(c,d);a=C.document.createDocumentFragment();for(g=0;g<e;g++)a.appendChild(b[g]);F.hasData(d)&&(F.data(c,F.data(d)),F(d).off("$destroy"));F.cleanData(a.querySelectorAll("*"));for(g=1;g<e;g++)delete b[g];b[0]=c;b.length=1}function ja(a,
b){return S(function(){return a.apply(null,arguments)},a,b)}function la(a,b,d,e,f,g){try{a(b,d,e,f,g)}catch(h){c(h,ya(d))}}function ka(a,c,d,e,f){function g(b,c,e){z(d.$onChanges)&&c!==e&&(Y||(a.$$postDigest(I),Y=[]),m||(m={},Y.push(h)),m[b]&&(e=m[b].previousValue),m[b]=new Fb(e,c))}function h(){d.$onChanges(m);m=void 0}var k=[],l={},m;q(e,function(e,h){var m=e.attrName,p=e.optional,v,u,x,H;switch(e.mode){case "@":p||ua.call(c,m)||(d[h]=c[m]=void 0);c.$observe(m,function(a){if(G(a)||Ga(a))g(h,a,d[h]),
d[h]=a});c.$$observers[m].$$scope=a;v=c[m];G(v)?d[h]=b(v)(a):Ga(v)&&(d[h]=v);l[h]=new Fb(bc,d[h]);break;case "=":if(!ua.call(c,m)){if(p)break;c[m]=void 0}if(p&&!c[m])break;u=n(c[m]);H=u.literal?na:function(a,b){return a===b||a!==a&&b!==b};x=u.assign||function(){v=d[h]=u(a);throw ga("nonassign",c[m],m,f.name);};v=d[h]=u(a);p=function(b){H(b,d[h])||(H(b,v)?x(a,b=d[h]):d[h]=b);return v=b};p.$stateful=!0;p=e.collection?a.$watchCollection(c[m],p):a.$watch(n(c[m],p),null,u.literal);k.push(p);break;case "<":if(!ua.call(c,
m)){if(p)break;c[m]=void 0}if(p&&!c[m])break;u=n(c[m]);var E=d[h]=u(a);l[h]=new Fb(bc,d[h]);p=a.$watch(u,function(a,b){if(b===a){if(b===E)return;b=E}g(h,a,b);d[h]=a},u.literal);k.push(p);break;case "&":u=c.hasOwnProperty(m)?n(c[m]):A;if(u===A&&p)break;d[h]=function(b){return u(a,b)}}});return{initialChanges:l,removeWatches:k.length&&function(){for(var a=0,b=k.length;a<b;++a)k[a]()}}}var ta=/^\w/,pa=C.document.createElement("div"),qa=u,Y;Da.prototype={$normalize:Aa,$addClass:function(a){a&&0<a.length&&
H.addClass(this.$$element,a)},$removeClass:function(a){a&&0<a.length&&H.removeClass(this.$$element,a)},$updateClass:function(a,b){var c=ad(a,b);c&&c.length&&H.addClass(this.$$element,c);(c=ad(b,a))&&c.length&&H.removeClass(this.$$element,c)},$set:function(a,b,d,e){var f=Uc(this.$$element[0],a),g=bd[a],h=a;f?(this.$$element.prop(a,b),e=f):g&&(this[g]=b,h=g);this[a]=b;e?this.$attr[a]=e:(e=this.$attr[a])||(this.$attr[a]=e=Cc(a,"-"));f=wa(this.$$element);if("a"===f&&("href"===a||"xlinkHref"===a)||"img"===
f&&"src"===a)this[a]=b=E(b,"src"===a);else if("img"===f&&"srcset"===a&&w(b)){for(var f="",g=W(b),k=/(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/,k=/\s/.test(g)?k:/(,)/,g=g.split(k),k=Math.floor(g.length/2),l=0;l<k;l++)var m=2*l,f=f+E(W(g[m]),!0),f=f+(" "+W(g[m+1]));g=W(g[2*l]).split(/\s/);f+=E(W(g[0]),!0);2===g.length&&(f+=" "+W(g[1]));this[a]=b=f}!1!==d&&(null===b||y(b)?this.$$element.removeAttr(e):ta.test(e)?this.$$element.attr(e,b):P(this.$$element[0],e,b));(a=this.$$observers)&&q(a[h],function(a){try{a(b)}catch(d){c(d)}})},
$observe:function(a,b){var c=this,d=c.$$observers||(c.$$observers=U()),e=d[a]||(d[a]=[]);e.push(b);K.$evalAsync(function(){e.$$inter||!c.hasOwnProperty(a)||y(c[a])||b(c[a])});return function(){Za(e,b)}}};var ra=b.startSymbol(),sa=b.endSymbol(),xa="{{"==ra&&"}}"==sa?Xa:function(a){return a.replace(/\{\{/g,ra).replace(/}}/g,sa)},Ba=/^ngAttr[A-Z]/,Ca=/^(.+)Start$/;aa.$$addBindingInfo=p?function(a,b){var c=a.data("$binding")||[];L(b)?c=c.concat(b):c.push(b);a.data("$binding",c)}:A;aa.$$addBindingClass=
p?function(a){x(a,"ng-binding")}:A;aa.$$addScopeInfo=p?function(a,b,c,d){a.data(c?d?"$isolateScopeNoTemplate":"$isolateScope":"$scope",b)}:A;aa.$$addScopeClass=p?function(a,b){x(a,b?"ng-isolate-scope":"ng-scope")}:A;aa.$$createComment=function(a,b){var c="";p&&(c=" "+(a||"")+": ",b&&(c+=b+" "));return C.document.createComment(c)};return aa}]}function Fb(a,b){this.previousValue=a;this.currentValue=b}function Aa(a){return db(a.replace(Yc,""))}function ad(a,b){var d="",c=a.split(/\s+/),e=b.split(/\s+/),
f=0;a:for(;f<c.length;f++){for(var g=c[f],h=0;h<e.length;h++)if(g==e[h])continue a;d+=(0<d.length?" ":"")+g}return d}function $c(a){a=F(a);var b=a.length;if(1>=b)return a;for(;b--;)8===a[b].nodeType&&bg.call(a,b,1);return a}function Xc(a,b){if(b&&G(b))return b;if(G(a)){var d=cd.exec(a);if(d)return d[3]}}function ff(){var a={},b=!1;this.has=function(b){return a.hasOwnProperty(b)};this.register=function(b,c){Qa(b,"controller");D(b)?S(a,b):a[b]=c};this.allowGlobals=function(){b=!0};this.$get=["$injector",
"$window",function(d,c){function e(a,b,c,d){if(!a||!D(a.$scope))throw N("$controller")("noscp",d,b);a.$scope[b]=c}return function(f,g,h,k){var l,m,n;h=!0===h;k&&G(k)&&(n=k);if(G(f)){k=f.match(cd);if(!k)throw cg("ctrlfmt",f);m=k[1];n=n||k[3];f=a.hasOwnProperty(m)?a[m]:Ec(g.$scope,m,!0)||(b?Ec(c,m,!0):void 0);Pa(f,m,!0)}if(h)return h=(L(f)?f[f.length-1]:f).prototype,l=Object.create(h||null),n&&e(g,n,l,m||f.name),S(function(){var a=d.invoke(f,l,g,m);a!==l&&(D(a)||z(a))&&(l=a,n&&e(g,n,l,m||f.name));return l},
{instance:l,identifier:n});l=d.instantiate(f,g,m);n&&e(g,n,l,m||f.name);return l}}]}function gf(){this.$get=["$window",function(a){return F(a.document)}]}function hf(){this.$get=["$log",function(a){return function(b,d){a.error.apply(a,arguments)}}]}function cc(a){return D(a)?da(a)?a.toISOString():bb(a):a}function nf(){this.$get=function(){return function(a){if(!a)return"";var b=[];tc(a,function(a,c){null===a||y(a)||(L(a)?q(a,function(a){b.push(ea(c)+"="+ea(cc(a)))}):b.push(ea(c)+"="+ea(cc(a))))});
return b.join("&")}}}function of(){this.$get=function(){return function(a){function b(a,e,f){null===a||y(a)||(L(a)?q(a,function(a,c){b(a,e+"["+(D(a)?c:"")+"]")}):D(a)&&!da(a)?tc(a,function(a,c){b(a,e+(f?"":"[")+c+(f?"":"]"))}):d.push(ea(e)+"="+ea(cc(a))))}if(!a)return"";var d=[];b(a,"",!0);return d.join("&")}}}function dc(a,b){if(G(a)){var d=a.replace(dg,"").trim();if(d){var c=b("Content-Type");(c=c&&0===c.indexOf(dd))||(c=(c=d.match(eg))&&fg[c[0]].test(d));c&&(a=xc(d))}}return a}function ed(a){var b=
U(),d;G(a)?q(a.split("\n"),function(a){d=a.indexOf(":");var e=Q(W(a.substr(0,d)));a=W(a.substr(d+1));e&&(b[e]=b[e]?b[e]+", "+a:a)}):D(a)&&q(a,function(a,d){var f=Q(d),g=W(a);f&&(b[f]=b[f]?b[f]+", "+g:g)});return b}function fd(a){var b;return function(d){b||(b=ed(a));return d?(d=b[Q(d)],void 0===d&&(d=null),d):b}}function gd(a,b,d,c){if(z(c))return c(a,b,d);q(c,function(c){a=c(a,b,d)});return a}function mf(){var a=this.defaults={transformResponse:[dc],transformRequest:[function(a){return D(a)&&"[object File]"!==
ma.call(a)&&"[object Blob]"!==ma.call(a)&&"[object FormData]"!==ma.call(a)?bb(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:ia(ec),put:ia(ec),patch:ia(ec)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",paramSerializer:"$httpParamSerializer"},b=!1;this.useApplyAsync=function(a){return w(a)?(b=!!a,this):b};var d=!0;this.useLegacyPromiseExtensions=function(a){return w(a)?(d=!!a,this):d};var c=this.interceptors=[];this.$get=["$httpBackend","$$cookieReader","$cacheFactory",
"$rootScope","$q","$injector",function(e,f,g,h,k,l){function m(b){function c(a,b){for(var d=0,e=b.length;d<e;){var f=b[d++],g=b[d++];a=a.then(f,g)}b.length=0;return a}function e(a,b){var c,d={};q(a,function(a,e){z(a)?(c=a(b),null!=c&&(d[e]=c)):d[e]=a});return d}function f(a){var b=S({},a);b.data=gd(a.data,a.headers,a.status,g.transformResponse);a=a.status;return 200<=a&&300>a?b:k.reject(b)}if(!D(b))throw N("$http")("badreq",b);if(!G(b.url))throw N("$http")("badreq",b.url);var g=S({method:"get",transformRequest:a.transformRequest,
transformResponse:a.transformResponse,paramSerializer:a.paramSerializer},b);g.headers=function(b){var c=a.headers,d=S({},b.headers),f,g,h,c=S({},c.common,c[Q(b.method)]);a:for(f in c){g=Q(f);for(h in d)if(Q(h)===g)continue a;d[f]=c[f]}return e(d,ia(b))}(b);g.method=ub(g.method);g.paramSerializer=G(g.paramSerializer)?l.get(g.paramSerializer):g.paramSerializer;var h=[],m=[],p=k.when(g);q(R,function(a){(a.request||a.requestError)&&h.unshift(a.request,a.requestError);(a.response||a.responseError)&&m.push(a.response,
a.responseError)});p=c(p,h);p=p.then(function(b){var c=b.headers,d=gd(b.data,fd(c),void 0,b.transformRequest);y(d)&&q(c,function(a,b){"content-type"===Q(b)&&delete c[b]});y(b.withCredentials)&&!y(a.withCredentials)&&(b.withCredentials=a.withCredentials);return n(b,d).then(f,f)});p=c(p,m);d?(p.success=function(a){Pa(a,"fn");p.then(function(b){a(b.data,b.status,b.headers,g)});return p},p.error=function(a){Pa(a,"fn");p.then(null,function(b){a(b.data,b.status,b.headers,g)});return p}):(p.success=hd("success"),
p.error=hd("error"));return p}function n(c,d){function g(a){if(a){var c={};q(a,function(a,d){c[d]=function(c){function d(){a(c)}b?h.$applyAsync(d):h.$$phase?d():h.$apply(d)}});return c}}function l(a,c,d,e){function f(){n(c,a,d,e)}E&&(200<=a&&300>a?E.put(P,[a,c,ed(d),e]):E.remove(P));b?h.$applyAsync(f):(f(),h.$$phase||h.$apply())}function n(a,b,d,e){b=-1<=b?b:0;(200<=b&&300>b?M.resolve:M.reject)({data:a,status:b,headers:fd(d),config:c,statusText:e})}function t(a){n(a.data,a.status,ia(a.headers()),
a.statusText)}function R(){var a=m.pendingRequests.indexOf(c);-1!==a&&m.pendingRequests.splice(a,1)}var M=k.defer(),H=M.promise,E,I,Da=c.headers,P=p(c.url,c.paramSerializer(c.params));m.pendingRequests.push(c);H.then(R,R);!c.cache&&!a.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(E=D(c.cache)?c.cache:D(a.cache)?a.cache:u);E&&(I=E.get(P),w(I)?I&&z(I.then)?I.then(t,t):L(I)?n(I[1],I[0],ia(I[2]),I[3]):n(I,200,{},"OK"):E.put(P,H));y(I)&&((I=id(c.url)?f()[c.xsrfCookieName||a.xsrfCookieName]:
void 0)&&(Da[c.xsrfHeaderName||a.xsrfHeaderName]=I),e(c.method,P,d,l,Da,c.timeout,c.withCredentials,c.responseType,g(c.eventHandlers),g(c.uploadEventHandlers)));return H}function p(a,b){0<b.length&&(a+=(-1==a.indexOf("?")?"?":"&")+b);return a}var u=g("$http");a.paramSerializer=G(a.paramSerializer)?l.get(a.paramSerializer):a.paramSerializer;var R=[];q(c,function(a){R.unshift(G(a)?l.get(a):l.invoke(a))});m.pendingRequests=[];(function(a){q(arguments,function(a){m[a]=function(b,c){return m(S({},c||{},
{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){q(arguments,function(a){m[a]=function(b,c,d){return m(S({},d||{},{method:a,url:b,data:c}))}})})("post","put","patch");m.defaults=a;return m}]}function qf(){this.$get=function(){return function(){return new C.XMLHttpRequest}}}function pf(){this.$get=["$browser","$jsonpCallbacks","$document","$xhrFactory",function(a,b,d,c){return gg(a,c,a.defer,b,d[0])}]}function gg(a,b,d,c,e){function f(a,b,d){a=a.replace("JSON_CALLBACK",b);var f=
e.createElement("script"),m=null;f.type="text/javascript";f.src=a;f.async=!0;m=function(a){f.removeEventListener("load",m,!1);f.removeEventListener("error",m,!1);e.body.removeChild(f);f=null;var g=-1,u="unknown";a&&("load"!==a.type||c.wasCalled(b)||(a={type:"error"}),u=a.type,g="error"===a.type?404:200);d&&d(g,u)};f.addEventListener("load",m,!1);f.addEventListener("error",m,!1);e.body.appendChild(f);return m}return function(e,h,k,l,m,n,p,u,R,B){function r(){fa&&fa();t&&t.abort()}function J(b,c,e,
f,g){w(M)&&d.cancel(M);fa=t=null;b(c,e,f,g);a.$$completeOutstandingRequest(A)}a.$$incOutstandingRequestCount();h=h||a.url();if("jsonp"===Q(e))var v=c.createCallback(h),fa=f(h,v,function(a,b){var d=200===a&&c.getResponse(v);J(l,a,d,"",b);c.removeCallback(v)});else{var t=b(e,h);t.open(e,h,!0);q(m,function(a,b){w(a)&&t.setRequestHeader(b,a)});t.onload=function(){var a=t.statusText||"",b="response"in t?t.response:t.responseText,c=1223===t.status?204:t.status;0===c&&(c=b?200:"file"==Y(h).protocol?404:
0);J(l,c,b,t.getAllResponseHeaders(),a)};e=function(){J(l,-1,null,null,"")};t.onerror=e;t.onabort=e;q(R,function(a,b){t.addEventListener(b,a)});q(B,function(a,b){t.upload.addEventListener(b,a)});p&&(t.withCredentials=!0);if(u)try{t.responseType=u}catch(K){if("json"!==u)throw K;}t.send(y(k)?null:k)}if(0<n)var M=d(r,n);else n&&z(n.then)&&n.then(r)}}function kf(){var a="{{",b="}}";this.startSymbol=function(b){return b?(a=b,this):a};this.endSymbol=function(a){return a?(b=a,this):b};this.$get=["$parse",
"$exceptionHandler","$sce",function(d,c,e){function f(a){return"\\\\\\"+a}function g(c){return c.replace(n,a).replace(p,b)}function h(a,b,c,d){var e;return e=a.$watch(function(a){e();return d(a)},b,c)}function k(f,k,p,n){function J(a){try{var b=a;a=p?e.getTrusted(p,b):e.valueOf(b);var d;if(n&&!w(a))d=a;else if(null==a)d="";else{switch(typeof a){case "string":break;case "number":a=""+a;break;default:a=bb(a)}d=a}return d}catch(g){c(Ka.interr(f,g))}}if(!f.length||-1===f.indexOf(a)){var v;k||(k=g(f),
v=ha(k),v.exp=f,v.expressions=[],v.$$watchDelegate=h);return v}n=!!n;var q,t,K=0,M=[],H=[];v=f.length;for(var E=[],I=[];K<v;)if(-1!=(q=f.indexOf(a,K))&&-1!=(t=f.indexOf(b,q+l)))K!==q&&E.push(g(f.substring(K,q))),K=f.substring(q+l,t),M.push(K),H.push(d(K,J)),K=t+m,I.push(E.length),E.push("");else{K!==v&&E.push(g(f.substring(K)));break}p&&1<E.length&&Ka.throwNoconcat(f);if(!k||M.length){var Da=function(a){for(var b=0,c=M.length;b<c;b++){if(n&&y(a[b]))return;E[I[b]]=a[b]}return E.join("")};return S(function(a){var b=
0,d=M.length,e=Array(d);try{for(;b<d;b++)e[b]=H[b](a);return Da(e)}catch(g){c(Ka.interr(f,g))}},{exp:f,expressions:M,$$watchDelegate:function(a,b){var c;return a.$watchGroup(H,function(d,e){var f=Da(d);z(b)&&b.call(this,f,d!==e?c:f,a);c=f})}})}}var l=a.length,m=b.length,n=new RegExp(a.replace(/./g,f),"g"),p=new RegExp(b.replace(/./g,f),"g");k.startSymbol=function(){return a};k.endSymbol=function(){return b};return k}]}function lf(){this.$get=["$rootScope","$window","$q","$$q","$browser",function(a,
b,d,c,e){function f(f,k,l,m){function n(){p?f.apply(null,u):f(r)}var p=4<arguments.length,u=p?va.call(arguments,4):[],R=b.setInterval,q=b.clearInterval,r=0,J=w(m)&&!m,v=(J?c:d).defer(),fa=v.promise;l=w(l)?l:0;fa.$$intervalId=R(function(){J?e.defer(n):a.$evalAsync(n);v.notify(r++);0<l&&r>=l&&(v.resolve(r),q(fa.$$intervalId),delete g[fa.$$intervalId]);J||a.$apply()},k);g[fa.$$intervalId]=v;return fa}var g={};f.cancel=function(a){return a&&a.$$intervalId in g?(g[a.$$intervalId].reject("canceled"),b.clearInterval(a.$$intervalId),
delete g[a.$$intervalId],!0):!1};return f}]}function fc(a){a=a.split("/");for(var b=a.length;b--;)a[b]=qb(a[b]);return a.join("/")}function jd(a,b){var d=Y(a);b.$$protocol=d.protocol;b.$$host=d.hostname;b.$$port=Z(d.port)||hg[d.protocol]||null}function kd(a,b){var d="/"!==a.charAt(0);d&&(a="/"+a);var c=Y(a);b.$$path=decodeURIComponent(d&&"/"===c.pathname.charAt(0)?c.pathname.substring(1):c.pathname);b.$$search=Ac(c.search);b.$$hash=decodeURIComponent(c.hash);b.$$path&&"/"!=b.$$path.charAt(0)&&(b.$$path=
"/"+b.$$path)}function ka(a,b){if(0===b.lastIndexOf(a,0))return b.substr(a.length)}function Ja(a){var b=a.indexOf("#");return-1==b?a:a.substr(0,b)}function jb(a){return a.replace(/(#.+)|#$/,"$1")}function gc(a,b,d){this.$$html5=!0;d=d||"";jd(a,this);this.$$parse=function(a){var d=ka(b,a);if(!G(d))throw Gb("ipthprfx",a,b);kd(d,this);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Tb(this.$$search),d=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=fc(this.$$path)+(a?"?"+
a:"")+d;this.$$absUrl=b+this.$$url.substr(1)};this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;w(f=ka(a,c))?(g=f,g=w(f=ka(d,f))?b+(ka("/",f)||f):a+g):w(f=ka(b,c))?g=b+f:b==c+"/"&&(g=b);g&&this.$$parse(g);return!!g}}function hc(a,b,d){jd(a,this);this.$$parse=function(c){var e=ka(a,c)||ka(b,c),f;y(e)||"#"!==e.charAt(0)?this.$$html5?f=e:(f="",y(e)&&(a=c,this.replace())):(f=ka(d,e),y(f)&&(f=e));kd(f,this);c=this.$$path;var e=a,g=/^\/[A-Z]:(\/.*)/;0===f.lastIndexOf(e,
0)&&(f=f.replace(e,""));g.exec(f)||(c=(f=g.exec(c))?f[1]:c);this.$$path=c;this.$$compose()};this.$$compose=function(){var b=Tb(this.$$search),e=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=fc(this.$$path)+(b?"?"+b:"")+e;this.$$absUrl=a+(this.$$url?d+this.$$url:"")};this.$$parseLinkUrl=function(b,d){return Ja(a)==Ja(b)?(this.$$parse(b),!0):!1}}function ld(a,b,d){this.$$html5=!0;hc.apply(this,arguments);this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;a==Ja(c)?
f=c:(g=ka(b,c))?f=a+d+g:b===c+"/"&&(f=b);f&&this.$$parse(f);return!!f};this.$$compose=function(){var b=Tb(this.$$search),e=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=fc(this.$$path)+(b?"?"+b:"")+e;this.$$absUrl=a+d+this.$$url}}function Hb(a){return function(){return this[a]}}function md(a,b){return function(d){if(y(d))return this[a];this[a]=b(d);this.$$compose();return this}}function sf(){var a="",b={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(b){return w(b)?(a=b,this):
a};this.html5Mode=function(a){return Ga(a)?(b.enabled=a,this):D(a)?(Ga(a.enabled)&&(b.enabled=a.enabled),Ga(a.requireBase)&&(b.requireBase=a.requireBase),Ga(a.rewriteLinks)&&(b.rewriteLinks=a.rewriteLinks),this):b};this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function(d,c,e,f,g){function h(a,b,d){var e=l.url(),f=l.$$state;try{c.url(a,b,d),l.$$state=c.state()}catch(g){throw l.url(e),l.$$state=f,g;}}function k(a,b){d.$broadcast("$locationChangeSuccess",l.absUrl(),a,l.$$state,
b)}var l,m;m=c.baseHref();var n=c.url(),p;if(b.enabled){if(!m&&b.requireBase)throw Gb("nobase");p=n.substring(0,n.indexOf("/",n.indexOf("//")+2))+(m||"/");m=e.history?gc:ld}else p=Ja(n),m=hc;var u=p.substr(0,Ja(p).lastIndexOf("/")+1);l=new m(p,u,"#"+a);l.$$parseLinkUrl(n,n);l.$$state=c.state();var R=/^\s*(javascript|mailto):/i;f.on("click",function(a){if(b.rewriteLinks&&!a.ctrlKey&&!a.metaKey&&!a.shiftKey&&2!=a.which&&2!=a.button){for(var e=F(a.target);"a"!==wa(e[0]);)if(e[0]===f[0]||!(e=e.parent())[0])return;
var h=e.prop("href"),k=e.attr("href")||e.attr("xlink:href");D(h)&&"[object SVGAnimatedString]"===h.toString()&&(h=Y(h.animVal).href);R.test(h)||!h||e.attr("target")||a.isDefaultPrevented()||!l.$$parseLinkUrl(h,k)||(a.preventDefault(),l.absUrl()!=c.url()&&(d.$apply(),g.angular["ff-684208-preventDefault"]=!0))}});jb(l.absUrl())!=jb(n)&&c.url(l.absUrl(),!0);var q=!0;c.onUrlChange(function(a,b){y(ka(u,a))?g.location.href=a:(d.$evalAsync(function(){var c=l.absUrl(),e=l.$$state,f;a=jb(a);l.$$parse(a);l.$$state=
b;f=d.$broadcast("$locationChangeStart",a,c,b,e).defaultPrevented;l.absUrl()===a&&(f?(l.$$parse(c),l.$$state=e,h(c,!1,e)):(q=!1,k(c,e)))}),d.$$phase||d.$digest())});d.$watch(function(){var a=jb(c.url()),b=jb(l.absUrl()),f=c.state(),g=l.$$replace,m=a!==b||l.$$html5&&e.history&&f!==l.$$state;if(q||m)q=!1,d.$evalAsync(function(){var b=l.absUrl(),c=d.$broadcast("$locationChangeStart",b,a,l.$$state,f).defaultPrevented;l.absUrl()===b&&(c?(l.$$parse(a),l.$$state=f):(m&&h(b,g,f===l.$$state?null:l.$$state),
k(a,f)))});l.$$replace=!1});return l}]}function tf(){var a=!0,b=this;this.debugEnabled=function(b){return w(b)?(a=b,this):a};this.$get=["$window",function(d){function c(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=d.console||{},e=b[a]||b.log||A;a=!1;try{a=!!e.apply}catch(k){}return a?function(){var a=[];q(arguments,function(b){a.push(c(b))});
return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){a&&c.apply(b,arguments)}}()}}]}function Sa(a,b){if("__defineGetter__"===a||"__defineSetter__"===a||"__lookupGetter__"===a||"__lookupSetter__"===a||"__proto__"===a)throw X("isecfld",b);return a}function ig(a){return a+""}function ra(a,b){if(a){if(a.constructor===a)throw X("isecfn",b);if(a.window===a)throw X("isecwindow",b);if(a.children&&
(a.nodeName||a.prop&&a.attr&&a.find))throw X("isecdom",b);if(a===Object)throw X("isecobj",b);}return a}function nd(a,b){if(a){if(a.constructor===a)throw X("isecfn",b);if(a===jg||a===kg||a===lg)throw X("isecff",b);}}function Ib(a,b){if(a&&(a===(0).constructor||a===(!1).constructor||a==="".constructor||a==={}.constructor||a===[].constructor||a===Function.constructor))throw X("isecaf",b);}function mg(a,b){return"undefined"!==typeof a?a:b}function od(a,b){return"undefined"===typeof a?b:"undefined"===
typeof b?a:a+b}function V(a,b){var d,c;switch(a.type){case s.Program:d=!0;q(a.body,function(a){V(a.expression,b);d=d&&a.expression.constant});a.constant=d;break;case s.Literal:a.constant=!0;a.toWatch=[];break;case s.UnaryExpression:V(a.argument,b);a.constant=a.argument.constant;a.toWatch=a.argument.toWatch;break;case s.BinaryExpression:V(a.left,b);V(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.left.toWatch.concat(a.right.toWatch);break;case s.LogicalExpression:V(a.left,b);V(a.right,
b);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.constant?[]:[a];break;case s.ConditionalExpression:V(a.test,b);V(a.alternate,b);V(a.consequent,b);a.constant=a.test.constant&&a.alternate.constant&&a.consequent.constant;a.toWatch=a.constant?[]:[a];break;case s.Identifier:a.constant=!1;a.toWatch=[a];break;case s.MemberExpression:V(a.object,b);a.computed&&V(a.property,b);a.constant=a.object.constant&&(!a.computed||a.property.constant);a.toWatch=[a];break;case s.CallExpression:d=a.filter?!b(a.callee.name).$stateful:
!1;c=[];q(a.arguments,function(a){V(a,b);d=d&&a.constant;a.constant||c.push.apply(c,a.toWatch)});a.constant=d;a.toWatch=a.filter&&!b(a.callee.name).$stateful?c:[a];break;case s.AssignmentExpression:V(a.left,b);V(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=[a];break;case s.ArrayExpression:d=!0;c=[];q(a.elements,function(a){V(a,b);d=d&&a.constant;a.constant||c.push.apply(c,a.toWatch)});a.constant=d;a.toWatch=c;break;case s.ObjectExpression:d=!0;c=[];q(a.properties,function(a){V(a.value,
b);d=d&&a.value.constant&&!a.computed;a.value.constant||c.push.apply(c,a.value.toWatch)});a.constant=d;a.toWatch=c;break;case s.ThisExpression:a.constant=!1;a.toWatch=[];break;case s.LocalsExpression:a.constant=!1,a.toWatch=[]}}function pd(a){if(1==a.length){a=a[0].expression;var b=a.toWatch;return 1!==b.length?b:b[0]!==a?b:void 0}}function qd(a){return a.type===s.Identifier||a.type===s.MemberExpression}function rd(a){if(1===a.body.length&&qd(a.body[0].expression))return{type:s.AssignmentExpression,
left:a.body[0].expression,right:{type:s.NGValueParameter},operator:"="}}function sd(a){return 0===a.body.length||1===a.body.length&&(a.body[0].expression.type===s.Literal||a.body[0].expression.type===s.ArrayExpression||a.body[0].expression.type===s.ObjectExpression)}function td(a,b){this.astBuilder=a;this.$filter=b}function ud(a,b){this.astBuilder=a;this.$filter=b}function Jb(a){return"constructor"==a}function ic(a){return z(a.valueOf)?a.valueOf():ng.call(a)}function uf(){var a=U(),b=U(),d={"true":!0,
"false":!1,"null":null,undefined:void 0},c,e;this.addLiteral=function(a,b){d[a]=b};this.setIdentifierFns=function(a,b){c=a;e=b;return this};this.$get=["$filter",function(f){function g(c,d,e){var g,k,H;e=e||J;switch(typeof c){case "string":H=c=c.trim();var E=e?b:a;g=E[H];if(!g){":"===c.charAt(0)&&":"===c.charAt(1)&&(k=!0,c=c.substring(2));g=e?r:B;var q=new jc(g);g=(new kc(q,f,g)).parse(c);g.constant?g.$$watchDelegate=p:k?g.$$watchDelegate=g.literal?n:m:g.inputs&&(g.$$watchDelegate=l);e&&(g=h(g));E[H]=
g}return u(g,d);case "function":return u(c,d);default:return u(A,d)}}function h(a){function b(c,d,e,f){var g=J;J=!0;try{return a(c,d,e,f)}finally{J=g}}if(!a)return a;b.$$watchDelegate=a.$$watchDelegate;b.assign=h(a.assign);b.constant=a.constant;b.literal=a.literal;for(var c=0;a.inputs&&c<a.inputs.length;++c)a.inputs[c]=h(a.inputs[c]);b.inputs=a.inputs;return b}function k(a,b){return null==a||null==b?a===b:"object"===typeof a&&(a=ic(a),"object"===typeof a)?!1:a===b||a!==a&&b!==b}function l(a,b,c,d,
e){var f=d.inputs,g;if(1===f.length){var h=k,f=f[0];return a.$watch(function(a){var b=f(a);k(b,h)||(g=d(a,void 0,void 0,[b]),h=b&&ic(b));return g},b,c,e)}for(var l=[],m=[],p=0,n=f.length;p<n;p++)l[p]=k,m[p]=null;return a.$watch(function(a){for(var b=!1,c=0,e=f.length;c<e;c++){var h=f[c](a);if(b||(b=!k(h,l[c])))m[c]=h,l[c]=h&&ic(h)}b&&(g=d(a,void 0,void 0,m));return g},b,c,e)}function m(a,b,c,d){var e,f;return e=a.$watch(function(a){return d(a)},function(a,c,d){f=a;z(b)&&b.apply(this,arguments);w(a)&&
d.$$postDigest(function(){w(f)&&e()})},c)}function n(a,b,c,d){function e(a){var b=!0;q(a,function(a){w(a)||(b=!1)});return b}var f,g;return f=a.$watch(function(a){return d(a)},function(a,c,d){g=a;z(b)&&b.call(this,a,c,d);e(a)&&d.$$postDigest(function(){e(g)&&f()})},c)}function p(a,b,c,d){var e;return e=a.$watch(function(a){e();return d(a)},b,c)}function u(a,b){if(!b)return a;var c=a.$$watchDelegate,d=!1,c=c!==n&&c!==m?function(c,e,f,g){f=d&&g?g[0]:a(c,e,f,g);return b(f,c,e)}:function(c,d,e,f){e=a(c,
d,e,f);c=b(e,c,d);return w(e)?c:e};a.$$watchDelegate&&a.$$watchDelegate!==l?c.$$watchDelegate=a.$$watchDelegate:b.$stateful||(c.$$watchDelegate=l,d=!a.inputs,c.inputs=a.inputs?a.inputs:[a]);return c}var R=Ba().noUnsafeEval,B={csp:R,expensiveChecks:!1,literals:pa(d),isIdentifierStart:z(c)&&c,isIdentifierContinue:z(e)&&e},r={csp:R,expensiveChecks:!0,literals:pa(d),isIdentifierStart:z(c)&&c,isIdentifierContinue:z(e)&&e},J=!1;g.$$runningExpensiveChecks=function(){return J};return g}]}function wf(){this.$get=
["$rootScope","$exceptionHandler",function(a,b){return vd(function(b){a.$evalAsync(b)},b)}]}function xf(){this.$get=["$browser","$exceptionHandler",function(a,b){return vd(function(b){a.defer(b)},b)}]}function vd(a,b){function d(){this.$$state={status:0}}function c(a,b){return function(c){b.call(a,c)}}function e(c){!c.processScheduled&&c.pending&&(c.processScheduled=!0,a(function(){var a,d,e;e=c.pending;c.processScheduled=!1;c.pending=void 0;for(var f=0,g=e.length;f<g;++f){d=e[f][0];a=e[f][c.status];
try{z(a)?d.resolve(a(c.value)):1===c.status?d.resolve(c.value):d.reject(c.value)}catch(h){d.reject(h),b(h)}}}))}function f(){this.promise=new d}var g=N("$q",TypeError),h=function(){var a=new f;a.resolve=c(a,a.resolve);a.reject=c(a,a.reject);a.notify=c(a,a.notify);return a};S(d.prototype,{then:function(a,b,c){if(y(a)&&y(b)&&y(c))return this;var d=new f;this.$$state.pending=this.$$state.pending||[];this.$$state.pending.push([d,a,b,c]);0<this.$$state.status&&e(this.$$state);return d.promise},"catch":function(a){return this.then(null,
a)},"finally":function(a,b){return this.then(function(b){return l(b,!0,a)},function(b){return l(b,!1,a)},b)}});S(f.prototype,{resolve:function(a){this.promise.$$state.status||(a===this.promise?this.$$reject(g("qcycle",a)):this.$$resolve(a))},$$resolve:function(a){function d(a){k||(k=!0,h.$$resolve(a))}function f(a){k||(k=!0,h.$$reject(a))}var g,h=this,k=!1;try{if(D(a)||z(a))g=a&&a.then;z(g)?(this.promise.$$state.status=-1,g.call(a,d,f,c(this,this.notify))):(this.promise.$$state.value=a,this.promise.$$state.status=
1,e(this.promise.$$state))}catch(l){f(l),b(l)}},reject:function(a){this.promise.$$state.status||this.$$reject(a)},$$reject:function(a){this.promise.$$state.value=a;this.promise.$$state.status=2;e(this.promise.$$state)},notify:function(c){var d=this.promise.$$state.pending;0>=this.promise.$$state.status&&d&&d.length&&a(function(){for(var a,e,f=0,g=d.length;f<g;f++){e=d[f][0];a=d[f][3];try{e.notify(z(a)?a(c):c)}catch(h){b(h)}}})}});var k=function(a,b){var c=new f;b?c.resolve(a):c.reject(a);return c.promise},
l=function(a,b,c){var d=null;try{z(c)&&(d=c())}catch(e){return k(e,!1)}return d&&z(d.then)?d.then(function(){return k(a,b)},function(a){return k(a,!1)}):k(a,b)},m=function(a,b,c,d){var e=new f;e.resolve(a);return e.promise.then(b,c,d)},n=function(a){if(!z(a))throw g("norslvr",a);var b=new f;a(function(a){b.resolve(a)},function(a){b.reject(a)});return b.promise};n.prototype=d.prototype;n.defer=h;n.reject=function(a){var b=new f;b.reject(a);return b.promise};n.when=m;n.resolve=m;n.all=function(a){var b=
new f,c=0,d=L(a)?[]:{};q(a,function(a,e){c++;m(a).then(function(a){d.hasOwnProperty(e)||(d[e]=a,--c||b.resolve(d))},function(a){d.hasOwnProperty(e)||b.reject(a)})});0===c&&b.resolve(d);return b.promise};n.race=function(a){var b=h();q(a,function(a){m(a).then(b.resolve,b.reject)});return b.promise};return n}function Gf(){this.$get=["$window","$timeout",function(a,b){var d=a.requestAnimationFrame||a.webkitRequestAnimationFrame,c=a.cancelAnimationFrame||a.webkitCancelAnimationFrame||a.webkitCancelRequestAnimationFrame,
e=!!d,f=e?function(a){var b=d(a);return function(){c(b)}}:function(a){var c=b(a,16.66,!1);return function(){b.cancel(c)}};f.supported=e;return f}]}function vf(){function a(a){function b(){this.$$watchers=this.$$nextSibling=this.$$childHead=this.$$childTail=null;this.$$listeners={};this.$$listenerCount={};this.$$watchersCount=0;this.$id=++pb;this.$$ChildScope=null}b.prototype=a;return b}var b=10,d=N("$rootScope"),c=null,e=null;this.digestTtl=function(a){arguments.length&&(b=a);return b};this.$get=
["$exceptionHandler","$parse","$browser",function(f,g,h){function k(a){a.currentScope.$$destroyed=!0}function l(a){9===Ea&&(a.$$childHead&&l(a.$$childHead),a.$$nextSibling&&l(a.$$nextSibling));a.$parent=a.$$nextSibling=a.$$prevSibling=a.$$childHead=a.$$childTail=a.$root=a.$$watchers=null}function m(){this.$id=++pb;this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;this.$root=this;this.$$destroyed=!1;this.$$listeners={};this.$$listenerCount=
{};this.$$watchersCount=0;this.$$isolateBindings=null}function n(a){if(J.$$phase)throw d("inprog",J.$$phase);J.$$phase=a}function p(a,b){do a.$$watchersCount+=b;while(a=a.$parent)}function u(a,b,c){do a.$$listenerCount[c]-=b,0===a.$$listenerCount[c]&&delete a.$$listenerCount[c];while(a=a.$parent)}function s(){}function B(){for(;t.length;)try{t.shift()()}catch(a){f(a)}e=null}function r(){null===e&&(e=h.defer(function(){J.$apply(B)}))}m.prototype={constructor:m,$new:function(b,c){var d;c=c||this;b?
(d=new m,d.$root=this.$root):(this.$$ChildScope||(this.$$ChildScope=a(this)),d=new this.$$ChildScope);d.$parent=c;d.$$prevSibling=c.$$childTail;c.$$childHead?(c.$$childTail.$$nextSibling=d,c.$$childTail=d):c.$$childHead=c.$$childTail=d;(b||c!=this)&&d.$on("$destroy",k);return d},$watch:function(a,b,d,e){var f=g(a);if(f.$$watchDelegate)return f.$$watchDelegate(this,b,d,f,a);var h=this,k=h.$$watchers,l={fn:b,last:s,get:f,exp:e||a,eq:!!d};c=null;z(b)||(l.fn=A);k||(k=h.$$watchers=[]);k.unshift(l);p(this,
1);return function(){0<=Za(k,l)&&p(h,-1);c=null}},$watchGroup:function(a,b){function c(){h=!1;k?(k=!1,b(e,e,g)):b(e,d,g)}var d=Array(a.length),e=Array(a.length),f=[],g=this,h=!1,k=!0;if(!a.length){var l=!0;g.$evalAsync(function(){l&&b(e,e,g)});return function(){l=!1}}if(1===a.length)return this.$watch(a[0],function(a,c,f){e[0]=a;d[0]=c;b(e,a===c?e:d,f)});q(a,function(a,b){var k=g.$watch(a,function(a,f){e[b]=a;d[b]=f;h||(h=!0,g.$evalAsync(c))});f.push(k)});return function(){for(;f.length;)f.shift()()}},
$watchCollection:function(a,b){function c(a){e=a;var b,d,g,h;if(!y(e)){if(D(e))if(ta(e))for(f!==n&&(f=n,u=f.length=0,l++),a=e.length,u!==a&&(l++,f.length=u=a),b=0;b<a;b++)h=f[b],g=e[b],d=h!==h&&g!==g,d||h===g||(l++,f[b]=g);else{f!==p&&(f=p={},u=0,l++);a=0;for(b in e)ua.call(e,b)&&(a++,g=e[b],h=f[b],b in f?(d=h!==h&&g!==g,d||h===g||(l++,f[b]=g)):(u++,f[b]=g,l++));if(u>a)for(b in l++,f)ua.call(e,b)||(u--,delete f[b])}else f!==e&&(f=e,l++);return l}}c.$stateful=!0;var d=this,e,f,h,k=1<b.length,l=0,m=
g(a,c),n=[],p={},r=!0,u=0;return this.$watch(m,function(){r?(r=!1,b(e,e,d)):b(e,h,d);if(k)if(D(e))if(ta(e)){h=Array(e.length);for(var a=0;a<e.length;a++)h[a]=e[a]}else for(a in h={},e)ua.call(e,a)&&(h[a]=e[a]);else h=e})},$digest:function(){var a,g,k,l,m,p,u,r,q=b,t,y=[],A,C;n("$digest");h.$$checkUrlChange();this===J&&null!==e&&(h.defer.cancel(e),B());c=null;do{r=!1;t=this;for(p=0;p<v.length;p++){try{C=v[p],C.scope.$eval(C.expression,C.locals)}catch(F){f(F)}c=null}v.length=0;a:do{if(p=t.$$watchers)for(u=
p.length;u--;)try{if(a=p[u])if(m=a.get,(g=m(t))!==(k=a.last)&&!(a.eq?na(g,k):"number"===typeof g&&"number"===typeof k&&isNaN(g)&&isNaN(k)))r=!0,c=a,a.last=a.eq?pa(g,null):g,l=a.fn,l(g,k===s?g:k,t),5>q&&(A=4-q,y[A]||(y[A]=[]),y[A].push({msg:z(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):a.exp,newVal:g,oldVal:k}));else if(a===c){r=!1;break a}}catch(G){f(G)}if(!(p=t.$$watchersCount&&t.$$childHead||t!==this&&t.$$nextSibling))for(;t!==this&&!(p=t.$$nextSibling);)t=t.$parent}while(t=p);if((r||v.length)&&
!q--)throw J.$$phase=null,d("infdig",b,y);}while(r||v.length);for(J.$$phase=null;K<w.length;)try{w[K++]()}catch(D){f(D)}w.length=K=0},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;this===J&&h.$$applicationDestroyed();p(this,-this.$$watchersCount);for(var b in this.$$listenerCount)u(this,this.$$listenerCount[b],b);a&&a.$$childHead==this&&(a.$$childHead=this.$$nextSibling);a&&a.$$childTail==this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&
(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling);this.$destroy=this.$digest=this.$apply=this.$evalAsync=this.$applyAsync=A;this.$on=this.$watch=this.$watchGroup=function(){return A};this.$$listeners={};this.$$nextSibling=null;l(this)}},$eval:function(a,b){return g(a)(this,b)},$evalAsync:function(a,b){J.$$phase||v.length||h.defer(function(){v.length&&J.$digest()});v.push({scope:this,expression:g(a),locals:b})},$$postDigest:function(a){w.push(a)},
$apply:function(a){try{n("$apply");try{return this.$eval(a)}finally{J.$$phase=null}}catch(b){f(b)}finally{try{J.$digest()}catch(c){throw f(c),c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&t.push(b);a=g(a);r()},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]||(d.$$listenerCount[a]=0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){var d=c.indexOf(b);-1!==d&&(c[d]=null,u(e,1,a))}},$emit:function(a,
b){var c=[],d,e=this,g=!1,h={name:a,targetScope:e,stopPropagation:function(){g=!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1},k=$a([h],arguments,1),l,m;do{d=e.$$listeners[a]||c;h.currentScope=e;l=0;for(m=d.length;l<m;l++)if(d[l])try{d[l].apply(null,k)}catch(n){f(n)}else d.splice(l,1),l--,m--;if(g)return h.currentScope=null,h;e=e.$parent}while(e);h.currentScope=null;return h},$broadcast:function(a,b){var c=this,d=this,e={name:a,targetScope:this,preventDefault:function(){e.defaultPrevented=
!0},defaultPrevented:!1};if(!this.$$listenerCount[a])return e;for(var g=$a([e],arguments,1),h,k;c=d;){e.currentScope=c;d=c.$$listeners[a]||[];h=0;for(k=d.length;h<k;h++)if(d[h])try{d[h].apply(null,g)}catch(l){f(l)}else d.splice(h,1),h--,k--;if(!(d=c.$$listenerCount[a]&&c.$$childHead||c!==this&&c.$$nextSibling))for(;c!==this&&!(d=c.$$nextSibling);)c=c.$parent}e.currentScope=null;return e}};var J=new m,v=J.$$asyncQueue=[],w=J.$$postDigestQueue=[],t=J.$$applyAsyncQueue=[],K=0;return J}]}function ne(){var a=
/^\s*(https?|ftp|mailto|tel|file):/,b=/^\s*((https?|ftp|file|blob):|data:image\/)/;this.aHrefSanitizationWhitelist=function(b){return w(b)?(a=b,this):a};this.imgSrcSanitizationWhitelist=function(a){return w(a)?(b=a,this):b};this.$get=function(){return function(d,c){var e=c?b:a,f;f=Y(d).href;return""===f||f.match(e)?d:"unsafe:"+f}}}function og(a){if("self"===a)return a;if(G(a)){if(-1<a.indexOf("***"))throw sa("iwcard",a);a=wd(a).replace("\\*\\*",".*").replace("\\*","[^:/.?&;]*");return new RegExp("^"+
a+"$")}if(Wa(a))return new RegExp("^"+a.source+"$");throw sa("imatcher");}function xd(a){var b=[];w(a)&&q(a,function(a){b.push(og(a))});return b}function zf(){this.SCE_CONTEXTS=la;var a=["self"],b=[];this.resourceUrlWhitelist=function(b){arguments.length&&(a=xd(b));return a};this.resourceUrlBlacklist=function(a){arguments.length&&(b=xd(a));return b};this.$get=["$injector",function(d){function c(a,b){return"self"===a?id(b):!!a.exec(b.href)}function e(a){var b=function(a){this.$$unwrapTrustedValue=
function(){return a}};a&&(b.prototype=new a);b.prototype.valueOf=function(){return this.$$unwrapTrustedValue()};b.prototype.toString=function(){return this.$$unwrapTrustedValue().toString()};return b}var f=function(a){throw sa("unsafe");};d.has("$sanitize")&&(f=d.get("$sanitize"));var g=e(),h={};h[la.HTML]=e(g);h[la.CSS]=e(g);h[la.URL]=e(g);h[la.JS]=e(g);h[la.RESOURCE_URL]=e(h[la.URL]);return{trustAs:function(a,b){var c=h.hasOwnProperty(a)?h[a]:null;if(!c)throw sa("icontext",a,b);if(null===b||y(b)||
""===b)return b;if("string"!==typeof b)throw sa("itype",a);return new c(b)},getTrusted:function(d,e){if(null===e||y(e)||""===e)return e;var g=h.hasOwnProperty(d)?h[d]:null;if(g&&e instanceof g)return e.$$unwrapTrustedValue();if(d===la.RESOURCE_URL){var g=Y(e.toString()),n,p,u=!1;n=0;for(p=a.length;n<p;n++)if(c(a[n],g)){u=!0;break}if(u)for(n=0,p=b.length;n<p;n++)if(c(b[n],g)){u=!1;break}if(u)return e;throw sa("insecurl",e.toString());}if(d===la.HTML)return f(e);throw sa("unsafe");},valueOf:function(a){return a instanceof
g?a.$$unwrapTrustedValue():a}}}]}function yf(){var a=!0;this.enabled=function(b){arguments.length&&(a=!!b);return a};this.$get=["$parse","$sceDelegate",function(b,d){if(a&&8>Ea)throw sa("iequirks");var c=ia(la);c.isEnabled=function(){return a};c.trustAs=d.trustAs;c.getTrusted=d.getTrusted;c.valueOf=d.valueOf;a||(c.trustAs=c.getTrusted=function(a,b){return b},c.valueOf=Xa);c.parseAs=function(a,d){var e=b(d);return e.literal&&e.constant?e:b(d,function(b){return c.getTrusted(a,b)})};var e=c.parseAs,
f=c.getTrusted,g=c.trustAs;q(la,function(a,b){var d=Q(b);c[db("parse_as_"+d)]=function(b){return e(a,b)};c[db("get_trusted_"+d)]=function(b){return f(a,b)};c[db("trust_as_"+d)]=function(b){return g(a,b)}});return c}]}function Af(){this.$get=["$window","$document",function(a,b){var d={},c=!(a.chrome&&a.chrome.app&&a.chrome.app.runtime)&&a.history&&a.history.pushState,e=Z((/android (\d+)/.exec(Q((a.navigator||{}).userAgent))||[])[1]),f=/Boxee/i.test((a.navigator||{}).userAgent),g=b[0]||{},h,k=/^(Moz|webkit|ms)(?=[A-Z])/,
l=g.body&&g.body.style,m=!1,n=!1;if(l){for(var p in l)if(m=k.exec(p)){h=m[0];h=h[0].toUpperCase()+h.substr(1);break}h||(h="WebkitOpacity"in l&&"webkit");m=!!("transition"in l||h+"Transition"in l);n=!!("animation"in l||h+"Animation"in l);!e||m&&n||(m=G(l.webkitTransition),n=G(l.webkitAnimation))}return{history:!(!c||4>e||f),hasEvent:function(a){if("input"===a&&11>=Ea)return!1;if(y(d[a])){var b=g.createElement("div");d[a]="on"+a in b}return d[a]},csp:Ba(),vendorPrefix:h,transitions:m,animations:n,android:e}}]}
function Cf(){var a;this.httpOptions=function(b){return b?(a=b,this):a};this.$get=["$templateCache","$http","$q","$sce",function(b,d,c,e){function f(g,h){f.totalPendingRequests++;if(!G(g)||y(b.get(g)))g=e.getTrustedResourceUrl(g);var k=d.defaults&&d.defaults.transformResponse;L(k)?k=k.filter(function(a){return a!==dc}):k===dc&&(k=null);return d.get(g,S({cache:b,transformResponse:k},a))["finally"](function(){f.totalPendingRequests--}).then(function(a){b.put(g,a.data);return a.data},function(a){if(!h)throw pg("tpload",
g,a.status,a.statusText);return c.reject(a)})}f.totalPendingRequests=0;return f}]}function Df(){this.$get=["$rootScope","$browser","$location",function(a,b,d){return{findBindings:function(a,b,d){a=a.getElementsByClassName("ng-binding");var g=[];q(a,function(a){var c=ca.element(a).data("$binding");c&&q(c,function(c){d?(new RegExp("(^|\\s)"+wd(b)+"(\\s|\\||$)")).test(c)&&g.push(a):-1!=c.indexOf(b)&&g.push(a)})});return g},findModels:function(a,b,d){for(var g=["ng-","data-ng-","ng\\:"],h=0;h<g.length;++h){var k=
a.querySelectorAll("["+g[h]+"model"+(d?"=":"*=")+'"'+b+'"]');if(k.length)return k}},getLocation:function(){return d.url()},setLocation:function(b){b!==d.url()&&(d.url(b),a.$digest())},whenStable:function(a){b.notifyWhenNoOutstandingRequests(a)}}}]}function Ef(){this.$get=["$rootScope","$browser","$q","$$q","$exceptionHandler",function(a,b,d,c,e){function f(f,k,l){z(f)||(l=k,k=f,f=A);var m=va.call(arguments,3),n=w(l)&&!l,p=(n?c:d).defer(),u=p.promise,q;q=b.defer(function(){try{p.resolve(f.apply(null,
m))}catch(b){p.reject(b),e(b)}finally{delete g[u.$$timeoutId]}n||a.$apply()},k);u.$$timeoutId=q;g[q]=p;return u}var g={};f.cancel=function(a){return a&&a.$$timeoutId in g?(g[a.$$timeoutId].reject("canceled"),delete g[a.$$timeoutId],b.defer.cancel(a.$$timeoutId)):!1};return f}]}function Y(a){Ea&&($.setAttribute("href",a),a=$.href);$.setAttribute("href",a);return{href:$.href,protocol:$.protocol?$.protocol.replace(/:$/,""):"",host:$.host,search:$.search?$.search.replace(/^\?/,""):"",hash:$.hash?$.hash.replace(/^#/,
""):"",hostname:$.hostname,port:$.port,pathname:"/"===$.pathname.charAt(0)?$.pathname:"/"+$.pathname}}function id(a){a=G(a)?Y(a):a;return a.protocol===yd.protocol&&a.host===yd.host}function Ff(){this.$get=ha(C)}function zd(a){function b(a){try{return decodeURIComponent(a)}catch(b){return a}}var d=a[0]||{},c={},e="";return function(){var a,g,h,k,l;a=d.cookie||"";if(a!==e)for(e=a,a=e.split("; "),c={},h=0;h<a.length;h++)g=a[h],k=g.indexOf("="),0<k&&(l=b(g.substring(0,k)),y(c[l])&&(c[l]=b(g.substring(k+
1))));return c}}function Jf(){this.$get=zd}function Mc(a){function b(d,c){if(D(d)){var e={};q(d,function(a,c){e[c]=b(c,a)});return e}return a.factory(d+"Filter",c)}this.register=b;this.$get=["$injector",function(a){return function(b){return a.get(b+"Filter")}}];b("currency",Ad);b("date",Bd);b("filter",qg);b("json",rg);b("limitTo",sg);b("lowercase",tg);b("number",Cd);b("orderBy",Dd);b("uppercase",ug)}function qg(){return function(a,b,d,c){if(!ta(a)){if(null==a)return a;throw N("filter")("notarray",
a);}c=c||"$";var e;switch(lc(b)){case "function":break;case "boolean":case "null":case "number":case "string":e=!0;case "object":b=vg(b,d,c,e);break;default:return a}return Array.prototype.filter.call(a,b)}}function vg(a,b,d,c){var e=D(a)&&d in a;!0===b?b=na:z(b)||(b=function(a,b){if(y(a))return!1;if(null===a||null===b)return a===b;if(D(b)||D(a)&&!vc(a))return!1;a=Q(""+a);b=Q(""+b);return-1!==a.indexOf(b)});return function(f){return e&&!D(f)?La(f,a[d],b,d,!1):La(f,a,b,d,c)}}function La(a,b,d,c,e,
f){var g=lc(a),h=lc(b);if("string"===h&&"!"===b.charAt(0))return!La(a,b.substring(1),d,c,e);if(L(a))return a.some(function(a){return La(a,b,d,c,e)});switch(g){case "object":var k;if(e){for(k in a)if("$"!==k.charAt(0)&&La(a[k],b,d,c,!0))return!0;return f?!1:La(a,b,d,c,!1)}if("object"===h){for(k in b)if(f=b[k],!z(f)&&!y(f)&&(g=k===c,!La(g?a:a[k],f,d,c,g,g)))return!1;return!0}return d(a,b);case "function":return!1;default:return d(a,b)}}function lc(a){return null===a?"null":typeof a}function Ad(a){var b=
a.NUMBER_FORMATS;return function(a,c,e){y(c)&&(c=b.CURRENCY_SYM);y(e)&&(e=b.PATTERNS[1].maxFrac);return null==a?a:Ed(a,b.PATTERNS[1],b.GROUP_SEP,b.DECIMAL_SEP,e).replace(/\u00A4/g,c)}}function Cd(a){var b=a.NUMBER_FORMATS;return function(a,c){return null==a?a:Ed(a,b.PATTERNS[0],b.GROUP_SEP,b.DECIMAL_SEP,c)}}function wg(a){var b=0,d,c,e,f,g;-1<(c=a.indexOf(Fd))&&(a=a.replace(Fd,""));0<(e=a.search(/e/i))?(0>c&&(c=e),c+=+a.slice(e+1),a=a.substring(0,e)):0>c&&(c=a.length);for(e=0;a.charAt(e)==mc;e++);
if(e==(g=a.length))d=[0],c=1;else{for(g--;a.charAt(g)==mc;)g--;c-=e;d=[];for(f=0;e<=g;e++,f++)d[f]=+a.charAt(e)}c>Gd&&(d=d.splice(0,Gd-1),b=c-1,c=1);return{d:d,e:b,i:c}}function xg(a,b,d,c){var e=a.d,f=e.length-a.i;b=y(b)?Math.min(Math.max(d,f),c):+b;d=b+a.i;c=e[d];if(0<d){e.splice(Math.max(a.i,d));for(var g=d;g<e.length;g++)e[g]=0}else for(f=Math.max(0,f),a.i=1,e.length=Math.max(1,d=b+1),e[0]=0,g=1;g<d;g++)e[g]=0;if(5<=c)if(0>d-1){for(c=0;c>d;c--)e.unshift(0),a.i++;e.unshift(1);a.i++}else e[d-1]++;
for(;f<Math.max(0,b);f++)e.push(0);if(b=e.reduceRight(function(a,b,c,d){b+=a;d[c]=b%10;return Math.floor(b/10)},0))e.unshift(b),a.i++}function Ed(a,b,d,c,e){if(!G(a)&&!T(a)||isNaN(a))return"";var f=!isFinite(a),g=!1,h=Math.abs(a)+"",k="";if(f)k="\u221e";else{g=wg(h);xg(g,e,b.minFrac,b.maxFrac);k=g.d;h=g.i;e=g.e;f=[];for(g=k.reduce(function(a,b){return a&&!b},!0);0>h;)k.unshift(0),h++;0<h?f=k.splice(h,k.length):(f=k,k=[0]);h=[];for(k.length>=b.lgSize&&h.unshift(k.splice(-b.lgSize,k.length).join(""));k.length>
b.gSize;)h.unshift(k.splice(-b.gSize,k.length).join(""));k.length&&h.unshift(k.join(""));k=h.join(d);f.length&&(k+=c+f.join(""));e&&(k+="e+"+e)}return 0>a&&!g?b.negPre+k+b.negSuf:b.posPre+k+b.posSuf}function Kb(a,b,d,c){var e="";if(0>a||c&&0>=a)c?a=-a+1:(a=-a,e="-");for(a=""+a;a.length<b;)a=mc+a;d&&(a=a.substr(a.length-b));return e+a}function ba(a,b,d,c,e){d=d||0;return function(f){f=f["get"+a]();if(0<d||f>-d)f+=d;0===f&&-12==d&&(f=12);return Kb(f,b,c,e)}}function kb(a,b,d){return function(c,e){var f=
c["get"+a](),g=ub((d?"STANDALONE":"")+(b?"SHORT":"")+a);return e[g][f]}}function Hd(a){var b=(new Date(a,0,1)).getDay();return new Date(a,0,(4>=b?5:12)-b)}function Id(a){return function(b){var d=Hd(b.getFullYear());b=+new Date(b.getFullYear(),b.getMonth(),b.getDate()+(4-b.getDay()))-+d;b=1+Math.round(b/6048E5);return Kb(b,a)}}function nc(a,b){return 0>=a.getFullYear()?b.ERAS[0]:b.ERAS[1]}function Bd(a){function b(a){var b;if(b=a.match(d)){a=new Date(0);var f=0,g=0,h=b[8]?a.setUTCFullYear:a.setFullYear,
k=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=Z(b[9]+b[10]),g=Z(b[9]+b[11]));h.call(a,Z(b[1]),Z(b[2])-1,Z(b[3]));f=Z(b[4]||0)-f;g=Z(b[5]||0)-g;h=Z(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));k.call(a,f,g,h,b)}return a}var d=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,d,f){var g="",h=[],k,l;d=d||"mediumDate";d=a.DATETIME_FORMATS[d]||d;G(c)&&(c=yg.test(c)?Z(c):b(c));T(c)&&(c=new Date(c));if(!da(c)||!isFinite(c.getTime()))return c;
for(;d;)(l=zg.exec(d))?(h=$a(h,l,1),d=h.pop()):(h.push(d),d=null);var m=c.getTimezoneOffset();f&&(m=yc(f,m),c=Sb(c,f,!0));q(h,function(b){k=Ag[b];g+=k?k(c,a.DATETIME_FORMATS,m):"''"===b?"'":b.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function rg(){return function(a,b){y(b)&&(b=2);return bb(a,b)}}function sg(){return function(a,b,d){b=Infinity===Math.abs(Number(b))?Number(b):Z(b);if(isNaN(b))return a;T(a)&&(a=a.toString());if(!ta(a))return a;d=!d||isNaN(d)?0:Z(d);d=0>d?Math.max(0,a.length+
d):d;return 0<=b?oc(a,d,d+b):0===d?oc(a,b,a.length):oc(a,Math.max(0,d+b),d)}}function oc(a,b,d){return G(a)?a.slice(b,d):va.call(a,b,d)}function Dd(a){function b(b){return b.map(function(b){var c=1,d=Xa;if(z(b))d=b;else if(G(b)){if("+"==b.charAt(0)||"-"==b.charAt(0))c="-"==b.charAt(0)?-1:1,b=b.substring(1);if(""!==b&&(d=a(b),d.constant))var e=d(),d=function(a){return a[e]}}return{get:d,descending:c}})}function d(a){switch(typeof a){case "number":case "boolean":case "string":return!0;default:return!1}}
function c(a,b){var c=0,d=a.type,k=b.type;if(d===k){var k=a.value,l=b.value;"string"===d?(k=k.toLowerCase(),l=l.toLowerCase()):"object"===d&&(D(k)&&(k=a.index),D(l)&&(l=b.index));k!==l&&(c=k<l?-1:1)}else c=d<k?-1:1;return c}return function(a,f,g,h){if(null==a)return a;if(!ta(a))throw N("orderBy")("notarray",a);L(f)||(f=[f]);0===f.length&&(f=["+"]);var k=b(f),l=g?-1:1,m=z(h)?h:c;a=Array.prototype.map.call(a,function(a,b){return{value:a,tieBreaker:{value:b,type:"number",index:b},predicateValues:k.map(function(c){var e=
c.get(a);c=typeof e;if(null===e)c="string",e="null";else if("object"===c)a:{if(z(e.valueOf)&&(e=e.valueOf(),d(e)))break a;vc(e)&&(e=e.toString(),d(e))}return{value:e,type:c,index:b}})}});a.sort(function(a,b){for(var c=0,d=k.length;c<d;c++){var e=m(a.predicateValues[c],b.predicateValues[c]);if(e)return e*k[c].descending*l}return m(a.tieBreaker,b.tieBreaker)*l});return a=a.map(function(a){return a.value})}}function Ta(a){z(a)&&(a={link:a});a.restrict=a.restrict||"AC";return ha(a)}function Jd(a,b,d,
c,e){var f=this,g=[];f.$error={};f.$$success={};f.$pending=void 0;f.$name=e(b.name||b.ngForm||"")(d);f.$dirty=!1;f.$pristine=!0;f.$valid=!0;f.$invalid=!1;f.$submitted=!1;f.$$parentForm=Lb;f.$rollbackViewValue=function(){q(g,function(a){a.$rollbackViewValue()})};f.$commitViewValue=function(){q(g,function(a){a.$commitViewValue()})};f.$addControl=function(a){Qa(a.$name,"input");g.push(a);a.$name&&(f[a.$name]=a);a.$$parentForm=f};f.$$renameControl=function(a,b){var c=a.$name;f[c]===a&&delete f[c];f[b]=
a;a.$name=b};f.$removeControl=function(a){a.$name&&f[a.$name]===a&&delete f[a.$name];q(f.$pending,function(b,c){f.$setValidity(c,null,a)});q(f.$error,function(b,c){f.$setValidity(c,null,a)});q(f.$$success,function(b,c){f.$setValidity(c,null,a)});Za(g,a);a.$$parentForm=Lb};Kd({ctrl:this,$element:a,set:function(a,b,c){var d=a[b];d?-1===d.indexOf(c)&&d.push(c):a[b]=[c]},unset:function(a,b,c){var d=a[b];d&&(Za(d,c),0===d.length&&delete a[b])},$animate:c});f.$setDirty=function(){c.removeClass(a,Ua);c.addClass(a,
Mb);f.$dirty=!0;f.$pristine=!1;f.$$parentForm.$setDirty()};f.$setPristine=function(){c.setClass(a,Ua,Mb+" ng-submitted");f.$dirty=!1;f.$pristine=!0;f.$submitted=!1;q(g,function(a){a.$setPristine()})};f.$setUntouched=function(){q(g,function(a){a.$setUntouched()})};f.$setSubmitted=function(){c.addClass(a,"ng-submitted");f.$submitted=!0;f.$$parentForm.$setSubmitted()}}function pc(a){a.$formatters.push(function(b){return a.$isEmpty(b)?b:b.toString()})}function lb(a,b,d,c,e,f){var g=Q(b[0].type);if(!e.android){var h=
!1;b.on("compositionstart",function(){h=!0});b.on("compositionend",function(){h=!1;l()})}var k,l=function(a){k&&(f.defer.cancel(k),k=null);if(!h){var e=b.val();a=a&&a.type;"password"===g||d.ngTrim&&"false"===d.ngTrim||(e=W(e));(c.$viewValue!==e||""===e&&c.$$hasNativeValidators)&&c.$setViewValue(e,a)}};if(e.hasEvent("input"))b.on("input",l);else{var m=function(a,b,c){k||(k=f.defer(function(){k=null;b&&b.value===c||l(a)}))};b.on("keydown",function(a){var b=a.keyCode;91===b||15<b&&19>b||37<=b&&40>=b||
m(a,this,this.value)});if(e.hasEvent("paste"))b.on("paste cut",m)}b.on("change",l);if(Ld[g]&&c.$$hasNativeValidators&&g===d.type)b.on("keydown wheel mousedown",function(a){if(!k){var b=this.validity,c=b.badInput,d=b.typeMismatch;k=f.defer(function(){k=null;b.badInput===c&&b.typeMismatch===d||l(a)})}});c.$render=function(){var a=c.$isEmpty(c.$viewValue)?"":c.$viewValue;b.val()!==a&&b.val(a)}}function Nb(a,b){return function(d,c){var e,f;if(da(d))return d;if(G(d)){'"'==d.charAt(0)&&'"'==d.charAt(d.length-
1)&&(d=d.substring(1,d.length-1));if(Bg.test(d))return new Date(d);a.lastIndex=0;if(e=a.exec(d))return e.shift(),f=c?{yyyy:c.getFullYear(),MM:c.getMonth()+1,dd:c.getDate(),HH:c.getHours(),mm:c.getMinutes(),ss:c.getSeconds(),sss:c.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0,sss:0},q(e,function(a,c){c<b.length&&(f[b[c]]=+a)}),new Date(f.yyyy,f.MM-1,f.dd,f.HH,f.mm,f.ss||0,1E3*f.sss||0)}return NaN}}function mb(a,b,d,c){return function(e,f,g,h,k,l,m){function n(a){return a&&!(a.getTime&&
a.getTime()!==a.getTime())}function p(a){return w(a)&&!da(a)?d(a)||void 0:a}Md(e,f,g,h);lb(e,f,g,h,k,l);var u=h&&h.$options&&h.$options.timezone,q;h.$$parserName=a;h.$parsers.push(function(a){if(h.$isEmpty(a))return null;if(b.test(a))return a=d(a,q),u&&(a=Sb(a,u)),a});h.$formatters.push(function(a){if(a&&!da(a))throw nb("datefmt",a);if(n(a))return(q=a)&&u&&(q=Sb(q,u,!0)),m("date")(a,c,u);q=null;return""});if(w(g.min)||g.ngMin){var s;h.$validators.min=function(a){return!n(a)||y(s)||d(a)>=s};g.$observe("min",
function(a){s=p(a);h.$validate()})}if(w(g.max)||g.ngMax){var r;h.$validators.max=function(a){return!n(a)||y(r)||d(a)<=r};g.$observe("max",function(a){r=p(a);h.$validate()})}}}function Md(a,b,d,c){(c.$$hasNativeValidators=D(b[0].validity))&&c.$parsers.push(function(a){var c=b.prop("validity")||{};return c.badInput||c.typeMismatch?void 0:a})}function Nd(a,b,d,c,e){if(w(c)){a=a(c);if(!a.constant)throw nb("constexpr",d,c);return a(b)}return e}function qc(a,b){a="ngClass"+a;return["$animate",function(d){function c(a,
b){var c=[],d=0;a:for(;d<a.length;d++){for(var e=a[d],m=0;m<b.length;m++)if(e==b[m])continue a;c.push(e)}return c}function e(a){var b=[];return L(a)?(q(a,function(a){b=b.concat(e(a))}),b):G(a)?a.split(" "):D(a)?(q(a,function(a,c){a&&(b=b.concat(c.split(" ")))}),b):a}return{restrict:"AC",link:function(f,g,h){function k(a){a=l(a,1);h.$addClass(a)}function l(a,b){var c=g.data("$classCounts")||U(),d=[];q(a,function(a){if(0<b||c[a])c[a]=(c[a]||0)+b,c[a]===+(0<b)&&d.push(a)});g.data("$classCounts",c);return d.join(" ")}
function m(a,b){var e=c(b,a),f=c(a,b),e=l(e,1),f=l(f,-1);e&&e.length&&d.addClass(g,e);f&&f.length&&d.removeClass(g,f)}function n(a){if(!0===b||(f.$index&1)===b){var c=e(a||[]);if(!p)k(c);else if(!na(a,p)){var d=e(p);m(d,c)}}p=L(a)?a.map(function(a){return ia(a)}):ia(a)}var p;f.$watch(h[a],n,!0);h.$observe("class",function(b){n(f.$eval(h[a]))});"ngClass"!==a&&f.$watch("$index",function(c,d){var g=c&1;if(g!==(d&1)){var m=e(f.$eval(h[a]));g===b?k(m):(g=l(m,-1),h.$removeClass(g))}})}}}]}function Kd(a){function b(a,
b){b&&!f[a]?(k.addClass(e,a),f[a]=!0):!b&&f[a]&&(k.removeClass(e,a),f[a]=!1)}function d(a,c){a=a?"-"+Cc(a,"-"):"";b(ob+a,!0===c);b(Od+a,!1===c)}var c=a.ctrl,e=a.$element,f={},g=a.set,h=a.unset,k=a.$animate;f[Od]=!(f[ob]=e.hasClass(ob));c.$setValidity=function(a,e,f){y(e)?(c.$pending||(c.$pending={}),g(c.$pending,a,f)):(c.$pending&&h(c.$pending,a,f),Pd(c.$pending)&&(c.$pending=void 0));Ga(e)?e?(h(c.$error,a,f),g(c.$$success,a,f)):(g(c.$error,a,f),h(c.$$success,a,f)):(h(c.$error,a,f),h(c.$$success,
a,f));c.$pending?(b(Qd,!0),c.$valid=c.$invalid=void 0,d("",null)):(b(Qd,!1),c.$valid=Pd(c.$error),c.$invalid=!c.$valid,d("",c.$valid));e=c.$pending&&c.$pending[a]?void 0:c.$error[a]?!1:c.$$success[a]?!0:null;d(a,e);c.$$parentForm.$setValidity(a,e,c)}}function Pd(a){if(a)for(var b in a)if(a.hasOwnProperty(b))return!1;return!0}var Cg=/^\/(.+)\/([a-z]*)$/,ua=Object.prototype.hasOwnProperty,Q=function(a){return G(a)?a.toLowerCase():a},ub=function(a){return G(a)?a.toUpperCase():a},Ea,F,qa,va=[].slice,
bg=[].splice,Dg=[].push,ma=Object.prototype.toString,wc=Object.getPrototypeOf,xa=N("ng"),ca=C.angular||(C.angular={}),Ub,pb=0;Ea=C.document.documentMode;A.$inject=[];Xa.$inject=[];var L=Array.isArray,ae=/^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/,W=function(a){return G(a)?a.trim():a},wd=function(a){return a.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08")},Ba=function(){if(!w(Ba.rules)){var a=C.document.querySelector("[ng-csp]")||
C.document.querySelector("[data-ng-csp]");if(a){var b=a.getAttribute("ng-csp")||a.getAttribute("data-ng-csp");Ba.rules={noUnsafeEval:!b||-1!==b.indexOf("no-unsafe-eval"),noInlineStyle:!b||-1!==b.indexOf("no-inline-style")}}else{a=Ba;try{new Function(""),b=!1}catch(d){b=!0}a.rules={noUnsafeEval:b,noInlineStyle:!1}}}return Ba.rules},rb=function(){if(w(rb.name_))return rb.name_;var a,b,d=Na.length,c,e;for(b=0;b<d;++b)if(c=Na[b],a=C.document.querySelector("["+c.replace(":","\\:")+"jq]")){e=a.getAttribute(c+
"jq");break}return rb.name_=e},de=/:/g,Na=["ng-","data-ng-","ng:","x-ng-"],ie=/[A-Z]/g,Dc=!1,Ma=3,me={full:"1.5.8",major:1,minor:5,dot:8,codeName:"arbitrary-fallbacks"};O.expando="ng339";var fb=O.cache={},Pf=1;O._data=function(a){return this.cache[a[this.expando]]||{}};var Kf=/([\:\-\_]+(.))/g,Lf=/^moz([A-Z])/,yb={mouseleave:"mouseout",mouseenter:"mouseover"},Wb=N("jqLite"),Of=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,Vb=/<|&#?\w+;/,Mf=/<([\w:-]+)/,Nf=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,
ja={option:[1,'<select multiple="multiple">',"</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ja.optgroup=ja.option;ja.tbody=ja.tfoot=ja.colgroup=ja.caption=ja.thead;ja.th=ja.td;var Uf=C.Node.prototype.contains||function(a){return!!(this.compareDocumentPosition(a)&16)},Oa=O.prototype={ready:function(a){function b(){d||(d=!0,a())}var d=!1;"complete"===
C.document.readyState?C.setTimeout(b):(this.on("DOMContentLoaded",b),O(C).on("load",b))},toString:function(){var a=[];q(this,function(b){a.push(""+b)});return"["+a.join(", ")+"]"},eq:function(a){return 0<=a?F(this[a]):F(this[this.length+a])},length:0,push:Dg,sort:[].sort,splice:[].splice},Eb={};q("multiple selected checked disabled readOnly required open".split(" "),function(a){Eb[Q(a)]=a});var Vc={};q("input select option textarea button form details".split(" "),function(a){Vc[a]=!0});var bd={ngMinlength:"minlength",
ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern"};q({data:Yb,removeData:eb,hasData:function(a){for(var b in fb[a.ng339])return!0;return!1},cleanData:function(a){for(var b=0,d=a.length;b<d;b++)eb(a[b])}},function(a,b){O[b]=a});q({data:Yb,inheritedData:Cb,scope:function(a){return F.data(a,"$scope")||Cb(a.parentNode||a,["$isolateScope","$scope"])},isolateScope:function(a){return F.data(a,"$isolateScope")||F.data(a,"$isolateScopeNoTemplate")},controller:Sc,injector:function(a){return Cb(a,
"$injector")},removeAttr:function(a,b){a.removeAttribute(b)},hasClass:zb,css:function(a,b,d){b=db(b);if(w(d))a.style[b]=d;else return a.style[b]},attr:function(a,b,d){var c=a.nodeType;if(c!==Ma&&2!==c&&8!==c)if(c=Q(b),Eb[c])if(w(d))d?(a[b]=!0,a.setAttribute(b,c)):(a[b]=!1,a.removeAttribute(c));else return a[b]||(a.attributes.getNamedItem(b)||A).specified?c:void 0;else if(w(d))a.setAttribute(b,d);else if(a.getAttribute)return a=a.getAttribute(b,2),null===a?void 0:a},prop:function(a,b,d){if(w(d))a[b]=
d;else return a[b]},text:function(){function a(a,d){if(y(d)){var c=a.nodeType;return 1===c||c===Ma?a.textContent:""}a.textContent=d}a.$dv="";return a}(),val:function(a,b){if(y(b)){if(a.multiple&&"select"===wa(a)){var d=[];q(a.options,function(a){a.selected&&d.push(a.value||a.text)});return 0===d.length?null:d}return a.value}a.value=b},html:function(a,b){if(y(b))return a.innerHTML;wb(a,!0);a.innerHTML=b},empty:Tc},function(a,b){O.prototype[b]=function(b,c){var e,f,g=this.length;if(a!==Tc&&y(2==a.length&&
a!==zb&&a!==Sc?b:c)){if(D(b)){for(e=0;e<g;e++)if(a===Yb)a(this[e],b);else for(f in b)a(this[e],f,b[f]);return this}e=a.$dv;g=y(e)?Math.min(g,1):g;for(f=0;f<g;f++){var h=a(this[f],b,c);e=e?e+h:h}return e}for(e=0;e<g;e++)a(this[e],b,c);return this}});q({removeData:eb,on:function(a,b,d,c){if(w(c))throw Wb("onargs");if(Nc(a)){c=xb(a,!0);var e=c.events,f=c.handle;f||(f=c.handle=Rf(a,e));c=0<=b.indexOf(" ")?b.split(" "):[b];for(var g=c.length,h=function(b,c,g){var h=e[b];h||(h=e[b]=[],h.specialHandlerWrapper=
c,"$destroy"===b||g||a.addEventListener(b,f,!1));h.push(d)};g--;)b=c[g],yb[b]?(h(yb[b],Tf),h(b,void 0,!0)):h(b)}},off:Rc,one:function(a,b,d){a=F(a);a.on(b,function e(){a.off(b,d);a.off(b,e)});a.on(b,d)},replaceWith:function(a,b){var d,c=a.parentNode;wb(a);q(new O(b),function(b){d?c.insertBefore(b,d.nextSibling):c.replaceChild(b,a);d=b})},children:function(a){var b=[];q(a.childNodes,function(a){1===a.nodeType&&b.push(a)});return b},contents:function(a){return a.contentDocument||a.childNodes||[]},append:function(a,
b){var d=a.nodeType;if(1===d||11===d){b=new O(b);for(var d=0,c=b.length;d<c;d++)a.appendChild(b[d])}},prepend:function(a,b){if(1===a.nodeType){var d=a.firstChild;q(new O(b),function(b){a.insertBefore(b,d)})}},wrap:function(a,b){Pc(a,F(b).eq(0).clone()[0])},remove:Db,detach:function(a){Db(a,!0)},after:function(a,b){var d=a,c=a.parentNode;b=new O(b);for(var e=0,f=b.length;e<f;e++){var g=b[e];c.insertBefore(g,d.nextSibling);d=g}},addClass:Bb,removeClass:Ab,toggleClass:function(a,b,d){b&&q(b.split(" "),
function(b){var e=d;y(e)&&(e=!zb(a,b));(e?Bb:Ab)(a,b)})},parent:function(a){return(a=a.parentNode)&&11!==a.nodeType?a:null},next:function(a){return a.nextElementSibling},find:function(a,b){return a.getElementsByTagName?a.getElementsByTagName(b):[]},clone:Xb,triggerHandler:function(a,b,d){var c,e,f=b.type||b,g=xb(a);if(g=(g=g&&g.events)&&g[f])c={preventDefault:function(){this.defaultPrevented=!0},isDefaultPrevented:function(){return!0===this.defaultPrevented},stopImmediatePropagation:function(){this.immediatePropagationStopped=
!0},isImmediatePropagationStopped:function(){return!0===this.immediatePropagationStopped},stopPropagation:A,type:f,target:a},b.type&&(c=S(c,b)),b=ia(g),e=d?[c].concat(d):[c],q(b,function(b){c.isImmediatePropagationStopped()||b.apply(a,e)})}},function(a,b){O.prototype[b]=function(b,c,e){for(var f,g=0,h=this.length;g<h;g++)y(f)?(f=a(this[g],b,c,e),w(f)&&(f=F(f))):Qc(f,a(this[g],b,c,e));return w(f)?f:this};O.prototype.bind=O.prototype.on;O.prototype.unbind=O.prototype.off});Ra.prototype={put:function(a,
b){this[Ca(a,this.nextUid)]=b},get:function(a){return this[Ca(a,this.nextUid)]},remove:function(a){var b=this[a=Ca(a,this.nextUid)];delete this[a];return b}};var If=[function(){this.$get=[function(){return Ra}]}],Wf=/^([^\(]+?)=>/,Xf=/^[^\(]*\(\s*([^\)]*)\)/m,Eg=/,/,Fg=/^\s*(_?)(\S+?)\1\s*$/,Vf=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Ha=N("$injector");cb.$$annotate=function(a,b,d){var c;if("function"===typeof a){if(!(c=a.$inject)){c=[];if(a.length){if(b)throw G(d)&&d||(d=a.name||Yf(a)),Ha("strictdi",d);
b=Wc(a);q(b[1].split(Eg),function(a){a.replace(Fg,function(a,b,d){c.push(d)})})}a.$inject=c}}else L(a)?(b=a.length-1,Pa(a[b],"fn"),c=a.slice(0,b)):Pa(a,"fn",!0);return c};var Rd=N("$animate"),$e=function(){this.$get=A},af=function(){var a=new Ra,b=[];this.$get=["$$AnimateRunner","$rootScope",function(d,c){function e(a,b,c){var d=!1;b&&(b=G(b)?b.split(" "):L(b)?b:[],q(b,function(b){b&&(d=!0,a[b]=c)}));return d}function f(){q(b,function(b){var c=a.get(b);if(c){var d=Zf(b.attr("class")),e="",f="";q(c,
function(a,b){a!==!!d[b]&&(a?e+=(e.length?" ":"")+b:f+=(f.length?" ":"")+b)});q(b,function(a){e&&Bb(a,e);f&&Ab(a,f)});a.remove(b)}});b.length=0}return{enabled:A,on:A,off:A,pin:A,push:function(g,h,k,l){l&&l();k=k||{};k.from&&g.css(k.from);k.to&&g.css(k.to);if(k.addClass||k.removeClass)if(h=k.addClass,l=k.removeClass,k=a.get(g)||{},h=e(k,h,!0),l=e(k,l,!1),h||l)a.put(g,k),b.push(g),1===b.length&&c.$$postDigest(f);g=new d;g.complete();return g}}}]},Ye=["$provide",function(a){var b=this;this.$$registeredAnimations=
Object.create(null);this.register=function(d,c){if(d&&"."!==d.charAt(0))throw Rd("notcsel",d);var e=d+"-animation";b.$$registeredAnimations[d.substr(1)]=e;a.factory(e,c)};this.classNameFilter=function(a){if(1===arguments.length&&(this.$$classNameFilter=a instanceof RegExp?a:null)&&/(\s+|\/)ng-animate(\s+|\/)/.test(this.$$classNameFilter.toString()))throw Rd("nongcls","ng-animate");return this.$$classNameFilter};this.$get=["$$animateQueue",function(a){function b(a,c,d){if(d){var h;a:{for(h=0;h<d.length;h++){var k=
d[h];if(1===k.nodeType){h=k;break a}}h=void 0}!h||h.parentNode||h.previousElementSibling||(d=null)}d?d.after(a):c.prepend(a)}return{on:a.on,off:a.off,pin:a.pin,enabled:a.enabled,cancel:function(a){a.end&&a.end()},enter:function(e,f,g,h){f=f&&F(f);g=g&&F(g);f=f||g.parent();b(e,f,g);return a.push(e,"enter",Ia(h))},move:function(e,f,g,h){f=f&&F(f);g=g&&F(g);f=f||g.parent();b(e,f,g);return a.push(e,"move",Ia(h))},leave:function(b,c){return a.push(b,"leave",Ia(c),function(){b.remove()})},addClass:function(b,
c,g){g=Ia(g);g.addClass=gb(g.addclass,c);return a.push(b,"addClass",g)},removeClass:function(b,c,g){g=Ia(g);g.removeClass=gb(g.removeClass,c);return a.push(b,"removeClass",g)},setClass:function(b,c,g,h){h=Ia(h);h.addClass=gb(h.addClass,c);h.removeClass=gb(h.removeClass,g);return a.push(b,"setClass",h)},animate:function(b,c,g,h,k){k=Ia(k);k.from=k.from?S(k.from,c):c;k.to=k.to?S(k.to,g):g;k.tempClasses=gb(k.tempClasses,h||"ng-inline-animate");return a.push(b,"animate",k)}}}]}],cf=function(){this.$get=
["$$rAF",function(a){function b(b){d.push(b);1<d.length||a(function(){for(var a=0;a<d.length;a++)d[a]();d=[]})}var d=[];return function(){var a=!1;b(function(){a=!0});return function(d){a?d():b(d)}}}]},bf=function(){this.$get=["$q","$sniffer","$$animateAsyncRun","$document","$timeout",function(a,b,d,c,e){function f(a){this.setHost(a);var b=d();this._doneCallbacks=[];this._tick=function(a){var d=c[0];d&&d.hidden?e(a,0,!1):b(a)};this._state=0}f.chain=function(a,b){function c(){if(d===a.length)b(!0);
else a[d](function(a){!1===a?b(!1):(d++,c())})}var d=0;c()};f.all=function(a,b){function c(f){e=e&&f;++d===a.length&&b(e)}var d=0,e=!0;q(a,function(a){a.done(c)})};f.prototype={setHost:function(a){this.host=a||{}},done:function(a){2===this._state?a():this._doneCallbacks.push(a)},progress:A,getPromise:function(){if(!this.promise){var b=this;this.promise=a(function(a,c){b.done(function(b){!1===b?c():a()})})}return this.promise},then:function(a,b){return this.getPromise().then(a,b)},"catch":function(a){return this.getPromise()["catch"](a)},
"finally":function(a){return this.getPromise()["finally"](a)},pause:function(){this.host.pause&&this.host.pause()},resume:function(){this.host.resume&&this.host.resume()},end:function(){this.host.end&&this.host.end();this._resolve(!0)},cancel:function(){this.host.cancel&&this.host.cancel();this._resolve(!1)},complete:function(a){var b=this;0===b._state&&(b._state=1,b._tick(function(){b._resolve(a)}))},_resolve:function(a){2!==this._state&&(q(this._doneCallbacks,function(b){b(a)}),this._doneCallbacks.length=
0,this._state=2)}};return f}]},Ze=function(){this.$get=["$$rAF","$q","$$AnimateRunner",function(a,b,d){return function(b,e){function f(){a(function(){g.addClass&&(b.addClass(g.addClass),g.addClass=null);g.removeClass&&(b.removeClass(g.removeClass),g.removeClass=null);g.to&&(b.css(g.to),g.to=null);h||k.complete();h=!0});return k}var g=e||{};g.$$prepared||(g=pa(g));g.cleanupStyles&&(g.from=g.to=null);g.from&&(b.css(g.from),g.from=null);var h,k=new d;return{start:f,end:f}}}]},ga=N("$compile"),bc=new function(){};
Fc.$inject=["$provide","$$sanitizeUriProvider"];Fb.prototype.isFirstChange=function(){return this.previousValue===bc};var Yc=/^((?:x|data)[\:\-_])/i,cg=N("$controller"),cd=/^(\S+)(\s+as\s+([\w$]+))?$/,jf=function(){this.$get=["$document",function(a){return function(b){b?!b.nodeType&&b instanceof F&&(b=b[0]):b=a[0].body;return b.offsetWidth+1}}]},dd="application/json",ec={"Content-Type":dd+";charset=utf-8"},eg=/^\[|^\{(?!\{)/,fg={"[":/]$/,"{":/}$/},dg=/^\)\]\}',?\n/,Gg=N("$http"),hd=function(a){return function(){throw Gg("legacy",
a);}},Ka=ca.$interpolateMinErr=N("$interpolate");Ka.throwNoconcat=function(a){throw Ka("noconcat",a);};Ka.interr=function(a,b){return Ka("interr",a,b.toString())};var rf=function(){this.$get=["$window",function(a){function b(a){var b=function(a){b.data=a;b.called=!0};b.id=a;return b}var d=a.angular.callbacks,c={};return{createCallback:function(a){a="_"+(d.$$counter++).toString(36);var f="angular.callbacks."+a,g=b(a);c[f]=d[a]=g;return f},wasCalled:function(a){return c[a].called},getResponse:function(a){return c[a].data},
removeCallback:function(a){delete d[c[a].id];delete c[a]}}}]},Hg=/^([^\?#]*)(\?([^#]*))?(#(.*))?$/,hg={http:80,https:443,ftp:21},Gb=N("$location"),Ig={$$absUrl:"",$$html5:!1,$$replace:!1,absUrl:Hb("$$absUrl"),url:function(a){if(y(a))return this.$$url;var b=Hg.exec(a);(b[1]||""===a)&&this.path(decodeURIComponent(b[1]));(b[2]||b[1]||""===a)&&this.search(b[3]||"");this.hash(b[5]||"");return this},protocol:Hb("$$protocol"),host:Hb("$$host"),port:Hb("$$port"),path:md("$$path",function(a){a=null!==a?a.toString():
"";return"/"==a.charAt(0)?a:"/"+a}),search:function(a,b){switch(arguments.length){case 0:return this.$$search;case 1:if(G(a)||T(a))a=a.toString(),this.$$search=Ac(a);else if(D(a))a=pa(a,{}),q(a,function(b,c){null==b&&delete a[c]}),this.$$search=a;else throw Gb("isrcharg");break;default:y(b)||null===b?delete this.$$search[a]:this.$$search[a]=b}this.$$compose();return this},hash:md("$$hash",function(a){return null!==a?a.toString():""}),replace:function(){this.$$replace=!0;return this}};q([ld,hc,gc],
function(a){a.prototype=Object.create(Ig);a.prototype.state=function(b){if(!arguments.length)return this.$$state;if(a!==gc||!this.$$html5)throw Gb("nostate");this.$$state=y(b)?null:b;return this}});var X=N("$parse"),jg=Function.prototype.call,kg=Function.prototype.apply,lg=Function.prototype.bind,Ob=U();q("+ - * / % === !== == != < > <= >= && || ! = |".split(" "),function(a){Ob[a]=!0});var Jg={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},jc=function(a){this.options=a};jc.prototype={constructor:jc,
lex:function(a){this.text=a;this.index=0;for(this.tokens=[];this.index<this.text.length;)if(a=this.text.charAt(this.index),'"'===a||"'"===a)this.readString(a);else if(this.isNumber(a)||"."===a&&this.isNumber(this.peek()))this.readNumber();else if(this.isIdentifierStart(this.peekMultichar()))this.readIdent();else if(this.is(a,"(){}[].,;:?"))this.tokens.push({index:this.index,text:a}),this.index++;else if(this.isWhitespace(a))this.index++;else{var b=a+this.peek(),d=b+this.peek(2),c=Ob[b],e=Ob[d];Ob[a]||
c||e?(a=e?d:c?b:a,this.tokens.push({index:this.index,text:a,operator:!0}),this.index+=a.length):this.throwError("Unexpected next character ",this.index,this.index+1)}return this.tokens},is:function(a,b){return-1!==b.indexOf(a)},peek:function(a){a=a||1;return this.index+a<this.text.length?this.text.charAt(this.index+a):!1},isNumber:function(a){return"0"<=a&&"9">=a&&"string"===typeof a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdentifierStart:function(a){return this.options.isIdentifierStart?
this.options.isIdentifierStart(a,this.codePointAt(a)):this.isValidIdentifierStart(a)},isValidIdentifierStart:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isIdentifierContinue:function(a){return this.options.isIdentifierContinue?this.options.isIdentifierContinue(a,this.codePointAt(a)):this.isValidIdentifierContinue(a)},isValidIdentifierContinue:function(a,b){return this.isValidIdentifierStart(a,b)||this.isNumber(a)},codePointAt:function(a){return 1===a.length?a.charCodeAt(0):
(a.charCodeAt(0)<<10)+a.charCodeAt(1)-56613888},peekMultichar:function(){var a=this.text.charAt(this.index),b=this.peek();if(!b)return a;var d=a.charCodeAt(0),c=b.charCodeAt(0);return 55296<=d&&56319>=d&&56320<=c&&57343>=c?a+b:a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,b,d){d=d||this.index;b=w(b)?"s "+b+"-"+this.index+" ["+this.text.substring(b,d)+"]":" "+d;throw X("lexerr",a,b,this.text);},readNumber:function(){for(var a="",b=this.index;this.index<
this.text.length;){var d=Q(this.text.charAt(this.index));if("."==d||this.isNumber(d))a+=d;else{var c=this.peek();if("e"==d&&this.isExpOperator(c))a+=d;else if(this.isExpOperator(d)&&c&&this.isNumber(c)&&"e"==a.charAt(a.length-1))a+=d;else if(!this.isExpOperator(d)||c&&this.isNumber(c)||"e"!=a.charAt(a.length-1))break;else this.throwError("Invalid exponent")}this.index++}this.tokens.push({index:b,text:a,constant:!0,value:Number(a)})},readIdent:function(){var a=this.index;for(this.index+=this.peekMultichar().length;this.index<
this.text.length;){var b=this.peekMultichar();if(!this.isIdentifierContinue(b))break;this.index+=b.length}this.tokens.push({index:a,text:this.text.slice(a,this.index),identifier:!0})},readString:function(a){var b=this.index;this.index++;for(var d="",c=a,e=!1;this.index<this.text.length;){var f=this.text.charAt(this.index),c=c+f;if(e)"u"===f?(e=this.text.substring(this.index+1,this.index+5),e.match(/[\da-f]{4}/i)||this.throwError("Invalid unicode escape [\\u"+e+"]"),this.index+=4,d+=String.fromCharCode(parseInt(e,
16))):d+=Jg[f]||f,e=!1;else if("\\"===f)e=!0;else{if(f===a){this.index++;this.tokens.push({index:b,text:c,constant:!0,value:d});return}d+=f}this.index++}this.throwError("Unterminated quote",b)}};var s=function(a,b){this.lexer=a;this.options=b};s.Program="Program";s.ExpressionStatement="ExpressionStatement";s.AssignmentExpression="AssignmentExpression";s.ConditionalExpression="ConditionalExpression";s.LogicalExpression="LogicalExpression";s.BinaryExpression="BinaryExpression";s.UnaryExpression="UnaryExpression";
s.CallExpression="CallExpression";s.MemberExpression="MemberExpression";s.Identifier="Identifier";s.Literal="Literal";s.ArrayExpression="ArrayExpression";s.Property="Property";s.ObjectExpression="ObjectExpression";s.ThisExpression="ThisExpression";s.LocalsExpression="LocalsExpression";s.NGValueParameter="NGValueParameter";s.prototype={ast:function(a){this.text=a;this.tokens=this.lexer.lex(a);a=this.program();0!==this.tokens.length&&this.throwError("is an unexpected token",this.tokens[0]);return a},
program:function(){for(var a=[];;)if(0<this.tokens.length&&!this.peek("}",")",";","]")&&a.push(this.expressionStatement()),!this.expect(";"))return{type:s.Program,body:a}},expressionStatement:function(){return{type:s.ExpressionStatement,expression:this.filterChain()}},filterChain:function(){for(var a=this.expression();this.expect("|");)a=this.filter(a);return a},expression:function(){return this.assignment()},assignment:function(){var a=this.ternary();this.expect("=")&&(a={type:s.AssignmentExpression,
left:a,right:this.assignment(),operator:"="});return a},ternary:function(){var a=this.logicalOR(),b,d;return this.expect("?")&&(b=this.expression(),this.consume(":"))?(d=this.expression(),{type:s.ConditionalExpression,test:a,alternate:b,consequent:d}):a},logicalOR:function(){for(var a=this.logicalAND();this.expect("||");)a={type:s.LogicalExpression,operator:"||",left:a,right:this.logicalAND()};return a},logicalAND:function(){for(var a=this.equality();this.expect("&&");)a={type:s.LogicalExpression,
operator:"&&",left:a,right:this.equality()};return a},equality:function(){for(var a=this.relational(),b;b=this.expect("==","!=","===","!==");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.relational()};return a},relational:function(){for(var a=this.additive(),b;b=this.expect("<",">","<=",">=");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.additive()};return a},additive:function(){for(var a=this.multiplicative(),b;b=this.expect("+","-");)a={type:s.BinaryExpression,operator:b.text,
left:a,right:this.multiplicative()};return a},multiplicative:function(){for(var a=this.unary(),b;b=this.expect("*","/","%");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.unary()};return a},unary:function(){var a;return(a=this.expect("+","-","!"))?{type:s.UnaryExpression,operator:a.text,prefix:!0,argument:this.unary()}:this.primary()},primary:function(){var a;this.expect("(")?(a=this.filterChain(),this.consume(")")):this.expect("[")?a=this.arrayDeclaration():this.expect("{")?a=this.object():
this.selfReferential.hasOwnProperty(this.peek().text)?a=pa(this.selfReferential[this.consume().text]):this.options.literals.hasOwnProperty(this.peek().text)?a={type:s.Literal,value:this.options.literals[this.consume().text]}:this.peek().identifier?a=this.identifier():this.peek().constant?a=this.constant():this.throwError("not a primary expression",this.peek());for(var b;b=this.expect("(","[",".");)"("===b.text?(a={type:s.CallExpression,callee:a,arguments:this.parseArguments()},this.consume(")")):
"["===b.text?(a={type:s.MemberExpression,object:a,property:this.expression(),computed:!0},this.consume("]")):"."===b.text?a={type:s.MemberExpression,object:a,property:this.identifier(),computed:!1}:this.throwError("IMPOSSIBLE");return a},filter:function(a){a=[a];for(var b={type:s.CallExpression,callee:this.identifier(),arguments:a,filter:!0};this.expect(":");)a.push(this.expression());return b},parseArguments:function(){var a=[];if(")"!==this.peekToken().text){do a.push(this.filterChain());while(this.expect(","))
}return a},identifier:function(){var a=this.consume();a.identifier||this.throwError("is not a valid identifier",a);return{type:s.Identifier,name:a.text}},constant:function(){return{type:s.Literal,value:this.consume().value}},arrayDeclaration:function(){var a=[];if("]"!==this.peekToken().text){do{if(this.peek("]"))break;a.push(this.expression())}while(this.expect(","))}this.consume("]");return{type:s.ArrayExpression,elements:a}},object:function(){var a=[],b;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;
b={type:s.Property,kind:"init"};this.peek().constant?(b.key=this.constant(),b.computed=!1,this.consume(":"),b.value=this.expression()):this.peek().identifier?(b.key=this.identifier(),b.computed=!1,this.peek(":")?(this.consume(":"),b.value=this.expression()):b.value=b.key):this.peek("[")?(this.consume("["),b.key=this.expression(),this.consume("]"),b.computed=!0,this.consume(":"),b.value=this.expression()):this.throwError("invalid key",this.peek());a.push(b)}while(this.expect(","))}this.consume("}");
return{type:s.ObjectExpression,properties:a}},throwError:function(a,b){throw X("syntax",b.text,a,b.index+1,this.text,this.text.substring(b.index));},consume:function(a){if(0===this.tokens.length)throw X("ueoe",this.text);var b=this.expect(a);b||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return b},peekToken:function(){if(0===this.tokens.length)throw X("ueoe",this.text);return this.tokens[0]},peek:function(a,b,d,c){return this.peekAhead(0,a,b,d,c)},peekAhead:function(a,b,d,c,e){if(this.tokens.length>
a){a=this.tokens[a];var f=a.text;if(f===b||f===d||f===c||f===e||!(b||d||c||e))return a}return!1},expect:function(a,b,d,c){return(a=this.peek(a,b,d,c))?(this.tokens.shift(),a):!1},selfReferential:{"this":{type:s.ThisExpression},$locals:{type:s.LocalsExpression}}};td.prototype={compile:function(a,b){var d=this,c=this.astBuilder.ast(a);this.state={nextId:0,filters:{},expensiveChecks:b,fn:{vars:[],body:[],own:{}},assign:{vars:[],body:[],own:{}},inputs:[]};V(c,d.$filter);var e="",f;this.stage="assign";
if(f=rd(c))this.state.computing="assign",e=this.nextId(),this.recurse(f,e),this.return_(e),e="fn.assign="+this.generateFunction("assign","s,v,l");f=pd(c.body);d.stage="inputs";q(f,function(a,b){var c="fn"+b;d.state[c]={vars:[],body:[],own:{}};d.state.computing=c;var e=d.nextId();d.recurse(a,e);d.return_(e);d.state.inputs.push(c);a.watchId=b});this.state.computing="fn";this.stage="main";this.recurse(c);e='"'+this.USE+" "+this.STRICT+'";\n'+this.filterPrefix()+"var fn="+this.generateFunction("fn","s,l,a,i")+
e+this.watchFns()+"return fn;";e=(new Function("$filter","ensureSafeMemberName","ensureSafeObject","ensureSafeFunction","getStringValue","ensureSafeAssignContext","ifDefined","plus","text",e))(this.$filter,Sa,ra,nd,ig,Ib,mg,od,a);this.state=this.stage=void 0;e.literal=sd(c);e.constant=c.constant;return e},USE:"use",STRICT:"strict",watchFns:function(){var a=[],b=this.state.inputs,d=this;q(b,function(b){a.push("var "+b+"="+d.generateFunction(b,"s"))});b.length&&a.push("fn.inputs=["+b.join(",")+"];");
return a.join("")},generateFunction:function(a,b){return"function("+b+"){"+this.varsPrefix(a)+this.body(a)+"};"},filterPrefix:function(){var a=[],b=this;q(this.state.filters,function(d,c){a.push(d+"=$filter("+b.escape(c)+")")});return a.length?"var "+a.join(",")+";":""},varsPrefix:function(a){return this.state[a].vars.length?"var "+this.state[a].vars.join(",")+";":""},body:function(a){return this.state[a].body.join("")},recurse:function(a,b,d,c,e,f){var g,h,k=this,l,m,n;c=c||A;if(!f&&w(a.watchId))b=
b||this.nextId(),this.if_("i",this.lazyAssign(b,this.computedMember("i",a.watchId)),this.lazyRecurse(a,b,d,c,e,!0));else switch(a.type){case s.Program:q(a.body,function(b,c){k.recurse(b.expression,void 0,void 0,function(a){h=a});c!==a.body.length-1?k.current().body.push(h,";"):k.return_(h)});break;case s.Literal:m=this.escape(a.value);this.assign(b,m);c(m);break;case s.UnaryExpression:this.recurse(a.argument,void 0,void 0,function(a){h=a});m=a.operator+"("+this.ifDefined(h,0)+")";this.assign(b,m);
c(m);break;case s.BinaryExpression:this.recurse(a.left,void 0,void 0,function(a){g=a});this.recurse(a.right,void 0,void 0,function(a){h=a});m="+"===a.operator?this.plus(g,h):"-"===a.operator?this.ifDefined(g,0)+a.operator+this.ifDefined(h,0):"("+g+")"+a.operator+"("+h+")";this.assign(b,m);c(m);break;case s.LogicalExpression:b=b||this.nextId();k.recurse(a.left,b);k.if_("&&"===a.operator?b:k.not(b),k.lazyRecurse(a.right,b));c(b);break;case s.ConditionalExpression:b=b||this.nextId();k.recurse(a.test,
b);k.if_(b,k.lazyRecurse(a.alternate,b),k.lazyRecurse(a.consequent,b));c(b);break;case s.Identifier:b=b||this.nextId();d&&(d.context="inputs"===k.stage?"s":this.assign(this.nextId(),this.getHasOwnProperty("l",a.name)+"?l:s"),d.computed=!1,d.name=a.name);Sa(a.name);k.if_("inputs"===k.stage||k.not(k.getHasOwnProperty("l",a.name)),function(){k.if_("inputs"===k.stage||"s",function(){e&&1!==e&&k.if_(k.not(k.nonComputedMember("s",a.name)),k.lazyAssign(k.nonComputedMember("s",a.name),"{}"));k.assign(b,k.nonComputedMember("s",
a.name))})},b&&k.lazyAssign(b,k.nonComputedMember("l",a.name)));(k.state.expensiveChecks||Jb(a.name))&&k.addEnsureSafeObject(b);c(b);break;case s.MemberExpression:g=d&&(d.context=this.nextId())||this.nextId();b=b||this.nextId();k.recurse(a.object,g,void 0,function(){k.if_(k.notNull(g),function(){e&&1!==e&&k.addEnsureSafeAssignContext(g);if(a.computed)h=k.nextId(),k.recurse(a.property,h),k.getStringValue(h),k.addEnsureSafeMemberName(h),e&&1!==e&&k.if_(k.not(k.computedMember(g,h)),k.lazyAssign(k.computedMember(g,
h),"{}")),m=k.ensureSafeObject(k.computedMember(g,h)),k.assign(b,m),d&&(d.computed=!0,d.name=h);else{Sa(a.property.name);e&&1!==e&&k.if_(k.not(k.nonComputedMember(g,a.property.name)),k.lazyAssign(k.nonComputedMember(g,a.property.name),"{}"));m=k.nonComputedMember(g,a.property.name);if(k.state.expensiveChecks||Jb(a.property.name))m=k.ensureSafeObject(m);k.assign(b,m);d&&(d.computed=!1,d.name=a.property.name)}},function(){k.assign(b,"undefined")});c(b)},!!e);break;case s.CallExpression:b=b||this.nextId();
a.filter?(h=k.filter(a.callee.name),l=[],q(a.arguments,function(a){var b=k.nextId();k.recurse(a,b);l.push(b)}),m=h+"("+l.join(",")+")",k.assign(b,m),c(b)):(h=k.nextId(),g={},l=[],k.recurse(a.callee,h,g,function(){k.if_(k.notNull(h),function(){k.addEnsureSafeFunction(h);q(a.arguments,function(a){k.recurse(a,k.nextId(),void 0,function(a){l.push(k.ensureSafeObject(a))})});g.name?(k.state.expensiveChecks||k.addEnsureSafeObject(g.context),m=k.member(g.context,g.name,g.computed)+"("+l.join(",")+")"):m=
h+"("+l.join(",")+")";m=k.ensureSafeObject(m);k.assign(b,m)},function(){k.assign(b,"undefined")});c(b)}));break;case s.AssignmentExpression:h=this.nextId();g={};if(!qd(a.left))throw X("lval");this.recurse(a.left,void 0,g,function(){k.if_(k.notNull(g.context),function(){k.recurse(a.right,h);k.addEnsureSafeObject(k.member(g.context,g.name,g.computed));k.addEnsureSafeAssignContext(g.context);m=k.member(g.context,g.name,g.computed)+a.operator+h;k.assign(b,m);c(b||m)})},1);break;case s.ArrayExpression:l=
[];q(a.elements,function(a){k.recurse(a,k.nextId(),void 0,function(a){l.push(a)})});m="["+l.join(",")+"]";this.assign(b,m);c(m);break;case s.ObjectExpression:l=[];n=!1;q(a.properties,function(a){a.computed&&(n=!0)});n?(b=b||this.nextId(),this.assign(b,"{}"),q(a.properties,function(a){a.computed?(g=k.nextId(),k.recurse(a.key,g)):g=a.key.type===s.Identifier?a.key.name:""+a.key.value;h=k.nextId();k.recurse(a.value,h);k.assign(k.member(b,g,a.computed),h)})):(q(a.properties,function(b){k.recurse(b.value,
a.constant?void 0:k.nextId(),void 0,function(a){l.push(k.escape(b.key.type===s.Identifier?b.key.name:""+b.key.value)+":"+a)})}),m="{"+l.join(",")+"}",this.assign(b,m));c(b||m);break;case s.ThisExpression:this.assign(b,"s");c("s");break;case s.LocalsExpression:this.assign(b,"l");c("l");break;case s.NGValueParameter:this.assign(b,"v"),c("v")}},getHasOwnProperty:function(a,b){var d=a+"."+b,c=this.current().own;c.hasOwnProperty(d)||(c[d]=this.nextId(!1,a+"&&("+this.escape(b)+" in "+a+")"));return c[d]},
assign:function(a,b){if(a)return this.current().body.push(a,"=",b,";"),a},filter:function(a){this.state.filters.hasOwnProperty(a)||(this.state.filters[a]=this.nextId(!0));return this.state.filters[a]},ifDefined:function(a,b){return"ifDefined("+a+","+this.escape(b)+")"},plus:function(a,b){return"plus("+a+","+b+")"},return_:function(a){this.current().body.push("return ",a,";")},if_:function(a,b,d){if(!0===a)b();else{var c=this.current().body;c.push("if(",a,"){");b();c.push("}");d&&(c.push("else{"),
d(),c.push("}"))}},not:function(a){return"!("+a+")"},notNull:function(a){return a+"!=null"},nonComputedMember:function(a,b){var d=/[^$_a-zA-Z0-9]/g;return/[$_a-zA-Z][$_a-zA-Z0-9]*/.test(b)?a+"."+b:a+'["'+b.replace(d,this.stringEscapeFn)+'"]'},computedMember:function(a,b){return a+"["+b+"]"},member:function(a,b,d){return d?this.computedMember(a,b):this.nonComputedMember(a,b)},addEnsureSafeObject:function(a){this.current().body.push(this.ensureSafeObject(a),";")},addEnsureSafeMemberName:function(a){this.current().body.push(this.ensureSafeMemberName(a),
";")},addEnsureSafeFunction:function(a){this.current().body.push(this.ensureSafeFunction(a),";")},addEnsureSafeAssignContext:function(a){this.current().body.push(this.ensureSafeAssignContext(a),";")},ensureSafeObject:function(a){return"ensureSafeObject("+a+",text)"},ensureSafeMemberName:function(a){return"ensureSafeMemberName("+a+",text)"},ensureSafeFunction:function(a){return"ensureSafeFunction("+a+",text)"},getStringValue:function(a){this.assign(a,"getStringValue("+a+")")},ensureSafeAssignContext:function(a){return"ensureSafeAssignContext("+
a+",text)"},lazyRecurse:function(a,b,d,c,e,f){var g=this;return function(){g.recurse(a,b,d,c,e,f)}},lazyAssign:function(a,b){var d=this;return function(){d.assign(a,b)}},stringEscapeRegex:/[^ a-zA-Z0-9]/g,stringEscapeFn:function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)},escape:function(a){if(G(a))return"'"+a.replace(this.stringEscapeRegex,this.stringEscapeFn)+"'";if(T(a))return a.toString();if(!0===a)return"true";if(!1===a)return"false";if(null===a)return"null";if("undefined"===
typeof a)return"undefined";throw X("esc");},nextId:function(a,b){var d="v"+this.state.nextId++;a||this.current().vars.push(d+(b?"="+b:""));return d},current:function(){return this.state[this.state.computing]}};ud.prototype={compile:function(a,b){var d=this,c=this.astBuilder.ast(a);this.expression=a;this.expensiveChecks=b;V(c,d.$filter);var e,f;if(e=rd(c))f=this.recurse(e);e=pd(c.body);var g;e&&(g=[],q(e,function(a,b){var c=d.recurse(a);a.input=c;g.push(c);a.watchId=b}));var h=[];q(c.body,function(a){h.push(d.recurse(a.expression))});
e=0===c.body.length?A:1===c.body.length?h[0]:function(a,b){var c;q(h,function(d){c=d(a,b)});return c};f&&(e.assign=function(a,b,c){return f(a,c,b)});g&&(e.inputs=g);e.literal=sd(c);e.constant=c.constant;return e},recurse:function(a,b,d){var c,e,f=this,g;if(a.input)return this.inputs(a.input,a.watchId);switch(a.type){case s.Literal:return this.value(a.value,b);case s.UnaryExpression:return e=this.recurse(a.argument),this["unary"+a.operator](e,b);case s.BinaryExpression:return c=this.recurse(a.left),
e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case s.LogicalExpression:return c=this.recurse(a.left),e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case s.ConditionalExpression:return this["ternary?:"](this.recurse(a.test),this.recurse(a.alternate),this.recurse(a.consequent),b);case s.Identifier:return Sa(a.name,f.expression),f.identifier(a.name,f.expensiveChecks||Jb(a.name),b,d,f.expression);case s.MemberExpression:return c=this.recurse(a.object,!1,!!d),a.computed||(Sa(a.property.name,
f.expression),e=a.property.name),a.computed&&(e=this.recurse(a.property)),a.computed?this.computedMember(c,e,b,d,f.expression):this.nonComputedMember(c,e,f.expensiveChecks,b,d,f.expression);case s.CallExpression:return g=[],q(a.arguments,function(a){g.push(f.recurse(a))}),a.filter&&(e=this.$filter(a.callee.name)),a.filter||(e=this.recurse(a.callee,!0)),a.filter?function(a,c,d,f){for(var n=[],p=0;p<g.length;++p)n.push(g[p](a,c,d,f));a=e.apply(void 0,n,f);return b?{context:void 0,name:void 0,value:a}:
a}:function(a,c,d,m){var n=e(a,c,d,m),p;if(null!=n.value){ra(n.context,f.expression);nd(n.value,f.expression);p=[];for(var q=0;q<g.length;++q)p.push(ra(g[q](a,c,d,m),f.expression));p=ra(n.value.apply(n.context,p),f.expression)}return b?{value:p}:p};case s.AssignmentExpression:return c=this.recurse(a.left,!0,1),e=this.recurse(a.right),function(a,d,g,m){var n=c(a,d,g,m);a=e(a,d,g,m);ra(n.value,f.expression);Ib(n.context);n.context[n.name]=a;return b?{value:a}:a};case s.ArrayExpression:return g=[],q(a.elements,
function(a){g.push(f.recurse(a))}),function(a,c,d,e){for(var f=[],p=0;p<g.length;++p)f.push(g[p](a,c,d,e));return b?{value:f}:f};case s.ObjectExpression:return g=[],q(a.properties,function(a){a.computed?g.push({key:f.recurse(a.key),computed:!0,value:f.recurse(a.value)}):g.push({key:a.key.type===s.Identifier?a.key.name:""+a.key.value,computed:!1,value:f.recurse(a.value)})}),function(a,c,d,e){for(var f={},p=0;p<g.length;++p)g[p].computed?f[g[p].key(a,c,d,e)]=g[p].value(a,c,d,e):f[g[p].key]=g[p].value(a,
c,d,e);return b?{value:f}:f};case s.ThisExpression:return function(a){return b?{value:a}:a};case s.LocalsExpression:return function(a,c){return b?{value:c}:c};case s.NGValueParameter:return function(a,c,d){return b?{value:d}:d}}},"unary+":function(a,b){return function(d,c,e,f){d=a(d,c,e,f);d=w(d)?+d:0;return b?{value:d}:d}},"unary-":function(a,b){return function(d,c,e,f){d=a(d,c,e,f);d=w(d)?-d:0;return b?{value:d}:d}},"unary!":function(a,b){return function(d,c,e,f){d=!a(d,c,e,f);return b?{value:d}:
d}},"binary+":function(a,b,d){return function(c,e,f,g){var h=a(c,e,f,g);c=b(c,e,f,g);h=od(h,c);return d?{value:h}:h}},"binary-":function(a,b,d){return function(c,e,f,g){var h=a(c,e,f,g);c=b(c,e,f,g);h=(w(h)?h:0)-(w(c)?c:0);return d?{value:h}:h}},"binary*":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)*b(c,e,f,g);return d?{value:c}:c}},"binary/":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)/b(c,e,f,g);return d?{value:c}:c}},"binary%":function(a,b,d){return function(c,e,f,g){c=a(c,e,
f,g)%b(c,e,f,g);return d?{value:c}:c}},"binary===":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)===b(c,e,f,g);return d?{value:c}:c}},"binary!==":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)!==b(c,e,f,g);return d?{value:c}:c}},"binary==":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)==b(c,e,f,g);return d?{value:c}:c}},"binary!=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)!=b(c,e,f,g);return d?{value:c}:c}},"binary<":function(a,b,d){return function(c,e,f,g){c=a(c,e,
f,g)<b(c,e,f,g);return d?{value:c}:c}},"binary>":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>b(c,e,f,g);return d?{value:c}:c}},"binary<=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)<=b(c,e,f,g);return d?{value:c}:c}},"binary>=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>=b(c,e,f,g);return d?{value:c}:c}},"binary&&":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)&&b(c,e,f,g);return d?{value:c}:c}},"binary||":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)||
b(c,e,f,g);return d?{value:c}:c}},"ternary?:":function(a,b,d,c){return function(e,f,g,h){e=a(e,f,g,h)?b(e,f,g,h):d(e,f,g,h);return c?{value:e}:e}},value:function(a,b){return function(){return b?{context:void 0,name:void 0,value:a}:a}},identifier:function(a,b,d,c,e){return function(f,g,h,k){f=g&&a in g?g:f;c&&1!==c&&f&&!f[a]&&(f[a]={});g=f?f[a]:void 0;b&&ra(g,e);return d?{context:f,name:a,value:g}:g}},computedMember:function(a,b,d,c,e){return function(f,g,h,k){var l=a(f,g,h,k),m,n;null!=l&&(m=b(f,
g,h,k),m+="",Sa(m,e),c&&1!==c&&(Ib(l),l&&!l[m]&&(l[m]={})),n=l[m],ra(n,e));return d?{context:l,name:m,value:n}:n}},nonComputedMember:function(a,b,d,c,e,f){return function(g,h,k,l){g=a(g,h,k,l);e&&1!==e&&(Ib(g),g&&!g[b]&&(g[b]={}));h=null!=g?g[b]:void 0;(d||Jb(b))&&ra(h,f);return c?{context:g,name:b,value:h}:h}},inputs:function(a,b){return function(d,c,e,f){return f?f[b]:a(d,c,e)}}};var kc=function(a,b,d){this.lexer=a;this.$filter=b;this.options=d;this.ast=new s(a,d);this.astCompiler=d.csp?new ud(this.ast,
b):new td(this.ast,b)};kc.prototype={constructor:kc,parse:function(a){return this.astCompiler.compile(a,this.options.expensiveChecks)}};var ng=Object.prototype.valueOf,sa=N("$sce"),la={HTML:"html",CSS:"css",URL:"url",RESOURCE_URL:"resourceUrl",JS:"js"},pg=N("$compile"),$=C.document.createElement("a"),yd=Y(C.location.href);zd.$inject=["$document"];Mc.$inject=["$provide"];var Gd=22,Fd=".",mc="0";Ad.$inject=["$locale"];Cd.$inject=["$locale"];var Ag={yyyy:ba("FullYear",4,0,!1,!0),yy:ba("FullYear",2,0,
!0,!0),y:ba("FullYear",1,0,!1,!0),MMMM:kb("Month"),MMM:kb("Month",!0),MM:ba("Month",2,1),M:ba("Month",1,1),LLLL:kb("Month",!1,!0),dd:ba("Date",2),d:ba("Date",1),HH:ba("Hours",2),H:ba("Hours",1),hh:ba("Hours",2,-12),h:ba("Hours",1,-12),mm:ba("Minutes",2),m:ba("Minutes",1),ss:ba("Seconds",2),s:ba("Seconds",1),sss:ba("Milliseconds",3),EEEE:kb("Day"),EEE:kb("Day",!0),a:function(a,b){return 12>a.getHours()?b.AMPMS[0]:b.AMPMS[1]},Z:function(a,b,d){a=-1*d;return a=(0<=a?"+":"")+(Kb(Math[0<a?"floor":"ceil"](a/
60),2)+Kb(Math.abs(a%60),2))},ww:Id(2),w:Id(1),G:nc,GG:nc,GGG:nc,GGGG:function(a,b){return 0>=a.getFullYear()?b.ERANAMES[0]:b.ERANAMES[1]}},zg=/((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,yg=/^\-?\d+$/;Bd.$inject=["$locale"];var tg=ha(Q),ug=ha(ub);Dd.$inject=["$parse"];var oe=ha({restrict:"E",compile:function(a,b){if(!b.href&&!b.xlinkHref)return function(a,b){if("a"===b[0].nodeName.toLowerCase()){var e="[object SVGAnimatedString]"===ma.call(b.prop("href"))?
"xlink:href":"href";b.on("click",function(a){b.attr(e)||a.preventDefault()})}}}}),vb={};q(Eb,function(a,b){function d(a,d,e){a.$watch(e[c],function(a){e.$set(b,!!a)})}if("multiple"!=a){var c=Aa("ng-"+b),e=d;"checked"===a&&(e=function(a,b,e){e.ngModel!==e[c]&&d(a,b,e)});vb[c]=function(){return{restrict:"A",priority:100,link:e}}}});q(bd,function(a,b){vb[b]=function(){return{priority:100,link:function(a,c,e){if("ngPattern"===b&&"/"==e.ngPattern.charAt(0)&&(c=e.ngPattern.match(Cg))){e.$set("ngPattern",
new RegExp(c[1],c[2]));return}a.$watch(e[b],function(a){e.$set(b,a)})}}}});q(["src","srcset","href"],function(a){var b=Aa("ng-"+a);vb[b]=function(){return{priority:99,link:function(d,c,e){var f=a,g=a;"href"===a&&"[object SVGAnimatedString]"===ma.call(c.prop("href"))&&(g="xlinkHref",e.$attr[g]="xlink:href",f=null);e.$observe(b,function(b){b?(e.$set(g,b),Ea&&f&&c.prop(f,e[g])):"href"===a&&e.$set(g,null)})}}}});var Lb={$addControl:A,$$renameControl:function(a,b){a.$name=b},$removeControl:A,$setValidity:A,
$setDirty:A,$setPristine:A,$setSubmitted:A};Jd.$inject=["$element","$attrs","$scope","$animate","$interpolate"];var Sd=function(a){return["$timeout","$parse",function(b,d){function c(a){return""===a?d('this[""]').assign:d(a).assign||A}return{name:"form",restrict:a?"EAC":"E",require:["form","^^?form"],controller:Jd,compile:function(d,f){d.addClass(Ua).addClass(ob);var g=f.name?"name":a&&f.ngForm?"ngForm":!1;return{pre:function(a,d,e,f){var n=f[0];if(!("action"in e)){var p=function(b){a.$apply(function(){n.$commitViewValue();
n.$setSubmitted()});b.preventDefault()};d[0].addEventListener("submit",p,!1);d.on("$destroy",function(){b(function(){d[0].removeEventListener("submit",p,!1)},0,!1)})}(f[1]||n.$$parentForm).$addControl(n);var q=g?c(n.$name):A;g&&(q(a,n),e.$observe(g,function(b){n.$name!==b&&(q(a,void 0),n.$$parentForm.$$renameControl(n,b),q=c(n.$name),q(a,n))}));d.on("$destroy",function(){n.$$parentForm.$removeControl(n);q(a,void 0);S(n,Lb)})}}}}}]},pe=Sd(),Ce=Sd(!0),Bg=/^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/,
Kg=/^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+\])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i,Lg=/^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+\/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+\/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/,Mg=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/,Td=/^(\d{4,})-(\d{2})-(\d{2})$/,Ud=/^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,rc=/^(\d{4,})-W(\d\d)$/,Vd=/^(\d{4,})-(\d\d)$/,
Wd=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Ld=U();q(["date","datetime-local","month","time","week"],function(a){Ld[a]=!0});var Xd={text:function(a,b,d,c,e,f){lb(a,b,d,c,e,f);pc(c)},date:mb("date",Td,Nb(Td,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":mb("datetimelocal",Ud,Nb(Ud,"yyyy MM dd HH mm ss sss".split(" ")),"yyyy-MM-ddTHH:mm:ss.sss"),time:mb("time",Wd,Nb(Wd,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:mb("week",rc,function(a,b){if(da(a))return a;if(G(a)){rc.lastIndex=0;var d=rc.exec(a);
if(d){var c=+d[1],e=+d[2],f=d=0,g=0,h=0,k=Hd(c),e=7*(e-1);b&&(d=b.getHours(),f=b.getMinutes(),g=b.getSeconds(),h=b.getMilliseconds());return new Date(c,0,k.getDate()+e,d,f,g,h)}}return NaN},"yyyy-Www"),month:mb("month",Vd,Nb(Vd,["yyyy","MM"]),"yyyy-MM"),number:function(a,b,d,c,e,f){Md(a,b,d,c);lb(a,b,d,c,e,f);c.$$parserName="number";c.$parsers.push(function(a){if(c.$isEmpty(a))return null;if(Mg.test(a))return parseFloat(a)});c.$formatters.push(function(a){if(!c.$isEmpty(a)){if(!T(a))throw nb("numfmt",
a);a=a.toString()}return a});if(w(d.min)||d.ngMin){var g;c.$validators.min=function(a){return c.$isEmpty(a)||y(g)||a>=g};d.$observe("min",function(a){w(a)&&!T(a)&&(a=parseFloat(a));g=T(a)&&!isNaN(a)?a:void 0;c.$validate()})}if(w(d.max)||d.ngMax){var h;c.$validators.max=function(a){return c.$isEmpty(a)||y(h)||a<=h};d.$observe("max",function(a){w(a)&&!T(a)&&(a=parseFloat(a));h=T(a)&&!isNaN(a)?a:void 0;c.$validate()})}},url:function(a,b,d,c,e,f){lb(a,b,d,c,e,f);pc(c);c.$$parserName="url";c.$validators.url=
function(a,b){var d=a||b;return c.$isEmpty(d)||Kg.test(d)}},email:function(a,b,d,c,e,f){lb(a,b,d,c,e,f);pc(c);c.$$parserName="email";c.$validators.email=function(a,b){var d=a||b;return c.$isEmpty(d)||Lg.test(d)}},radio:function(a,b,d,c){y(d.name)&&b.attr("name",++pb);b.on("click",function(a){b[0].checked&&c.$setViewValue(d.value,a&&a.type)});c.$render=function(){b[0].checked=d.value==c.$viewValue};d.$observe("value",c.$render)},checkbox:function(a,b,d,c,e,f,g,h){var k=Nd(h,a,"ngTrueValue",d.ngTrueValue,
!0),l=Nd(h,a,"ngFalseValue",d.ngFalseValue,!1);b.on("click",function(a){c.$setViewValue(b[0].checked,a&&a.type)});c.$render=function(){b[0].checked=c.$viewValue};c.$isEmpty=function(a){return!1===a};c.$formatters.push(function(a){return na(a,k)});c.$parsers.push(function(a){return a?k:l})},hidden:A,button:A,submit:A,reset:A,file:A},Gc=["$browser","$sniffer","$filter","$parse",function(a,b,d,c){return{restrict:"E",require:["?ngModel"],link:{pre:function(e,f,g,h){h[0]&&(Xd[Q(g.type)]||Xd.text)(e,f,
g,h[0],b,a,d,c)}}}}],Ng=/^(true|false|\d+)$/,Ue=function(){return{restrict:"A",priority:100,compile:function(a,b){return Ng.test(b.ngValue)?function(a,b,e){e.$set("value",a.$eval(e.ngValue))}:function(a,b,e){a.$watch(e.ngValue,function(a){e.$set("value",a)})}}}},ue=["$compile",function(a){return{restrict:"AC",compile:function(b){a.$$addBindingClass(b);return function(b,c,e){a.$$addBindingInfo(c,e.ngBind);c=c[0];b.$watch(e.ngBind,function(a){c.textContent=y(a)?"":a})}}}}],we=["$interpolate","$compile",
function(a,b){return{compile:function(d){b.$$addBindingClass(d);return function(c,d,f){c=a(d.attr(f.$attr.ngBindTemplate));b.$$addBindingInfo(d,c.expressions);d=d[0];f.$observe("ngBindTemplate",function(a){d.textContent=y(a)?"":a})}}}}],ve=["$sce","$parse","$compile",function(a,b,d){return{restrict:"A",compile:function(c,e){var f=b(e.ngBindHtml),g=b(e.ngBindHtml,function(b){return a.valueOf(b)});d.$$addBindingClass(c);return function(b,c,e){d.$$addBindingInfo(c,e.ngBindHtml);b.$watch(g,function(){var d=
f(b);c.html(a.getTrustedHtml(d)||"")})}}}}],Te=ha({restrict:"A",require:"ngModel",link:function(a,b,d,c){c.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),xe=qc("",!0),ze=qc("Odd",0),ye=qc("Even",1),Ae=Ta({compile:function(a,b){b.$set("ngCloak",void 0);a.removeClass("ng-cloak")}}),Be=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],Lc={},Og={blur:!0,focus:!0};q("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),
function(a){var b=Aa("ng-"+a);Lc[b]=["$parse","$rootScope",function(d,c){return{restrict:"A",compile:function(e,f){var g=d(f[b],null,!0);return function(b,d){d.on(a,function(d){var e=function(){g(b,{$event:d})};Og[a]&&c.$$phase?b.$evalAsync(e):b.$apply(e)})}}}}]});var Ee=["$animate","$compile",function(a,b){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(d,c,e,f,g){var h,k,l;d.$watch(e.ngIf,function(d){d?k||g(function(d,f){k=f;d[d.length++]=
b.$$createComment("end ngIf",e.ngIf);h={clone:d};a.enter(d,c.parent(),c)}):(l&&(l.remove(),l=null),k&&(k.$destroy(),k=null),h&&(l=tb(h.clone),a.leave(l).then(function(){l=null}),h=null))})}}}],Fe=["$templateRequest","$anchorScroll","$animate",function(a,b,d){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:ca.noop,compile:function(c,e){var f=e.ngInclude||e.src,g=e.onload||"",h=e.autoscroll;return function(c,e,m,n,p){var q=0,s,B,r,y=function(){B&&(B.remove(),B=null);s&&
(s.$destroy(),s=null);r&&(d.leave(r).then(function(){B=null}),B=r,r=null)};c.$watch(f,function(f){var m=function(){!w(h)||h&&!c.$eval(h)||b()},t=++q;f?(a(f,!0).then(function(a){if(!c.$$destroyed&&t===q){var b=c.$new();n.template=a;a=p(b,function(a){y();d.enter(a,null,e).then(m)});s=b;r=a;s.$emit("$includeContentLoaded",f);c.$eval(g)}},function(){c.$$destroyed||t!==q||(y(),c.$emit("$includeContentError",f))}),c.$emit("$includeContentRequested",f)):(y(),n.template=null)})}}}}],We=["$compile",function(a){return{restrict:"ECA",
priority:-400,require:"ngInclude",link:function(b,d,c,e){ma.call(d[0]).match(/SVG/)?(d.empty(),a(Oc(e.template,C.document).childNodes)(b,function(a){d.append(a)},{futureParentElement:d})):(d.html(e.template),a(d.contents())(b))}}}],Ge=Ta({priority:450,compile:function(){return{pre:function(a,b,d){a.$eval(d.ngInit)}}}}),Se=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,b,d,c){var e=b.attr(d.$attr.ngList)||", ",f="false"!==d.ngTrim,g=f?W(e):e;c.$parsers.push(function(a){if(!y(a)){var b=
[];a&&q(a.split(g),function(a){a&&b.push(f?W(a):a)});return b}});c.$formatters.push(function(a){if(L(a))return a.join(e)});c.$isEmpty=function(a){return!a||!a.length}}}},ob="ng-valid",Od="ng-invalid",Ua="ng-pristine",Mb="ng-dirty",Qd="ng-pending",nb=N("ngModel"),Pg=["$scope","$exceptionHandler","$attrs","$element","$parse","$animate","$timeout","$rootScope","$q","$interpolate",function(a,b,d,c,e,f,g,h,k,l){this.$modelValue=this.$viewValue=Number.NaN;this.$$rawModelValue=void 0;this.$validators={};
this.$asyncValidators={};this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$untouched=!0;this.$touched=!1;this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$error={};this.$$success={};this.$pending=void 0;this.$name=l(d.name||"",!1)(a);this.$$parentForm=Lb;var m=e(d.ngModel),n=m.assign,p=m,u=n,s=null,B,r=this;this.$$setOptions=function(a){if((r.$options=a)&&a.getterSetter){var b=e(d.ngModel+"()"),f=e(d.ngModel+"($$$p)");p=function(a){var c=m(a);z(c)&&(c=b(a));
return c};u=function(a,b){z(m(a))?f(a,{$$$p:b}):n(a,b)}}else if(!m.assign)throw nb("nonassign",d.ngModel,ya(c));};this.$render=A;this.$isEmpty=function(a){return y(a)||""===a||null===a||a!==a};this.$$updateEmptyClasses=function(a){r.$isEmpty(a)?(f.removeClass(c,"ng-not-empty"),f.addClass(c,"ng-empty")):(f.removeClass(c,"ng-empty"),f.addClass(c,"ng-not-empty"))};var J=0;Kd({ctrl:this,$element:c,set:function(a,b){a[b]=!0},unset:function(a,b){delete a[b]},$animate:f});this.$setPristine=function(){r.$dirty=
!1;r.$pristine=!0;f.removeClass(c,Mb);f.addClass(c,Ua)};this.$setDirty=function(){r.$dirty=!0;r.$pristine=!1;f.removeClass(c,Ua);f.addClass(c,Mb);r.$$parentForm.$setDirty()};this.$setUntouched=function(){r.$touched=!1;r.$untouched=!0;f.setClass(c,"ng-untouched","ng-touched")};this.$setTouched=function(){r.$touched=!0;r.$untouched=!1;f.setClass(c,"ng-touched","ng-untouched")};this.$rollbackViewValue=function(){g.cancel(s);r.$viewValue=r.$$lastCommittedViewValue;r.$render()};this.$validate=function(){if(!T(r.$modelValue)||
!isNaN(r.$modelValue)){var a=r.$$rawModelValue,b=r.$valid,c=r.$modelValue,d=r.$options&&r.$options.allowInvalid;r.$$runValidators(a,r.$$lastCommittedViewValue,function(e){d||b===e||(r.$modelValue=e?a:void 0,r.$modelValue!==c&&r.$$writeModelToScope())})}};this.$$runValidators=function(a,b,c){function d(){var c=!0;q(r.$validators,function(d,e){var g=d(a,b);c=c&&g;f(e,g)});return c?!0:(q(r.$asyncValidators,function(a,b){f(b,null)}),!1)}function e(){var c=[],d=!0;q(r.$asyncValidators,function(e,g){var h=
e(a,b);if(!h||!z(h.then))throw nb("nopromise",h);f(g,void 0);c.push(h.then(function(){f(g,!0)},function(){d=!1;f(g,!1)}))});c.length?k.all(c).then(function(){g(d)},A):g(!0)}function f(a,b){h===J&&r.$setValidity(a,b)}function g(a){h===J&&c(a)}J++;var h=J;(function(){var a=r.$$parserName||"parse";if(y(B))f(a,null);else return B||(q(r.$validators,function(a,b){f(b,null)}),q(r.$asyncValidators,function(a,b){f(b,null)})),f(a,B),B;return!0})()?d()?e():g(!1):g(!1)};this.$commitViewValue=function(){var a=
r.$viewValue;g.cancel(s);if(r.$$lastCommittedViewValue!==a||""===a&&r.$$hasNativeValidators)r.$$updateEmptyClasses(a),r.$$lastCommittedViewValue=a,r.$pristine&&this.$setDirty(),this.$$parseAndValidate()};this.$$parseAndValidate=function(){var b=r.$$lastCommittedViewValue;if(B=y(b)?void 0:!0)for(var c=0;c<r.$parsers.length;c++)if(b=r.$parsers[c](b),y(b)){B=!1;break}T(r.$modelValue)&&isNaN(r.$modelValue)&&(r.$modelValue=p(a));var d=r.$modelValue,e=r.$options&&r.$options.allowInvalid;r.$$rawModelValue=
b;e&&(r.$modelValue=b,r.$modelValue!==d&&r.$$writeModelToScope());r.$$runValidators(b,r.$$lastCommittedViewValue,function(a){e||(r.$modelValue=a?b:void 0,r.$modelValue!==d&&r.$$writeModelToScope())})};this.$$writeModelToScope=function(){u(a,r.$modelValue);q(r.$viewChangeListeners,function(a){try{a()}catch(c){b(c)}})};this.$setViewValue=function(a,b){r.$viewValue=a;r.$options&&!r.$options.updateOnDefault||r.$$debounceViewValueCommit(b)};this.$$debounceViewValueCommit=function(b){var c=0,d=r.$options;
d&&w(d.debounce)&&(d=d.debounce,T(d)?c=d:T(d[b])?c=d[b]:T(d["default"])&&(c=d["default"]));g.cancel(s);c?s=g(function(){r.$commitViewValue()},c):h.$$phase?r.$commitViewValue():a.$apply(function(){r.$commitViewValue()})};a.$watch(function(){var b=p(a);if(b!==r.$modelValue&&(r.$modelValue===r.$modelValue||b===b)){r.$modelValue=r.$$rawModelValue=b;B=void 0;for(var c=r.$formatters,d=c.length,e=b;d--;)e=c[d](e);r.$viewValue!==e&&(r.$$updateEmptyClasses(e),r.$viewValue=r.$$lastCommittedViewValue=e,r.$render(),
r.$$runValidators(b,e,A))}return b})}],Re=["$rootScope",function(a){return{restrict:"A",require:["ngModel","^?form","^?ngModelOptions"],controller:Pg,priority:1,compile:function(b){b.addClass(Ua).addClass("ng-untouched").addClass(ob);return{pre:function(a,b,e,f){var g=f[0];b=f[1]||g.$$parentForm;g.$$setOptions(f[2]&&f[2].$options);b.$addControl(g);e.$observe("name",function(a){g.$name!==a&&g.$$parentForm.$$renameControl(g,a)});a.$on("$destroy",function(){g.$$parentForm.$removeControl(g)})},post:function(b,
c,e,f){var g=f[0];if(g.$options&&g.$options.updateOn)c.on(g.$options.updateOn,function(a){g.$$debounceViewValueCommit(a&&a.type)});c.on("blur",function(){g.$touched||(a.$$phase?b.$evalAsync(g.$setTouched):b.$apply(g.$setTouched))})}}}}}],Qg=/(\s+|^)default(\s+|$)/,Ve=function(){return{restrict:"A",controller:["$scope","$attrs",function(a,b){var d=this;this.$options=pa(a.$eval(b.ngModelOptions));w(this.$options.updateOn)?(this.$options.updateOnDefault=!1,this.$options.updateOn=W(this.$options.updateOn.replace(Qg,
function(){d.$options.updateOnDefault=!0;return" "}))):this.$options.updateOnDefault=!0}]}},He=Ta({terminal:!0,priority:1E3}),Rg=N("ngOptions"),Sg=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/,Pe=["$compile","$document","$parse",function(a,b,d){function c(a,b,c){function e(a,b,c,d,f){this.selectValue=a;this.viewValue=
b;this.label=c;this.group=d;this.disabled=f}function f(a){var b;if(!q&&ta(a))b=a;else{b=[];for(var c in a)a.hasOwnProperty(c)&&"$"!==c.charAt(0)&&b.push(c)}return b}var n=a.match(Sg);if(!n)throw Rg("iexp",a,ya(b));var p=n[5]||n[7],q=n[6];a=/ as /.test(n[0])&&n[1];var s=n[9];b=d(n[2]?n[1]:p);var w=a&&d(a)||b,r=s&&d(s),y=s?function(a,b){return r(c,b)}:function(a){return Ca(a)},v=function(a,b){return y(a,E(a,b))},A=d(n[2]||n[1]),t=d(n[3]||""),K=d(n[4]||""),z=d(n[8]),H={},E=q?function(a,b){H[q]=b;H[p]=
a;return H}:function(a){H[p]=a;return H};return{trackBy:s,getTrackByValue:v,getWatchables:d(z,function(a){var b=[];a=a||[];for(var d=f(a),e=d.length,g=0;g<e;g++){var h=a===d?g:d[g],l=a[h],h=E(l,h),l=y(l,h);b.push(l);if(n[2]||n[1])l=A(c,h),b.push(l);n[4]&&(h=K(c,h),b.push(h))}return b}),getOptions:function(){for(var a=[],b={},d=z(c)||[],g=f(d),h=g.length,n=0;n<h;n++){var p=d===g?n:g[n],q=E(d[p],p),r=w(c,q),p=y(r,q),u=A(c,q),H=t(c,q),q=K(c,q),r=new e(p,r,u,H,q);a.push(r);b[p]=r}return{items:a,selectValueMap:b,
getOptionFromViewValue:function(a){return b[v(a)]},getViewValueFromOption:function(a){return s?ca.copy(a.viewValue):a.viewValue}}}}}var e=C.document.createElement("option"),f=C.document.createElement("optgroup");return{restrict:"A",terminal:!0,require:["select","ngModel"],link:{pre:function(a,b,c,d){d[0].registerOption=A},post:function(d,h,k,l){function m(a,b){a.element=b;b.disabled=a.disabled;a.label!==b.label&&(b.label=a.label,b.textContent=a.label);a.value!==b.value&&(b.value=a.selectValue)}function n(){var a=
t&&p.readValue();if(t)for(var b=t.items.length-1;0<=b;b--){var c=t.items[b];w(c.group)?Db(c.element.parentNode):Db(c.element)}t=K.getOptions();var d={};v&&h.prepend(B);t.items.forEach(function(a){var b;if(w(a.group)){b=d[a.group];b||(b=f.cloneNode(!1),C.appendChild(b),b.label=null===a.group?"null":a.group,d[a.group]=b);var c=e.cloneNode(!1)}else b=C,c=e.cloneNode(!1);b.appendChild(c);m(a,c)});h[0].appendChild(C);s.$render();s.$isEmpty(a)||(b=p.readValue(),(K.trackBy||y?na(a,b):a===b)||(s.$setViewValue(b),
s.$render()))}var p=l[0],s=l[1],y=k.multiple,B;l=0;for(var r=h.children(),A=r.length;l<A;l++)if(""===r[l].value){B=r.eq(l);break}var v=!!B,z=F(e.cloneNode(!1));z.val("?");var t,K=c(k.ngOptions,h,d),C=b[0].createDocumentFragment();y?(s.$isEmpty=function(a){return!a||0===a.length},p.writeValue=function(a){t.items.forEach(function(a){a.element.selected=!1});a&&a.forEach(function(a){if(a=t.getOptionFromViewValue(a))a.element.selected=!0})},p.readValue=function(){var a=h.val()||[],b=[];q(a,function(a){(a=
t.selectValueMap[a])&&!a.disabled&&b.push(t.getViewValueFromOption(a))});return b},K.trackBy&&d.$watchCollection(function(){if(L(s.$viewValue))return s.$viewValue.map(function(a){return K.getTrackByValue(a)})},function(){s.$render()})):(p.writeValue=function(a){var b=t.getOptionFromViewValue(a);b?(h[0].value!==b.selectValue&&(z.remove(),v||B.remove(),h[0].value=b.selectValue,b.element.selected=!0),b.element.setAttribute("selected","selected")):null===a||v?(z.remove(),v||h.prepend(B),h.val(""),B.prop("selected",
!0),B.attr("selected",!0)):(v||B.remove(),h.prepend(z),h.val("?"),z.prop("selected",!0),z.attr("selected",!0))},p.readValue=function(){var a=t.selectValueMap[h.val()];return a&&!a.disabled?(v||B.remove(),z.remove(),t.getViewValueFromOption(a)):null},K.trackBy&&d.$watch(function(){return K.getTrackByValue(s.$viewValue)},function(){s.$render()}));v?(B.remove(),a(B)(d),B.removeClass("ng-scope")):B=F(e.cloneNode(!1));h.empty();n();d.$watchCollection(K.getWatchables,n)}}}}],Ie=["$locale","$interpolate",
"$log",function(a,b,d){var c=/{}/g,e=/^when(Minus)?(.+)$/;return{link:function(f,g,h){function k(a){g.text(a||"")}var l=h.count,m=h.$attr.when&&g.attr(h.$attr.when),n=h.offset||0,p=f.$eval(m)||{},s={},w=b.startSymbol(),B=b.endSymbol(),r=w+l+"-"+n+B,z=ca.noop,v;q(h,function(a,b){var c=e.exec(b);c&&(c=(c[1]?"-":"")+Q(c[2]),p[c]=g.attr(h.$attr[b]))});q(p,function(a,d){s[d]=b(a.replace(c,r))});f.$watch(l,function(b){var c=parseFloat(b),e=isNaN(c);e||c in p||(c=a.pluralCat(c-n));c===v||e&&T(v)&&isNaN(v)||
(z(),e=s[c],y(e)?(null!=b&&d.debug("ngPluralize: no rule defined for '"+c+"' in "+m),z=A,k()):z=f.$watch(e,k),v=c)})}}}],Je=["$parse","$animate","$compile",function(a,b,d){var c=N("ngRepeat"),e=function(a,b,c,d,e,m,n){a[c]=d;e&&(a[e]=m);a.$index=b;a.$first=0===b;a.$last=b===n-1;a.$middle=!(a.$first||a.$last);a.$odd=!(a.$even=0===(b&1))};return{restrict:"A",multiElement:!0,transclude:"element",priority:1E3,terminal:!0,$$tlb:!0,compile:function(f,g){var h=g.ngRepeat,k=d.$$createComment("end ngRepeat",
h),l=h.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);if(!l)throw c("iexp",h);var m=l[1],n=l[2],p=l[3],s=l[4],l=m.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);if(!l)throw c("iidexp",m);var w=l[3]||l[1],y=l[2];if(p&&(!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(p)||/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(p)))throw c("badident",p);var r,z,v,A,t={$id:Ca};s?r=a(s):(v=function(a,b){return Ca(b)},
A=function(a){return a});return function(a,d,f,g,l){r&&(z=function(b,c,d){y&&(t[y]=b);t[w]=c;t.$index=d;return r(a,t)});var m=U();a.$watchCollection(n,function(f){var g,n,r=d[0],s,u=U(),t,C,F,E,G,D,H;p&&(a[p]=f);if(ta(f))G=f,n=z||v;else for(H in n=z||A,G=[],f)ua.call(f,H)&&"$"!==H.charAt(0)&&G.push(H);t=G.length;H=Array(t);for(g=0;g<t;g++)if(C=f===G?g:G[g],F=f[C],E=n(C,F,g),m[E])D=m[E],delete m[E],u[E]=D,H[g]=D;else{if(u[E])throw q(H,function(a){a&&a.scope&&(m[a.id]=a)}),c("dupes",h,E,F);H[g]={id:E,
scope:void 0,clone:void 0};u[E]=!0}for(s in m){D=m[s];E=tb(D.clone);b.leave(E);if(E[0].parentNode)for(g=0,n=E.length;g<n;g++)E[g].$$NG_REMOVED=!0;D.scope.$destroy()}for(g=0;g<t;g++)if(C=f===G?g:G[g],F=f[C],D=H[g],D.scope){s=r;do s=s.nextSibling;while(s&&s.$$NG_REMOVED);D.clone[0]!=s&&b.move(tb(D.clone),null,r);r=D.clone[D.clone.length-1];e(D.scope,g,w,F,y,C,t)}else l(function(a,c){D.scope=c;var d=k.cloneNode(!1);a[a.length++]=d;b.enter(a,null,r);r=d;D.clone=a;u[D.id]=D;e(D.scope,g,w,F,y,C,t)});m=
u})}}}}],Ke=["$animate",function(a){return{restrict:"A",multiElement:!0,link:function(b,d,c){b.$watch(c.ngShow,function(b){a[b?"removeClass":"addClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})})}}}],De=["$animate",function(a){return{restrict:"A",multiElement:!0,link:function(b,d,c){b.$watch(c.ngHide,function(b){a[b?"addClass":"removeClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})})}}}],Le=Ta(function(a,b,d){a.$watch(d.ngStyle,function(a,d){d&&a!==d&&q(d,function(a,c){b.css(c,"")});a&&b.css(a)},
!0)}),Me=["$animate","$compile",function(a,b){return{require:"ngSwitch",controller:["$scope",function(){this.cases={}}],link:function(d,c,e,f){var g=[],h=[],k=[],l=[],m=function(a,b){return function(){a.splice(b,1)}};d.$watch(e.ngSwitch||e.on,function(c){var d,e;d=0;for(e=k.length;d<e;++d)a.cancel(k[d]);d=k.length=0;for(e=l.length;d<e;++d){var s=tb(h[d].clone);l[d].$destroy();(k[d]=a.leave(s)).then(m(k,d))}h.length=0;l.length=0;(g=f.cases["!"+c]||f.cases["?"])&&q(g,function(c){c.transclude(function(d,
e){l.push(e);var f=c.element;d[d.length++]=b.$$createComment("end ngSwitchWhen");h.push({clone:d});a.enter(d,f.parent(),f)})})})}}}],Ne=Ta({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,b,d,c,e){c.cases["!"+d.ngSwitchWhen]=c.cases["!"+d.ngSwitchWhen]||[];c.cases["!"+d.ngSwitchWhen].push({transclude:e,element:b})}}),Oe=Ta({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,b,d,c,e){c.cases["?"]=c.cases["?"]||[];c.cases["?"].push({transclude:e,
element:b})}}),Tg=N("ngTransclude"),Qe=["$compile",function(a){return{restrict:"EAC",terminal:!0,compile:function(b){var d=a(b.contents());b.empty();return function(a,b,f,g,h){function k(){d(a,function(a){b.append(a)})}if(!h)throw Tg("orphan",ya(b));f.ngTransclude===f.$attr.ngTransclude&&(f.ngTransclude="");f=f.ngTransclude||f.ngTranscludeSlot;h(function(a,c){a.length?b.append(a):(k(),c.$destroy())},null,f);f&&!h.isSlotFilled(f)&&k()}}}}],qe=["$templateCache",function(a){return{restrict:"E",terminal:!0,
compile:function(b,d){"text/ng-template"==d.type&&a.put(d.id,b[0].text)}}}],Ug={$setViewValue:A,$render:A},Vg=["$element","$scope",function(a,b){var d=this,c=new Ra;d.ngModelCtrl=Ug;d.unknownOption=F(C.document.createElement("option"));d.renderUnknownOption=function(b){b="? "+Ca(b)+" ?";d.unknownOption.val(b);a.prepend(d.unknownOption);a.val(b)};b.$on("$destroy",function(){d.renderUnknownOption=A});d.removeUnknownOption=function(){d.unknownOption.parent()&&d.unknownOption.remove()};d.readValue=function(){d.removeUnknownOption();
return a.val()};d.writeValue=function(b){d.hasOption(b)?(d.removeUnknownOption(),a.val(b),""===b&&d.emptyOption.prop("selected",!0)):null==b&&d.emptyOption?(d.removeUnknownOption(),a.val("")):d.renderUnknownOption(b)};d.addOption=function(a,b){if(8!==b[0].nodeType){Qa(a,'"option value"');""===a&&(d.emptyOption=b);var g=c.get(a)||0;c.put(a,g+1);d.ngModelCtrl.$render();b[0].hasAttribute("selected")&&(b[0].selected=!0)}};d.removeOption=function(a){var b=c.get(a);b&&(1===b?(c.remove(a),""===a&&(d.emptyOption=
void 0)):c.put(a,b-1))};d.hasOption=function(a){return!!c.get(a)};d.registerOption=function(a,b,c,h,k){if(h){var l;c.$observe("value",function(a){w(l)&&d.removeOption(l);l=a;d.addOption(a,b)})}else k?a.$watch(k,function(a,e){c.$set("value",a);e!==a&&d.removeOption(e);d.addOption(a,b)}):d.addOption(c.value,b);b.on("$destroy",function(){d.removeOption(c.value);d.ngModelCtrl.$render()})}}],re=function(){return{restrict:"E",require:["select","?ngModel"],controller:Vg,priority:1,link:{pre:function(a,b,
d,c){var e=c[1];if(e){var f=c[0];f.ngModelCtrl=e;b.on("change",function(){a.$apply(function(){e.$setViewValue(f.readValue())})});if(d.multiple){f.readValue=function(){var a=[];q(b.find("option"),function(b){b.selected&&a.push(b.value)});return a};f.writeValue=function(a){var c=new Ra(a);q(b.find("option"),function(a){a.selected=w(c.get(a.value))})};var g,h=NaN;a.$watch(function(){h!==e.$viewValue||na(g,e.$viewValue)||(g=ia(e.$viewValue),e.$render());h=e.$viewValue});e.$isEmpty=function(a){return!a||
0===a.length}}}},post:function(a,b,d,c){var e=c[1];if(e){var f=c[0];e.$render=function(){f.writeValue(e.$viewValue)}}}}}},te=["$interpolate",function(a){return{restrict:"E",priority:100,compile:function(b,d){if(w(d.value))var c=a(d.value,!0);else{var e=a(b.text(),!0);e||d.$set("value",b.text())}return function(a,b,d){var k=b.parent();(k=k.data("$selectController")||k.parent().data("$selectController"))&&k.registerOption(a,b,d,c,e)}}}}],se=ha({restrict:"E",terminal:!1}),Ic=function(){return{restrict:"A",
require:"?ngModel",link:function(a,b,d,c){c&&(d.required=!0,c.$validators.required=function(a,b){return!d.required||!c.$isEmpty(b)},d.$observe("required",function(){c.$validate()}))}}},Hc=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var e,f=d.ngPattern||d.pattern;d.$observe("pattern",function(a){G(a)&&0<a.length&&(a=new RegExp("^"+a+"$"));if(a&&!a.test)throw N("ngPattern")("noregexp",f,a,ya(b));e=a||void 0;c.$validate()});c.$validators.pattern=function(a,b){return c.$isEmpty(b)||
y(e)||e.test(b)}}}}},Kc=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var e=-1;d.$observe("maxlength",function(a){a=Z(a);e=isNaN(a)?-1:a;c.$validate()});c.$validators.maxlength=function(a,b){return 0>e||c.$isEmpty(b)||b.length<=e}}}}},Jc=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var e=0;d.$observe("minlength",function(a){e=Z(a)||0;c.$validate()});c.$validators.minlength=function(a,b){return c.$isEmpty(b)||b.length>=e}}}}};C.angular.bootstrap?
C.console&&console.log("WARNING: Tried to load angular more than once."):(je(),le(ca),ca.module("ngLocale",[],["$provide",function(a){function b(a){a+="";var b=a.indexOf(".");return-1==b?0:a.length-b-1}a.value("$locale",{DATETIME_FORMATS:{AMPMS:["AM","PM"],DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),ERANAMES:["Before Christ","Anno Domini"],ERAS:["BC","AD"],FIRSTDAYOFWEEK:6,MONTH:"January February March April May June July August September October November December".split(" "),
SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),STANDALONEMONTH:"January February March April May June July August September October November December".split(" "),WEEKENDRANGE:[5,6],fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",medium:"MMM d, y h:mm:ss a",mediumDate:"MMM d, y",mediumTime:"h:mm:ss a","short":"M/d/yy h:mm a",shortDate:"M/d/yy",shortTime:"h:mm a"},NUMBER_FORMATS:{CURRENCY_SYM:"$",DECIMAL_SEP:".",GROUP_SEP:",",
PATTERNS:[{gSize:3,lgSize:3,maxFrac:3,minFrac:0,minInt:1,negPre:"-",negSuf:"",posPre:"",posSuf:""},{gSize:3,lgSize:3,maxFrac:2,minFrac:2,minInt:1,negPre:"-\u00a4",negSuf:"",posPre:"\u00a4",posSuf:""}]},id:"en-us",localeID:"en_US",pluralCat:function(a,c){var e=a|0,f=c;void 0===f&&(f=Math.min(b(a),3));Math.pow(10,f);return 1==e&&0==f?"one":"other"}})}]),F(C.document).ready(function(){fe(C.document,Bc)}))})(window);!window.angular.$$csp().noInlineStyle&&window.angular.element(document.head).prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}</style>');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,231 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/json"
"fmt"
"io"
"sync"
"time"
"github.com/fatedier/frp/src/models/client"
"github.com/fatedier/frp/src/models/consts"
"github.com/fatedier/frp/src/models/msg"
"github.com/fatedier/frp/src/utils/conn"
"github.com/fatedier/frp/src/utils/log"
"github.com/fatedier/frp/src/utils/pcrypto"
)
func ControlProcess(cli *client.ProxyClient, wait *sync.WaitGroup) {
defer wait.Done()
msgSendChan := make(chan interface{}, 1024)
c, err := loginToServer(cli)
if err != nil {
log.Error("ProxyName [%s], connect to server failed!", cli.Name)
return
}
defer c.Close()
go heartbeatSender(c, msgSendChan)
go msgSender(cli, c, msgSendChan)
msgReader(cli, c, msgSendChan)
close(msgSendChan)
}
// loop for reading messages from frpc after control connection is established
func msgReader(cli *client.ProxyClient, c *conn.Conn, msgSendChan chan interface{}) error {
// for heartbeat
var heartbeatTimeout bool = false
timer := time.AfterFunc(time.Duration(client.HeartBeatTimeout)*time.Second, func() {
heartbeatTimeout = true
if c != nil {
c.Close()
}
if cli != nil {
// if it's not udp type, nothing will happen
cli.CloseUdpTunnel()
cli.SetCloseFlag(true)
}
log.Error("ProxyName [%s], heartbeatRes from frps timeout", cli.Name)
})
defer timer.Stop()
for {
buf, err := c.ReadLine()
if err == io.EOF || c.IsClosed() {
timer.Stop()
c.Close()
cli.SetCloseFlag(true)
log.Warn("ProxyName [%s], frps close this control conn!", cli.Name)
var delayTime time.Duration = 1
// loop until reconnect to frps
for {
log.Info("ProxyName [%s], try to reconnect to frps [%s:%d]...", cli.Name, client.ServerAddr, client.ServerPort)
c, err = loginToServer(cli)
if err == nil {
close(msgSendChan)
msgSendChan = make(chan interface{}, 1024)
go heartbeatSender(c, msgSendChan)
go msgSender(cli, c, msgSendChan)
cli.SetCloseFlag(false)
break
}
if delayTime < 30 {
delayTime = delayTime * 2
} else {
delayTime = 30
}
time.Sleep(delayTime * time.Second)
}
continue
} else if err != nil {
log.Warn("ProxyName [%s], read from frps error: %v", cli.Name, err)
continue
}
ctlRes := &msg.ControlRes{}
if err := json.Unmarshal([]byte(buf), &ctlRes); err != nil {
log.Warn("ProxyName [%s], parse msg from frps error: %v : %s", cli.Name, err, buf)
continue
}
switch ctlRes.Type {
case consts.HeartbeatRes:
log.Debug("ProxyName [%s], receive heartbeat response", cli.Name)
timer.Reset(time.Duration(client.HeartBeatTimeout) * time.Second)
case consts.NoticeUserConn:
log.Debug("ProxyName [%s], new user connection", cli.Name)
// join local and remote connections, async
go cli.StartTunnel(client.ServerAddr, client.ServerPort)
default:
log.Warn("ProxyName [%s}, unsupport msgType [%d]", cli.Name, ctlRes.Type)
}
}
return nil
}
// loop for sending messages from channel to frps
func msgSender(cli *client.ProxyClient, c *conn.Conn, msgSendChan chan interface{}) {
for {
msg, ok := <-msgSendChan
if !ok {
break
}
buf, _ := json.Marshal(msg)
err := c.WriteString(string(buf) + "\n")
if err != nil {
log.Warn("ProxyName [%s], write to server error, proxy exit", cli.Name)
c.Close()
break
}
}
}
func loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) {
if client.HttpProxy == "" {
c, err = conn.ConnectServer(fmt.Sprintf("%s:%d", client.ServerAddr, client.ServerPort))
} else {
c, err = conn.ConnectServerByHttpProxy(client.HttpProxy, fmt.Sprintf("%s:%d", client.ServerAddr, client.ServerPort))
}
if err != nil {
log.Error("ProxyName [%s], connect to server [%s:%d] error, %v", cli.Name, client.ServerAddr, client.ServerPort, err)
return
}
nowTime := time.Now().Unix()
req := &msg.ControlReq{
Type: consts.NewCtlConn,
ProxyName: cli.Name,
UseEncryption: cli.UseEncryption,
UseGzip: cli.UseGzip,
PrivilegeMode: cli.PrivilegeMode,
ProxyType: cli.Type,
PoolCount: cli.PoolCount,
HostHeaderRewrite: cli.HostHeaderRewrite,
HttpUserName: cli.HttpUserName,
HttpPassWord: cli.HttpPassWord,
SubDomain: cli.SubDomain,
Timestamp: nowTime,
}
if cli.PrivilegeMode {
privilegeKey := pcrypto.GetAuthKey(cli.Name + client.PrivilegeToken + fmt.Sprintf("%d", nowTime))
req.RemotePort = cli.RemotePort
req.CustomDomains = cli.CustomDomains
req.Locations = cli.Locations
req.PrivilegeKey = privilegeKey
} else {
authKey := pcrypto.GetAuthKey(cli.Name + cli.AuthToken + fmt.Sprintf("%d", nowTime))
req.AuthKey = authKey
}
buf, _ := json.Marshal(req)
err = c.WriteString(string(buf) + "\n")
if err != nil {
log.Error("ProxyName [%s], write to server error, %v", cli.Name, err)
return
}
res, err := c.ReadLine()
if err != nil {
log.Error("ProxyName [%s], read from server error, %v", cli.Name, err)
return
}
log.Debug("ProxyName [%s], read [%s]", cli.Name, res)
ctlRes := &msg.ControlRes{}
if err = json.Unmarshal([]byte(res), &ctlRes); err != nil {
log.Error("ProxyName [%s], format server response error, %v", cli.Name, err)
return
}
if ctlRes.Code != 0 {
log.Error("ProxyName [%s], start proxy error, %s", cli.Name, ctlRes.Msg)
return c, fmt.Errorf("%s", ctlRes.Msg)
}
log.Info("ProxyName [%s], connect to server [%s:%d] success!", cli.Name, client.ServerAddr, client.ServerPort)
if cli.Type == "udp" {
// we only need one udp work connection
// all udp messages will be forwarded throngh this connection
go cli.StartUdpTunnelOnce(client.ServerAddr, client.ServerPort)
}
return
}
func heartbeatSender(c *conn.Conn, msgSendChan chan interface{}) {
heartbeatReq := &msg.ControlReq{
Type: consts.HeartbeatReq,
}
log.Info("Start to send heartbeat to frps")
for {
time.Sleep(time.Duration(client.HeartBeatInterval) * time.Second)
if c != nil && !c.IsClosed() {
log.Debug("Send heartbeat to server")
msgSendChan <- heartbeatReq
} else {
break
}
}
log.Info("Heartbeat goroutine exit")
}

View File

@ -1,365 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/json"
"fmt"
"io"
"strings"
"time"
"github.com/fatedier/frp/src/models/consts"
"github.com/fatedier/frp/src/models/metric"
"github.com/fatedier/frp/src/models/msg"
"github.com/fatedier/frp/src/models/server"
"github.com/fatedier/frp/src/utils/conn"
"github.com/fatedier/frp/src/utils/log"
"github.com/fatedier/frp/src/utils/pcrypto"
)
func ProcessControlConn(l *conn.Listener) {
for {
c, err := l.Accept()
if err != nil {
return
}
log.Debug("Get new connection, %v", c.GetRemoteAddr())
go controlWorker(c)
}
}
// connection from every client and server
func controlWorker(c *conn.Conn) {
// if login message type is NewWorkConn, don't close this connection
var closeFlag bool = true
var s *server.ProxyServer
defer func() {
if closeFlag {
c.Close()
if s != nil {
s.Close()
}
}
}()
// get login message
buf, err := c.ReadLine()
if err != nil {
log.Warn("Read error, %v", err)
return
}
log.Debug("Get msg from frpc: %s", buf)
cliReq := &msg.ControlReq{}
if err := json.Unmarshal([]byte(buf), &cliReq); err != nil {
log.Warn("Parse msg from frpc error: %v : %s", err, buf)
return
}
// login when type is NewCtlConn or NewWorkConn
ret, info, s := doLogin(cliReq, c)
// if login type is NewWorkConn, nothing will be send to frpc
if cliReq.Type == consts.NewCtlConn {
cliRes := &msg.ControlRes{
Type: consts.NewCtlConnRes,
Code: ret,
Msg: info,
}
byteBuf, _ := json.Marshal(cliRes)
err = c.WriteString(string(byteBuf) + "\n")
if err != nil {
log.Warn("ProxyName [%s], write to client error, proxy exit", cliReq.ProxyName)
return
}
} else {
if ret == 0 {
closeFlag = false
}
return
}
// if login failed, just return
if ret > 0 {
return
}
// create a channel for sending messages
msgSendChan := make(chan interface{}, 1024)
go msgSender(s, c, msgSendChan)
go noticeUserConn(s, msgSendChan)
// loop for reading control messages from frpc and deal with different types
msgReader(s, c, msgSendChan)
close(msgSendChan)
log.Info("ProxyName [%s], I'm dead!", s.Name)
return
}
// when frps get one new user connection, send NoticeUserConn message to frpc and accept one new WorkConn later
func noticeUserConn(s *server.ProxyServer, msgSendChan chan interface{}) {
for {
closeFlag := s.WaitUserConn()
if closeFlag {
log.Debug("ProxyName [%s], goroutine for noticing user conn is closed", s.Name)
break
}
notice := &msg.ControlRes{
Type: consts.NoticeUserConn,
}
msgSendChan <- notice
log.Debug("ProxyName [%s], notice client to add work conn", s.Name)
}
}
// loop for reading messages from frpc after control connection is established
func msgReader(s *server.ProxyServer, c *conn.Conn, msgSendChan chan interface{}) error {
// for heartbeat
var heartbeatTimeout bool = false
timer := time.AfterFunc(time.Duration(server.HeartBeatTimeout)*time.Second, func() {
heartbeatTimeout = true
s.Close()
log.Error("ProxyName [%s], client heartbeat timeout", s.Name)
})
defer timer.Stop()
for {
buf, err := c.ReadLine()
if err != nil {
if err == io.EOF {
log.Warn("ProxyName [%s], client is dead!", s.Name)
s.Close()
return err
} else if c == nil || c.IsClosed() {
log.Warn("ProxyName [%s], client connection is closed", s.Name)
s.Close()
return err
}
log.Warn("ProxyName [%s], read error: %v", s.Name, err)
continue
}
cliReq := &msg.ControlReq{}
if err := json.Unmarshal([]byte(buf), &cliReq); err != nil {
log.Warn("ProxyName [%s], parse msg from frpc error: %v : %s", s.Name, err, buf)
continue
}
switch cliReq.Type {
case consts.HeartbeatReq:
log.Debug("ProxyName [%s], get heartbeat", s.Name)
timer.Reset(time.Duration(server.HeartBeatTimeout) * time.Second)
heartbeatRes := &msg.ControlRes{
Type: consts.HeartbeatRes,
}
msgSendChan <- heartbeatRes
default:
log.Warn("ProxyName [%s}, unsupport msgType [%d]", s.Name, cliReq.Type)
}
}
return nil
}
// loop for sending messages from channel to frpc
func msgSender(s *server.ProxyServer, c *conn.Conn, msgSendChan chan interface{}) {
for {
msg, ok := <-msgSendChan
if !ok {
break
}
buf, _ := json.Marshal(msg)
err := c.WriteString(string(buf) + "\n")
if err != nil {
log.Warn("ProxyName [%s], write to client error, proxy exit", s.Name)
s.Close()
break
}
}
}
// if success, ret equals 0, otherwise greater than 0
// NewCtlConn
// NewWorkConn
// NewWorkConnUdp
func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string, s *server.ProxyServer) {
ret = 1
// check if PrivilegeMode is enabled
if req.PrivilegeMode && !server.PrivilegeMode {
info = fmt.Sprintf("ProxyName [%s], PrivilegeMode is disabled in frps", req.ProxyName)
log.Warn("info")
return
}
var ok bool
s, ok = server.GetProxyServer(req.ProxyName)
if req.PrivilegeMode && req.Type == consts.NewCtlConn {
log.Debug("ProxyName [%s], doLogin and privilege mode is enabled", req.ProxyName)
} else {
if !ok {
info = fmt.Sprintf("ProxyName [%s] is not exist", req.ProxyName)
log.Warn(info)
return
}
}
// check authKey or privilegeKey
nowTime := time.Now().Unix()
if req.PrivilegeMode {
privilegeKey := pcrypto.GetAuthKey(req.ProxyName + server.PrivilegeToken + fmt.Sprintf("%d", req.Timestamp))
// privilegeKey unavaiable after server.AuthTimeout minutes
if server.AuthTimeout != 0 && nowTime-req.Timestamp > server.AuthTimeout {
info = fmt.Sprintf("ProxyName [%s], privilege mode authorization timeout", req.ProxyName)
log.Warn(info)
return
} else if req.PrivilegeKey != privilegeKey {
info = fmt.Sprintf("ProxyName [%s], privilege mode authorization failed", req.ProxyName)
log.Warn(info)
log.Debug("PrivilegeKey [%s] and get [%s]", privilegeKey, req.PrivilegeKey)
return
}
} else {
authKey := pcrypto.GetAuthKey(req.ProxyName + s.AuthToken + fmt.Sprintf("%d", req.Timestamp))
if server.AuthTimeout != 0 && nowTime-req.Timestamp > server.AuthTimeout {
info = fmt.Sprintf("ProxyName [%s], authorization timeout", req.ProxyName)
log.Warn(info)
return
} else if req.AuthKey != authKey {
info = fmt.Sprintf("ProxyName [%s], authorization failed", req.ProxyName)
log.Warn(info)
log.Debug("AuthKey [%s] and get [%s]", authKey, req.AuthKey)
return
}
}
// control conn
if req.Type == consts.NewCtlConn {
if req.PrivilegeMode {
s = server.NewProxyServerFromCtlMsg(req)
// we check listen_port if privilege_allow_ports are set
// and PrivilegeMode is enabled
if s.Type == "tcp" {
if len(server.PrivilegeAllowPorts) != 0 {
_, ok := server.PrivilegeAllowPorts[s.ListenPort]
if !ok {
info = fmt.Sprintf("ProxyName [%s], remote_port [%d] isn't allowed", req.ProxyName, s.ListenPort)
log.Warn(info)
return
}
}
} else if s.Type == "http" || s.Type == "https" {
for _, domain := range s.CustomDomains {
if server.SubDomainHost != "" && strings.Contains(domain, server.SubDomainHost) {
info = fmt.Sprintf("ProxyName [%s], custom domain [%s] should not belong to subdomain_host [%s]", req.ProxyName, domain, server.SubDomainHost)
log.Warn(info)
return
}
}
if s.SubDomain != "" {
if strings.Contains(s.SubDomain, ".") || strings.Contains(s.SubDomain, "*") {
info = fmt.Sprintf("ProxyName [%s], '.' and '*' is not supported in subdomain", req.ProxyName)
log.Warn(info)
return
}
if server.SubDomainHost == "" {
info = fmt.Sprintf("ProxyName [%s], subdomain is not supported because this feature is not enabled by remote server", req.ProxyName)
log.Warn(info)
return
}
s.SubDomain = s.SubDomain + "." + server.SubDomainHost
}
}
err := server.CreateProxy(s)
if err != nil {
info = fmt.Sprintf("ProxyName [%s], %v", req.ProxyName, err)
log.Warn(info)
return
}
}
// check if vhost_port is set
if s.Type == "http" && server.VhostHttpMuxer == nil {
info = fmt.Sprintf("ProxyName [%s], type [http] not support when vhost_http_port is not set", req.ProxyName)
log.Warn(info)
return
}
if s.Type == "https" && server.VhostHttpsMuxer == nil {
info = fmt.Sprintf("ProxyName [%s], type [https] not support when vhost_https_port is not set", req.ProxyName)
log.Warn(info)
return
}
// set infomations from frpc
s.BindAddr = server.BindAddr
s.UseEncryption = req.UseEncryption
s.UseGzip = req.UseGzip
s.HostHeaderRewrite = req.HostHeaderRewrite
s.HttpUserName = req.HttpUserName
s.HttpPassWord = req.HttpPassWord
if req.PoolCount > server.MaxPoolCount {
s.PoolCount = server.MaxPoolCount
} else if req.PoolCount < 0 {
s.PoolCount = 0
} else {
s.PoolCount = req.PoolCount
}
if s.Status == consts.Working {
info = fmt.Sprintf("ProxyName [%s], already in use", req.ProxyName)
log.Warn(info)
return
}
// update metric's proxy status
metric.SetProxyInfo(s.Name, s.Type, s.BindAddr, s.UseEncryption, s.UseGzip, s.PrivilegeMode, s.CustomDomains, s.Locations, s.ListenPort)
// start proxy and listen for user connections, no block
err := s.Start(c)
if err != nil {
info = fmt.Sprintf("ProxyName [%s], start proxy error: %v", req.ProxyName, err)
log.Warn(info)
return
}
log.Info("ProxyName [%s], start proxy success", req.ProxyName)
if req.PrivilegeMode {
log.Info("ProxyName [%s], created by PrivilegeMode", req.ProxyName)
}
} else if req.Type == consts.NewWorkConn {
// work conn
if s.Status != consts.Working {
log.Warn("ProxyName [%s], is not working when it gets one new work connnection", req.ProxyName)
return
}
// the connection will close after join over
s.RegisterNewWorkConn(c)
} else if req.Type == consts.NewWorkConnUdp {
// work conn for udp
if s.Status != consts.Working {
log.Warn("ProxyName [%s], is not working when it gets one new work connnection for udp", req.ProxyName)
return
}
s.RegisterNewWorkConnUdp(c)
} else {
info = fmt.Sprintf("Unsupport login message type [%d]", req.Type)
log.Warn("Unsupport login message type [%d]", req.Type)
return
}
ret = 0
return
}

View File

@ -1,199 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"strconv"
"strings"
"time"
docopt "github.com/docopt/docopt-go"
"github.com/fatedier/frp/src/assets"
"github.com/fatedier/frp/src/models/server"
"github.com/fatedier/frp/src/utils/conn"
"github.com/fatedier/frp/src/utils/log"
"github.com/fatedier/frp/src/utils/version"
"github.com/fatedier/frp/src/utils/vhost"
)
var usage string = `frps is the server of frp
Usage:
frps [-c config_file] [-L log_file] [--log-level=<log_level>] [--addr=<bind_addr>]
frps [-c config_file] --reload
frps -h | --help
frps -v | --version
Options:
-c config_file set config file
-L log_file set output log file, including console
--log-level=<log_level> set log level: debug, info, warn, error
--addr=<bind_addr> listen addr for client, example: 0.0.0.0:7000
--reload reload ini file and configures in common section won't be changed
-h --help show this screen
-v --version show version
`
func main() {
// the configures parsed from file will be replaced by those from command line if exist
args, err := docopt.Parse(usage, nil, true, version.Full(), false)
if args["-c"] != nil {
server.ConfigFile = args["-c"].(string)
}
err = server.LoadConf(server.ConfigFile)
if err != nil {
fmt.Println(err)
os.Exit(-1)
}
// reload check
if args["--reload"] != nil {
if args["--reload"].(bool) {
req, err := http.NewRequest("GET", "http://"+server.BindAddr+":"+fmt.Sprintf("%d", server.DashboardPort)+"/api/reload", nil)
if err != nil {
fmt.Printf("frps reload error: %v\n", err)
os.Exit(1)
}
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(server.DashboardUsername+":"+server.DashboardPassword))
req.Header.Add("Authorization", authStr)
defaultClient := &http.Client{}
resp, err := defaultClient.Do(req)
if err != nil {
fmt.Printf("frps reload error: %v\n", err)
os.Exit(1)
} else {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("frps reload error: %v\n", err)
os.Exit(1)
}
res := &server.GeneralResponse{}
err = json.Unmarshal(body, &res)
if err != nil {
fmt.Printf("http response error: %s\n", strings.TrimSpace(string(body)))
os.Exit(1)
} else if res.Code != 0 {
fmt.Printf("reload error: %s\n", res.Msg)
os.Exit(1)
}
fmt.Printf("reload success\n")
os.Exit(0)
}
}
}
if args["-L"] != nil {
if args["-L"].(string) == "console" {
server.LogWay = "console"
} else {
server.LogWay = "file"
server.LogFile = args["-L"].(string)
}
}
if args["--log-level"] != nil {
server.LogLevel = args["--log-level"].(string)
}
if args["--addr"] != nil {
addr := strings.Split(args["--addr"].(string), ":")
if len(addr) != 2 {
fmt.Println("--addr format error: example 0.0.0.0:7000")
os.Exit(1)
}
bindPort, err := strconv.ParseInt(addr[1], 10, 64)
if err != nil {
fmt.Println("--addr format error, example 0.0.0.0:7000")
os.Exit(1)
}
server.BindAddr = addr[0]
server.BindPort = bindPort
}
if args["-v"] != nil {
if args["-v"].(bool) {
fmt.Println(version.Full())
os.Exit(0)
}
}
log.InitLog(server.LogWay, server.LogFile, server.LogLevel, server.LogMaxDays)
// init assets
err = assets.Load(server.AssetsDir)
if err != nil {
log.Error("Load assets error: %v", err)
os.Exit(1)
}
l, err := conn.Listen(server.BindAddr, server.BindPort)
if err != nil {
log.Error("Create server listener error, %v", err)
os.Exit(1)
}
// create vhost if VhostHttpPort != 0
if server.VhostHttpPort != 0 {
vhostListener, err := conn.Listen(server.BindAddr, server.VhostHttpPort)
if err != nil {
log.Error("Create vhost http listener error, %v", err)
os.Exit(1)
}
server.VhostHttpMuxer, err = vhost.NewHttpMuxer(vhostListener, 30*time.Second)
if err != nil {
log.Error("Create vhost httpMuxer error, %v", err)
}
}
// create vhost if VhostHttpPort != 0
if server.VhostHttpsPort != 0 {
vhostListener, err := conn.Listen(server.BindAddr, server.VhostHttpsPort)
if err != nil {
log.Error("Create vhost https listener error, %v", err)
os.Exit(1)
}
server.VhostHttpsMuxer, err = vhost.NewHttpsMuxer(vhostListener, 30*time.Second)
if err != nil {
log.Error("Create vhost httpsMuxer error, %v", err)
}
}
// create dashboard web server if DashboardPort is set, so it won't be 0
if server.DashboardPort != 0 {
err := server.RunDashboardServer(server.BindAddr, server.DashboardPort)
if err != nil {
log.Error("Create dashboard web server error, %v", err)
os.Exit(1)
}
}
log.Info("Start frps success")
if server.PrivilegeMode == true {
log.Info("PrivilegeMode is enabled, you should pay more attention to security issues")
}
ProcessControlConn(l)
}

View File

@ -1,186 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"encoding/json"
"fmt"
"sync"
"time"
"github.com/fatedier/frp/src/models/config"
"github.com/fatedier/frp/src/models/consts"
"github.com/fatedier/frp/src/models/msg"
"github.com/fatedier/frp/src/utils/conn"
"github.com/fatedier/frp/src/utils/log"
"github.com/fatedier/frp/src/utils/pcrypto"
)
type ProxyClient struct {
config.BaseConf
LocalIp string
LocalPort int64
RemotePort int64
CustomDomains []string
Locations []string
udpTunnel *conn.Conn
once sync.Once
closeFlag bool
mutex sync.RWMutex
}
// if proxy type is udp, keep a tcp connection for transferring udp packages
func (pc *ProxyClient) StartUdpTunnelOnce(addr string, port int64) {
pc.once.Do(func() {
var err error
var c *conn.Conn
udpProcessor := NewUdpProcesser(nil, pc.LocalIp, pc.LocalPort)
for {
if !pc.IsClosed() && (pc.udpTunnel == nil || pc.udpTunnel.IsClosed()) {
if HttpProxy == "" {
c, err = conn.ConnectServer(fmt.Sprintf("%s:%d", addr, port))
} else {
c, err = conn.ConnectServerByHttpProxy(HttpProxy, fmt.Sprintf("%s:%d", addr, port))
}
if err != nil {
log.Error("ProxyName [%s], udp tunnel connect to server [%s:%d] error, %v", pc.Name, addr, port, err)
time.Sleep(10 * time.Second)
continue
}
log.Info("ProxyName [%s], udp tunnel connect to server [%s:%d] success", pc.Name, addr, port)
nowTime := time.Now().Unix()
req := &msg.ControlReq{
Type: consts.NewWorkConnUdp,
ProxyName: pc.Name,
PrivilegeMode: pc.PrivilegeMode,
Timestamp: nowTime,
}
if pc.PrivilegeMode == true {
req.PrivilegeKey = pcrypto.GetAuthKey(pc.Name + PrivilegeToken + fmt.Sprintf("%d", nowTime))
} else {
req.AuthKey = pcrypto.GetAuthKey(pc.Name + pc.AuthToken + fmt.Sprintf("%d", nowTime))
}
buf, _ := json.Marshal(req)
err = c.WriteString(string(buf) + "\n")
if err != nil {
log.Error("ProxyName [%s], udp tunnel write to server error, %v", pc.Name, err)
c.Close()
time.Sleep(1 * time.Second)
continue
}
pc.mutex.Lock()
pc.udpTunnel = c
udpProcessor.UpdateTcpConn(pc.udpTunnel)
pc.mutex.Unlock()
udpProcessor.Run()
}
time.Sleep(1 * time.Second)
}
})
}
func (pc *ProxyClient) CloseUdpTunnel() {
pc.mutex.RLock()
defer pc.mutex.RUnlock()
if pc.udpTunnel != nil {
pc.udpTunnel.Close()
}
}
func (pc *ProxyClient) GetLocalConn() (c *conn.Conn, err error) {
c, err = conn.ConnectServer(fmt.Sprintf("%s:%d", pc.LocalIp, pc.LocalPort))
if err != nil {
log.Error("ProxyName [%s], connect to local port error, %v", pc.Name, err)
}
return
}
func (pc *ProxyClient) GetRemoteConn(addr string, port int64) (c *conn.Conn, err error) {
defer func() {
if err != nil && c != nil {
c.Close()
}
}()
if HttpProxy == "" {
c, err = conn.ConnectServer(fmt.Sprintf("%s:%d", addr, port))
} else {
c, err = conn.ConnectServerByHttpProxy(HttpProxy, fmt.Sprintf("%s:%d", addr, port))
}
if err != nil {
log.Error("ProxyName [%s], connect to server [%s:%d] error, %v", pc.Name, addr, port, err)
return
}
nowTime := time.Now().Unix()
req := &msg.ControlReq{
Type: consts.NewWorkConn,
ProxyName: pc.Name,
PrivilegeMode: pc.PrivilegeMode,
Timestamp: nowTime,
}
if pc.PrivilegeMode == true {
req.PrivilegeKey = pcrypto.GetAuthKey(pc.Name + PrivilegeToken + fmt.Sprintf("%d", nowTime))
} else {
req.AuthKey = pcrypto.GetAuthKey(pc.Name + pc.AuthToken + fmt.Sprintf("%d", nowTime))
}
buf, _ := json.Marshal(req)
err = c.WriteString(string(buf) + "\n")
if err != nil {
log.Error("ProxyName [%s], write to server error, %v", pc.Name, err)
return
}
err = nil
return
}
func (pc *ProxyClient) StartTunnel(serverAddr string, serverPort int64) (err error) {
localConn, err := pc.GetLocalConn()
if err != nil {
return
}
remoteConn, err := pc.GetRemoteConn(serverAddr, serverPort)
if err != nil {
return
}
// l means local, r means remote
log.Debug("Join two connections, (l[%s] r[%s]) (l[%s] r[%s])", localConn.GetLocalAddr(), localConn.GetRemoteAddr(),
remoteConn.GetLocalAddr(), remoteConn.GetRemoteAddr())
needRecord := false
go msg.JoinMore(localConn, remoteConn, pc.BaseConf, needRecord)
return nil
}
func (pc *ProxyClient) SetCloseFlag(closeFlag bool) {
pc.mutex.Lock()
defer pc.mutex.Unlock()
pc.closeFlag = closeFlag
}
func (pc *ProxyClient) IsClosed() bool {
pc.mutex.RLock()
defer pc.mutex.RUnlock()
return pc.closeFlag
}

View File

@ -1,302 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"fmt"
"os"
"strconv"
"strings"
ini "github.com/vaughan0/go-ini"
)
// common config
var (
ServerAddr string = "0.0.0.0"
ServerPort int64 = 7000
HttpProxy string = ""
LogFile string = "console"
LogWay string = "console"
LogLevel string = "info"
LogMaxDays int64 = 3
PrivilegeToken string = ""
HeartBeatInterval int64 = 10
HeartBeatTimeout int64 = 30
)
var ProxyClients map[string]*ProxyClient = make(map[string]*ProxyClient)
func LoadConf(confFile string) (err error) {
var tmpStr string
var ok bool
conf, err := ini.LoadFile(confFile)
if err != nil {
return err
}
// common
tmpStr, ok = conf.Get("common", "server_addr")
if ok {
ServerAddr = tmpStr
}
tmpStr, ok = conf.Get("common", "server_port")
if ok {
ServerPort, _ = strconv.ParseInt(tmpStr, 10, 64)
}
tmpStr, ok = conf.Get("common", "http_proxy")
if ok {
HttpProxy = tmpStr
} else {
// get http_proxy from env
HttpProxy = os.Getenv("http_proxy")
}
tmpStr, ok = conf.Get("common", "log_file")
if ok {
LogFile = tmpStr
if LogFile == "console" {
LogWay = "console"
} else {
LogWay = "file"
}
}
tmpStr, ok = conf.Get("common", "log_level")
if ok {
LogLevel = tmpStr
}
tmpStr, ok = conf.Get("common", "log_max_days")
if ok {
LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64)
}
tmpStr, ok = conf.Get("common", "privilege_token")
if ok {
PrivilegeToken = tmpStr
}
var authToken string
tmpStr, ok = conf.Get("common", "auth_token")
if ok {
authToken = tmpStr
}
tmpStr, ok = conf.Get("common", "heartbeat_timeout")
if ok {
v, err := strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
return fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
} else {
HeartBeatTimeout = v
}
}
tmpStr, ok = conf.Get("common", "heartbeat_interval")
if ok {
v, err := strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
return fmt.Errorf("Parse conf error: heartbeat_interval is incorrect")
} else {
HeartBeatInterval = v
}
}
if HeartBeatInterval <= 0 {
return fmt.Errorf("Parse conf error: heartbeat_interval is incorrect")
}
if HeartBeatTimeout < HeartBeatInterval {
return fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect, heartbeat_timeout is less than heartbeat_interval")
}
// proxies
for name, section := range conf {
if name != "common" {
proxyClient := &ProxyClient{}
// name
proxyClient.Name = name
// auth_token
proxyClient.AuthToken = authToken
// local_ip
proxyClient.LocalIp, ok = section["local_ip"]
if !ok {
// use 127.0.0.1 as default
proxyClient.LocalIp = "127.0.0.1"
}
// local_port
tmpStr, ok = section["local_port"]
if ok {
proxyClient.LocalPort, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
return fmt.Errorf("Parse conf error: proxy [%s] local_port error", proxyClient.Name)
}
} else {
return fmt.Errorf("Parse conf error: proxy [%s] local_port not found", proxyClient.Name)
}
// type
proxyClient.Type = "tcp"
tmpStr, ok = section["type"]
if ok {
if tmpStr != "tcp" && tmpStr != "http" && tmpStr != "https" && tmpStr != "udp" {
return fmt.Errorf("Parse conf error: proxy [%s] type error", proxyClient.Name)
}
proxyClient.Type = tmpStr
}
// use_encryption
proxyClient.UseEncryption = false
tmpStr, ok = section["use_encryption"]
if ok && tmpStr == "true" {
proxyClient.UseEncryption = true
}
// use_gzip
proxyClient.UseGzip = false
tmpStr, ok = section["use_gzip"]
if ok && tmpStr == "true" {
proxyClient.UseGzip = true
}
if proxyClient.Type == "http" {
// host_header_rewrite
tmpStr, ok = section["host_header_rewrite"]
if ok {
proxyClient.HostHeaderRewrite = tmpStr
}
// http_user
tmpStr, ok = section["http_user"]
if ok {
proxyClient.HttpUserName = tmpStr
}
// http_pwd
tmpStr, ok = section["http_pwd"]
if ok {
proxyClient.HttpPassWord = tmpStr
}
}
if proxyClient.Type == "http" || proxyClient.Type == "https" {
// subdomain
tmpStr, ok = section["subdomain"]
if ok {
proxyClient.SubDomain = tmpStr
}
}
// privilege_mode
proxyClient.PrivilegeMode = false
tmpStr, ok = section["privilege_mode"]
if ok && tmpStr == "true" {
proxyClient.PrivilegeMode = true
}
// pool_count
proxyClient.PoolCount = 0
tmpStr, ok = section["pool_count"]
if ok {
tmpInt, err := strconv.ParseInt(tmpStr, 10, 64)
if err != nil || tmpInt < 0 {
return fmt.Errorf("Parse conf error: proxy [%s] pool_count error", proxyClient.Name)
}
proxyClient.PoolCount = tmpInt
}
// configures used in privilege mode
if proxyClient.PrivilegeMode == true {
if PrivilegeToken == "" {
return fmt.Errorf("Parse conf error: proxy [%s] privilege_token must be set when privilege_mode = true", proxyClient.Name)
} else {
proxyClient.PrivilegeToken = PrivilegeToken
}
if proxyClient.Type == "tcp" || proxyClient.Type == "udp" {
// remote_port
tmpStr, ok = section["remote_port"]
if ok {
proxyClient.RemotePort, err = strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
return fmt.Errorf("Parse conf error: proxy [%s] remote_port error", proxyClient.Name)
}
} else {
return fmt.Errorf("Parse conf error: proxy [%s] remote_port not found", proxyClient.Name)
}
} else if proxyClient.Type == "http" {
// custom_domains
tmpStr, ok = section["custom_domains"]
if ok {
proxyClient.CustomDomains = strings.Split(tmpStr, ",")
for i, domain := range proxyClient.CustomDomains {
proxyClient.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain))
}
}
// subdomain
tmpStr, ok = section["subdomain"]
if ok {
proxyClient.SubDomain = tmpStr
}
if len(proxyClient.CustomDomains) == 0 && proxyClient.SubDomain == "" {
return fmt.Errorf("Parse conf error: proxy [%s] custom_domains and subdomain should set at least one of them when type is http", proxyClient.Name)
}
// locations
tmpStr, ok = section["locations"]
if ok {
proxyClient.Locations = strings.Split(tmpStr, ",")
} else {
proxyClient.Locations = []string{""}
}
} else if proxyClient.Type == "https" {
// custom_domains
tmpStr, ok = section["custom_domains"]
if ok {
proxyClient.CustomDomains = strings.Split(tmpStr, ",")
for i, domain := range proxyClient.CustomDomains {
proxyClient.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain))
}
}
// subdomain
tmpStr, ok = section["subdomain"]
if ok {
proxyClient.SubDomain = tmpStr
}
if len(proxyClient.CustomDomains) == 0 && proxyClient.SubDomain == "" {
return fmt.Errorf("Parse conf error: proxy [%s] custom_domains and subdomain should set at least one of them when type is https", proxyClient.Name)
}
}
}
ProxyClients[proxyClient.Name] = proxyClient
}
}
if len(ProxyClients) == 0 {
return fmt.Errorf("Parse conf error: no proxy config found")
}
return nil
}

View File

@ -1,153 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"fmt"
"io"
"net"
"sync"
"time"
"github.com/fatedier/frp/src/models/msg"
"github.com/fatedier/frp/src/utils/conn"
"github.com/fatedier/frp/src/utils/pool"
)
type UdpProcesser struct {
tcpConn *conn.Conn
closeCh chan struct{}
localAddr string
// cache local udp connections
// key is remoteAddr
localUdpConns map[string]*net.UDPConn
mutex sync.RWMutex
tcpConnMutex sync.RWMutex
}
func NewUdpProcesser(c *conn.Conn, localIp string, localPort int64) *UdpProcesser {
return &UdpProcesser{
tcpConn: c,
closeCh: make(chan struct{}),
localAddr: fmt.Sprintf("%s:%d", localIp, localPort),
localUdpConns: make(map[string]*net.UDPConn),
}
}
func (up *UdpProcesser) UpdateTcpConn(c *conn.Conn) {
up.tcpConnMutex.Lock()
defer up.tcpConnMutex.Unlock()
up.tcpConn = c
}
func (up *UdpProcesser) Run() {
go up.ReadLoop()
}
func (up *UdpProcesser) ReadLoop() {
var (
buf string
err error
)
for {
udpPacket := &msg.UdpPacket{}
// read udp package from frps
buf, err = up.tcpConn.ReadLine()
if err != nil {
if err == io.EOF {
return
} else {
continue
}
}
err = udpPacket.UnPack([]byte(buf))
if err != nil {
continue
}
// write to local udp port
sendConn, ok := up.GetUdpConn(udpPacket.SrcStr)
if !ok {
dstAddr, err := net.ResolveUDPAddr("udp", up.localAddr)
if err != nil {
continue
}
sendConn, err = net.DialUDP("udp", nil, dstAddr)
if err != nil {
continue
}
up.SetUdpConn(udpPacket.SrcStr, sendConn)
}
_, err = sendConn.Write(udpPacket.Content)
if err != nil {
sendConn.Close()
continue
}
if !ok {
go up.Forward(udpPacket, sendConn)
}
}
}
func (up *UdpProcesser) Forward(udpPacket *msg.UdpPacket, singleConn *net.UDPConn) {
addr := udpPacket.SrcStr
defer up.RemoveUdpConn(addr)
buf := pool.GetBuf(2048)
for {
singleConn.SetReadDeadline(time.Now().Add(120 * time.Second))
n, remoteAddr, err := singleConn.ReadFromUDP(buf)
if err != nil {
return
}
// forward to frps
forwardPacket := msg.NewUdpPacket(buf[0:n], remoteAddr, udpPacket.Src)
up.tcpConnMutex.RLock()
err = up.tcpConn.WriteString(string(forwardPacket.Pack()) + "\n")
up.tcpConnMutex.RUnlock()
if err != nil {
return
}
}
}
func (up *UdpProcesser) GetUdpConn(addr string) (singleConn *net.UDPConn, ok bool) {
up.mutex.RLock()
defer up.mutex.RUnlock()
singleConn, ok = up.localUdpConns[addr]
return
}
func (up *UdpProcesser) SetUdpConn(addr string, conn *net.UDPConn) {
up.mutex.Lock()
defer up.mutex.Unlock()
up.localUdpConns[addr] = conn
}
func (up *UdpProcesser) RemoveUdpConn(addr string) {
up.mutex.Lock()
defer up.mutex.Unlock()
if c, ok := up.localUdpConns[addr]; ok {
c.Close()
}
delete(up.localUdpConns, addr)
}

View File

@ -1,227 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package metric
import (
"sort"
"sync"
"time"
"github.com/fatedier/frp/src/models/consts"
)
var (
DailyDataKeepDays int = 7
ServerMetricInfoMap map[string]*ServerMetric
smMutex sync.RWMutex
)
type ServerMetric struct {
Name string `json:"name"`
Type string `json:"type"`
BindAddr string `json:"bind_addr"`
ListenPort int64 `json:"listen_port"`
CustomDomains []string `json:"custom_domains"`
Locations []string `json:"locations"`
Status string `json:"status"`
UseEncryption bool `json:"use_encryption"`
UseGzip bool `json:"use_gzip"`
PrivilegeMode bool `json:"privilege_mode"`
// statistics
CurrentConns int64 `json:"current_conns"`
Daily []*DailyServerStats `json:"daily"`
mutex sync.RWMutex
}
type DailyServerStats struct {
Time string `json:"time"`
FlowIn int64 `json:"flow_in"`
FlowOut int64 `json:"flow_out"`
TotalAcceptConns int64 `json:"total_accept_conns"`
}
// for sort
type ServerMetricList []*ServerMetric
func (l ServerMetricList) Len() int { return len(l) }
func (l ServerMetricList) Less(i, j int) bool { return l[i].Name < l[j].Name }
func (l ServerMetricList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
func init() {
ServerMetricInfoMap = make(map[string]*ServerMetric)
}
func (s *ServerMetric) clone() *ServerMetric {
copy := *s
copy.CustomDomains = make([]string, len(s.CustomDomains))
var i int
for i = range copy.CustomDomains {
copy.CustomDomains[i] = s.CustomDomains[i]
}
copy.Daily = make([]*DailyServerStats, len(s.Daily))
for i = range copy.Daily {
tmpDaily := *s.Daily[i]
copy.Daily[i] = &tmpDaily
}
return &copy
}
func GetAllProxyMetrics() []*ServerMetric {
result := make(ServerMetricList, 0)
smMutex.RLock()
for _, metric := range ServerMetricInfoMap {
metric.mutex.RLock()
tmpMetric := metric.clone()
metric.mutex.RUnlock()
result = append(result, tmpMetric)
}
smMutex.RUnlock()
// sort for result by proxy name
sort.Sort(result)
return result
}
// if proxyName isn't exist, return nil
func GetProxyMetrics(proxyName string) *ServerMetric {
smMutex.RLock()
defer smMutex.RUnlock()
metric, ok := ServerMetricInfoMap[proxyName]
if ok {
metric.mutex.RLock()
tmpMetric := metric.clone()
metric.mutex.RUnlock()
return tmpMetric
} else {
return nil
}
}
func SetProxyInfo(proxyName string, proxyType, bindAddr string,
useEncryption, useGzip, privilegeMode bool, customDomains []string,
locations []string, listenPort int64) {
smMutex.Lock()
info, ok := ServerMetricInfoMap[proxyName]
if !ok {
info = &ServerMetric{}
info.Daily = make([]*DailyServerStats, 0)
}
info.Name = proxyName
info.Type = proxyType
info.UseEncryption = useEncryption
info.UseGzip = useGzip
info.PrivilegeMode = privilegeMode
info.BindAddr = bindAddr
info.ListenPort = listenPort
info.CustomDomains = customDomains
info.Locations = locations
ServerMetricInfoMap[proxyName] = info
smMutex.Unlock()
}
func SetStatus(proxyName string, status int64) {
smMutex.RLock()
metric, ok := ServerMetricInfoMap[proxyName]
smMutex.RUnlock()
if ok {
metric.mutex.Lock()
metric.Status = consts.StatusStr[status]
metric.mutex.Unlock()
}
}
type DealFuncType func(*DailyServerStats)
func DealDailyData(dailyData []*DailyServerStats, fn DealFuncType) (newDailyData []*DailyServerStats) {
now := time.Now().Format("20060102")
dailyLen := len(dailyData)
if dailyLen == 0 {
daily := &DailyServerStats{}
daily.Time = now
fn(daily)
dailyData = append(dailyData, daily)
} else {
daily := dailyData[dailyLen-1]
if daily.Time == now {
fn(daily)
} else {
newDaily := &DailyServerStats{}
newDaily.Time = now
fn(newDaily)
if dailyLen == DailyDataKeepDays {
for i := 0; i < dailyLen-1; i++ {
dailyData[i] = dailyData[i+1]
}
dailyData[dailyLen-1] = newDaily
} else {
dailyData = append(dailyData, newDaily)
}
}
}
return dailyData
}
func OpenConnection(proxyName string) {
smMutex.RLock()
metric, ok := ServerMetricInfoMap[proxyName]
smMutex.RUnlock()
if ok {
metric.mutex.Lock()
metric.CurrentConns++
metric.Daily = DealDailyData(metric.Daily, func(stats *DailyServerStats) {
stats.TotalAcceptConns++
})
metric.mutex.Unlock()
}
}
func CloseConnection(proxyName string) {
smMutex.RLock()
metric, ok := ServerMetricInfoMap[proxyName]
smMutex.RUnlock()
if ok {
metric.mutex.Lock()
metric.CurrentConns--
metric.mutex.Unlock()
}
}
func AddFlowIn(proxyName string, value int64) {
smMutex.RLock()
metric, ok := ServerMetricInfoMap[proxyName]
smMutex.RUnlock()
if ok {
metric.mutex.Lock()
metric.Daily = DealDailyData(metric.Daily, func(stats *DailyServerStats) {
stats.FlowIn += value
})
metric.mutex.Unlock()
}
}
func AddFlowOut(proxyName string, value int64) {
smMutex.RLock()
metric, ok := ServerMetricInfoMap[proxyName]
smMutex.RUnlock()
if ok {
metric.mutex.Lock()
metric.Daily = DealDailyData(metric.Daily, func(stats *DailyServerStats) {
stats.FlowOut += value
})
metric.mutex.Unlock()
}
}

View File

@ -1,49 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
type GeneralRes struct {
Code int64 `json:"code"`
Msg string `json:"msg"`
}
// messages between control connections of frpc and frps
type ControlReq struct {
Type int64 `json:"type"`
ProxyName string `json:"proxy_name"`
AuthKey string `json:"auth_key"`
UseEncryption bool `json:"use_encryption"`
UseGzip bool `json:"use_gzip"`
PoolCount int64 `json:"pool_count"`
// configures used if privilege_mode is enabled
PrivilegeMode bool `json:"privilege_mode"`
PrivilegeKey string `json:"privilege_key"`
ProxyType string `json:"proxy_type"`
RemotePort int64 `json:"remote_port"`
CustomDomains []string `json:"custom_domains, omitempty"`
Locations []string `json:"locations"`
HostHeaderRewrite string `json:"host_header_rewrite"`
HttpUserName string `json:"http_username"`
HttpPassWord string `json:"http_password"`
SubDomain string `json:"subdomain"`
Timestamp int64 `json:"timestamp"`
}
type ControlRes struct {
Type int64 `json:"type"`
Code int64 `json:"code"`
Msg string `json:"msg"`
}

View File

@ -1,257 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"sync"
"github.com/fatedier/frp/src/models/config"
"github.com/fatedier/frp/src/models/metric"
"github.com/fatedier/frp/src/utils/conn"
"github.com/fatedier/frp/src/utils/log"
"github.com/fatedier/frp/src/utils/pcrypto"
"github.com/fatedier/frp/src/utils/pool"
)
// deprecated
// will block until connection close
func Join(c1 *conn.Conn, c2 *conn.Conn) {
var wait sync.WaitGroup
pipe := func(to *conn.Conn, from *conn.Conn) {
defer to.Close()
defer from.Close()
defer wait.Done()
var err error
_, err = io.Copy(to.TcpConn, from.TcpConn)
if err != nil {
log.Warn("join connections error, %v", err)
}
}
wait.Add(2)
go pipe(c1, c2)
go pipe(c2, c1)
wait.Wait()
return
}
// join two connections and do some operations
func JoinMore(c1 io.ReadWriteCloser, c2 io.ReadWriteCloser, conf config.BaseConf, needRecord bool) {
var wait sync.WaitGroup
encryptPipe := func(from io.ReadCloser, to io.WriteCloser) {
defer from.Close()
defer to.Close()
defer wait.Done()
// we don't care about errors here
pipeEncrypt(from, to, conf, needRecord)
}
decryptPipe := func(to io.ReadCloser, from io.WriteCloser) {
defer from.Close()
defer to.Close()
defer wait.Done()
// we don't care about errors here
pipeDecrypt(to, from, conf, needRecord)
}
if needRecord {
metric.OpenConnection(conf.Name)
}
wait.Add(2)
go encryptPipe(c1, c2)
go decryptPipe(c2, c1)
wait.Wait()
if needRecord {
metric.CloseConnection(conf.Name)
}
log.Debug("ProxyName [%s], One tunnel stopped", conf.Name)
return
}
func pkgMsg(data []byte) []byte {
llen := uint32(len(data))
buf := new(bytes.Buffer)
binary.Write(buf, binary.BigEndian, llen)
buf.Write(data)
return buf.Bytes()
}
func unpkgMsg(data []byte) (int, []byte, []byte) {
if len(data) < 4 {
return -1, nil, data
}
llen := int(binary.BigEndian.Uint32(data[0:4]))
// no complete
if len(data) < llen+4 {
return -1, nil, data
}
return 0, data[4 : llen+4], data[llen+4:]
}
// decrypt msg from reader, then write into writer
func pipeDecrypt(r io.Reader, w io.Writer, conf config.BaseConf, needRecord bool) (err error) {
laes := new(pcrypto.Pcrypto)
key := conf.AuthToken
if conf.PrivilegeMode {
key = conf.PrivilegeToken
}
if err := laes.Init([]byte(key)); err != nil {
log.Warn("ProxyName [%s], Pcrypto Init error: %v", conf.Name, err)
return fmt.Errorf("Pcrypto Init error: %v", err)
}
// get []byte from buffer pool
buf := pool.GetBuf(5*1024 + 4)
defer pool.PutBuf(buf)
var left, res []byte
var cnt int = -1
// record
var flowBytes int64 = 0
if needRecord {
defer func() {
metric.AddFlowOut(conf.Name, flowBytes)
}()
}
for {
// there may be more than 1 package in variable
// and we read more bytes if unpkgMsg returns an error
var newBuf []byte
if cnt < 0 {
n, err := r.Read(buf)
if err != nil {
return err
}
newBuf = append(left, buf[0:n]...)
} else {
newBuf = left
}
cnt, res, left = unpkgMsg(newBuf)
if cnt < 0 {
// limit one package length, maximum is 1MB
if len(res) > 1024*1024 {
log.Warn("ProxyName [%s], package length exceeds the limit")
return fmt.Errorf("package length error")
}
continue
}
// aes
if conf.UseEncryption {
res, err = laes.Decrypt(res)
if err != nil {
log.Warn("ProxyName [%s], decrypt error, %v", conf.Name, err)
return fmt.Errorf("Decrypt error: %v", err)
}
}
// gzip
if conf.UseGzip {
res, err = laes.Decompression(res)
if err != nil {
log.Warn("ProxyName [%s], decompression error, %v", conf.Name, err)
return fmt.Errorf("Decompression error: %v", err)
}
}
_, err = w.Write(res)
if err != nil {
return err
}
if needRecord {
flowBytes += int64(len(res))
if flowBytes >= 1024*1024 {
metric.AddFlowOut(conf.Name, flowBytes)
flowBytes = 0
}
}
}
return nil
}
// recvive msg from reader, then encrypt msg into writer
func pipeEncrypt(r io.Reader, w io.Writer, conf config.BaseConf, needRecord bool) (err error) {
laes := new(pcrypto.Pcrypto)
key := conf.AuthToken
if conf.PrivilegeMode {
key = conf.PrivilegeToken
}
if err := laes.Init([]byte(key)); err != nil {
log.Warn("ProxyName [%s], Pcrypto Init error: %v", conf.Name, err)
return fmt.Errorf("Pcrypto Init error: %v", err)
}
// record
var flowBytes int64 = 0
if needRecord {
defer func() {
metric.AddFlowIn(conf.Name, flowBytes)
}()
}
// get []byte from buffer pool
buf := pool.GetBuf(5*1024 + 4)
defer pool.PutBuf(buf)
for {
n, err := r.Read(buf)
if err != nil {
return err
}
if needRecord {
flowBytes += int64(n)
if flowBytes >= 1024*1024 {
metric.AddFlowIn(conf.Name, flowBytes)
flowBytes = 0
}
}
res := buf[0:n]
// gzip
if conf.UseGzip {
res, err = laes.Compression(res)
if err != nil {
log.Warn("ProxyName [%s], compression error: %v", conf.Name, err)
return fmt.Errorf("Compression error: %v", err)
}
}
// aes
if conf.UseEncryption {
res, err = laes.Encrypt(res)
if err != nil {
log.Warn("ProxyName [%s], encrypt error: %v", conf.Name, err)
return fmt.Errorf("Encrypt error: %v", err)
}
}
res = pkgMsg(res)
_, err = w.Write(res)
if err != nil {
return err
}
}
return nil
}

View File

@ -1,72 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
import (
"encoding/base64"
"encoding/json"
"net"
)
type UdpPacket struct {
Content []byte `json:"-"`
Src *net.UDPAddr `json:"-"`
Dst *net.UDPAddr `json:"-"`
EncodeContent string `json:"content"`
SrcStr string `json:"src"`
DstStr string `json:"dst"`
}
func NewUdpPacket(content []byte, src, dst *net.UDPAddr) *UdpPacket {
up := &UdpPacket{
Src: src,
Dst: dst,
EncodeContent: base64.StdEncoding.EncodeToString(content),
SrcStr: src.String(),
DstStr: dst.String(),
}
return up
}
// parse one udp packet struct to bytes
func (up *UdpPacket) Pack() []byte {
b, _ := json.Marshal(up)
return b
}
// parse from bytes to UdpPacket struct
func (up *UdpPacket) UnPack(packet []byte) error {
err := json.Unmarshal(packet, &up)
if err != nil {
return err
}
up.Content, err = base64.StdEncoding.DecodeString(up.EncodeContent)
if err != nil {
return err
}
up.Src, err = net.ResolveUDPAddr("udp", up.SrcStr)
if err != nil {
return err
}
up.Dst, err = net.ResolveUDPAddr("udp", up.DstStr)
if err != nil {
return err
}
return nil
}

View File

@ -1,50 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msg
import (
"net"
"testing"
"github.com/stretchr/testify/assert"
)
var (
content string = "udp packet test"
src string = "1.1.1.1:1000"
dst string = "2.2.2.2:2000"
udpMsg *UdpPacket
)
func init() {
srcAddr, _ := net.ResolveUDPAddr("udp", src)
dstAddr, _ := net.ResolveUDPAddr("udp", dst)
udpMsg = NewUdpPacket([]byte(content), srcAddr, dstAddr)
}
func TestPack(t *testing.T) {
assert := assert.New(t)
msg := udpMsg.Pack()
assert.Equal(string(msg), `{"content":"dWRwIHBhY2tldCB0ZXN0","src":"1.1.1.1:1000","dst":"2.2.2.2:2000"}`)
}
func TestUnpack(t *testing.T) {
assert := assert.New(t)
udpMsg.UnPack([]byte(`{"content":"dWRwIHBhY2tldCB0ZXN0","src":"1.1.1.1:1000","dst":"2.2.2.2:2000"}`))
assert.Equal(content, string(udpMsg.Content))
assert.Equal(src, udpMsg.Src.String())
assert.Equal(dst, udpMsg.Dst.String())
}

View File

@ -1,456 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"fmt"
"strconv"
"strings"
"sync"
ini "github.com/vaughan0/go-ini"
"github.com/fatedier/frp/src/models/consts"
"github.com/fatedier/frp/src/models/metric"
"github.com/fatedier/frp/src/utils/log"
"github.com/fatedier/frp/src/utils/vhost"
)
// common config
var (
ConfigFile string = "./frps.ini"
BindAddr string = "0.0.0.0"
BindPort int64 = 7000
VhostHttpPort int64 = 0 // if VhostHttpPort equals 0, don't listen a public port for http protocol
VhostHttpsPort int64 = 0 // if VhostHttpsPort equals 0, don't listen a public port for https protocol
DashboardPort int64 = 0 // if DashboardPort equals 0, dashboard is not available
DashboardUsername string = "admin"
DashboardPassword string = "admin"
AssetsDir string = ""
LogFile string = "console"
LogWay string = "console" // console or file
LogLevel string = "info"
LogMaxDays int64 = 3
PrivilegeMode bool = false
PrivilegeToken string = ""
AuthTimeout int64 = 900
SubDomainHost string = ""
// if PrivilegeAllowPorts is not nil, tcp proxies which remote port exist in this map can be connected
PrivilegeAllowPorts map[int64]struct{}
MaxPoolCount int64 = 100
HeartBeatTimeout int64 = 30
UserConnTimeout int64 = 10
VhostHttpMuxer *vhost.HttpMuxer
VhostHttpsMuxer *vhost.HttpsMuxer
ProxyServers map[string]*ProxyServer = make(map[string]*ProxyServer) // all proxy servers info and resources
ProxyServersMutex sync.RWMutex
)
func LoadConf(confFile string) (err error) {
err = loadCommonConf(confFile)
if err != nil {
return err
}
// load all proxy server's configure and initialize
// and set ProxyServers map
newProxyServers, err := loadProxyConf(confFile)
if err != nil {
return err
}
for _, proxyServer := range newProxyServers {
proxyServer.Init()
}
ProxyServersMutex.Lock()
ProxyServers = newProxyServers
ProxyServersMutex.Unlock()
return nil
}
func loadCommonConf(confFile string) error {
var tmpStr string
var ok bool
conf, err := ini.LoadFile(confFile)
if err != nil {
return err
}
// common
tmpStr, ok = conf.Get("common", "bind_addr")
if ok {
BindAddr = tmpStr
}
tmpStr, ok = conf.Get("common", "bind_port")
if ok {
v, err := strconv.ParseInt(tmpStr, 10, 64)
if err == nil {
BindPort = v
}
}
tmpStr, ok = conf.Get("common", "vhost_http_port")
if ok {
VhostHttpPort, _ = strconv.ParseInt(tmpStr, 10, 64)
} else {
VhostHttpPort = 0
}
tmpStr, ok = conf.Get("common", "vhost_https_port")
if ok {
VhostHttpsPort, _ = strconv.ParseInt(tmpStr, 10, 64)
} else {
VhostHttpsPort = 0
}
tmpStr, ok = conf.Get("common", "dashboard_port")
if ok {
DashboardPort, _ = strconv.ParseInt(tmpStr, 10, 64)
} else {
DashboardPort = 0
}
tmpStr, ok = conf.Get("common", "dashboard_user")
if ok {
DashboardUsername = tmpStr
}
tmpStr, ok = conf.Get("common", "dashboard_pwd")
if ok {
DashboardPassword = tmpStr
}
tmpStr, ok = conf.Get("common", "assets_dir")
if ok {
AssetsDir = tmpStr
}
tmpStr, ok = conf.Get("common", "log_file")
if ok {
LogFile = tmpStr
if LogFile == "console" {
LogWay = "console"
} else {
LogWay = "file"
}
}
tmpStr, ok = conf.Get("common", "log_level")
if ok {
LogLevel = tmpStr
}
tmpStr, ok = conf.Get("common", "log_max_days")
if ok {
v, err := strconv.ParseInt(tmpStr, 10, 64)
if err == nil {
LogMaxDays = v
}
}
tmpStr, ok = conf.Get("common", "privilege_mode")
if ok {
if tmpStr == "true" {
PrivilegeMode = true
}
}
if PrivilegeMode == true {
tmpStr, ok = conf.Get("common", "privilege_token")
if ok {
if tmpStr == "" {
return fmt.Errorf("Parse conf error: privilege_token can not be null")
}
PrivilegeToken = tmpStr
} else {
return fmt.Errorf("Parse conf error: privilege_token must be set if privilege_mode is enabled")
}
PrivilegeAllowPorts = make(map[int64]struct{})
tmpStr, ok = conf.Get("common", "privilege_allow_ports")
if ok {
// for example: 1000-2000,2001,2002,3000-4000
portRanges := strings.Split(tmpStr, ",")
for _, portRangeStr := range portRanges {
// 1000-2000 or 2001
portArray := strings.Split(portRangeStr, "-")
// lenght: only 1 or 2 is correct
rangeType := len(portArray)
if rangeType == 1 {
singlePort, err := strconv.ParseInt(portArray[0], 10, 64)
if err != nil {
return fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", err)
}
PrivilegeAllowPorts[singlePort] = struct{}{}
} else if rangeType == 2 {
min, err := strconv.ParseInt(portArray[0], 10, 64)
if err != nil {
return fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", err)
}
max, err := strconv.ParseInt(portArray[1], 10, 64)
if err != nil {
return fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", err)
}
if max < min {
return fmt.Errorf("Parse conf error: privilege_allow_ports range incorrect")
}
for i := min; i <= max; i++ {
PrivilegeAllowPorts[i] = struct{}{}
}
} else {
return fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect")
}
}
}
}
tmpStr, ok = conf.Get("common", "max_pool_count")
if ok {
v, err := strconv.ParseInt(tmpStr, 10, 64)
if err == nil && v >= 0 {
MaxPoolCount = v
}
}
tmpStr, ok = conf.Get("common", "authentication_timeout")
if ok {
v, err := strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
return fmt.Errorf("Parse conf error: authentication_timeout is incorrect")
} else {
AuthTimeout = v
}
}
SubDomainHost, ok = conf.Get("common", "subdomain_host")
if ok {
SubDomainHost = strings.ToLower(strings.TrimSpace(SubDomainHost))
}
tmpStr, ok = conf.Get("common", "heartbeat_timeout")
if ok {
v, err := strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
return fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
} else {
HeartBeatTimeout = v
}
}
return nil
}
func loadProxyConf(confFile string) (proxyServers map[string]*ProxyServer, err error) {
var ok bool
proxyServers = make(map[string]*ProxyServer)
conf, err := ini.LoadFile(confFile)
if err != nil {
return proxyServers, err
}
// servers
for name, section := range conf {
if name != "common" {
proxyServer := NewProxyServer()
proxyServer.Name = name
proxyServer.Type, ok = section["type"]
if ok {
if proxyServer.Type != "tcp" && proxyServer.Type != "http" && proxyServer.Type != "https" && proxyServer.Type != "udp" {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] type error", proxyServer.Name)
}
} else {
proxyServer.Type = "tcp"
}
proxyServer.AuthToken, ok = section["auth_token"]
if !ok {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] no auth_token found", proxyServer.Name)
}
// for tcp and udp
if proxyServer.Type == "tcp" || proxyServer.Type == "udp" {
proxyServer.BindAddr, ok = section["bind_addr"]
if !ok {
proxyServer.BindAddr = "0.0.0.0"
}
portStr, ok := section["listen_port"]
if ok {
proxyServer.ListenPort, err = strconv.ParseInt(portStr, 10, 64)
if err != nil {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] listen_port error", proxyServer.Name)
}
} else {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] listen_port not found", proxyServer.Name)
}
} else if proxyServer.Type == "http" {
// for http
proxyServer.ListenPort = VhostHttpPort
domainStr, ok := section["custom_domains"]
if ok {
proxyServer.CustomDomains = strings.Split(domainStr, ",")
for i, domain := range proxyServer.CustomDomains {
domain = strings.ToLower(strings.TrimSpace(domain))
// custom domain should not belong to subdomain_host
if SubDomainHost != "" && strings.Contains(domain, SubDomainHost) {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom domain should not belong to subdomain_host", proxyServer.Name)
}
proxyServer.CustomDomains[i] = domain
}
}
// subdomain
subdomainStr, ok := section["subdomain"]
if ok {
if strings.Contains(subdomainStr, ".") || strings.Contains(subdomainStr, "*") {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] '.' and '*' is not supported in subdomain", proxyServer.Name)
}
if SubDomainHost == "" {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] subdomain is not supported because subdomain_host is empty", proxyServer.Name)
}
proxyServer.SubDomain = subdomainStr + "." + SubDomainHost
}
if len(proxyServer.CustomDomains) == 0 && proxyServer.SubDomain == "" {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains and subdomain should set at least one of them when type is http", proxyServer.Name)
}
// locations
locations, ok := section["locations"]
if ok {
proxyServer.Locations = strings.Split(locations, ",")
} else {
proxyServer.Locations = []string{""}
}
} else if proxyServer.Type == "https" {
// for https
proxyServer.ListenPort = VhostHttpsPort
domainStr, ok := section["custom_domains"]
if ok {
proxyServer.CustomDomains = strings.Split(domainStr, ",")
for i, domain := range proxyServer.CustomDomains {
domain = strings.ToLower(strings.TrimSpace(domain))
if SubDomainHost != "" && strings.Contains(domain, SubDomainHost) {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom domain should not belong to subdomain_host", proxyServer.Name)
}
proxyServer.CustomDomains[i] = domain
}
}
// subdomain
subdomainStr, ok := section["subdomain"]
if ok {
if strings.Contains(subdomainStr, ".") || strings.Contains(subdomainStr, "*") {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] '.' and '*' is not supported in subdomain", proxyServer.Name)
}
if SubDomainHost == "" {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] subdomain is not supported because subdomain_host is empty", proxyServer.Name)
}
proxyServer.SubDomain = subdomainStr + "." + SubDomainHost
}
if len(proxyServer.CustomDomains) == 0 && proxyServer.SubDomain == "" {
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains and subdomain should set at least one of them when type is https", proxyServer.Name)
}
}
proxyServers[proxyServer.Name] = proxyServer
}
}
// set metric statistics of all proxies
for name, p := range proxyServers {
metric.SetProxyInfo(name, p.Type, p.BindAddr, p.UseEncryption, p.UseGzip,
p.PrivilegeMode, p.CustomDomains, p.Locations, p.ListenPort)
}
return proxyServers, nil
}
// the function can only reload proxy configures
// common section won't be changed
func ReloadConf(confFile string) (err error) {
loadProxyServers, err := loadProxyConf(confFile)
if err != nil {
return err
}
ProxyServersMutex.Lock()
for name, proxyServer := range loadProxyServers {
oldProxyServer, ok := ProxyServers[name]
if ok {
if !oldProxyServer.Compare(proxyServer) {
oldProxyServer.Close()
proxyServer.Init()
ProxyServers[name] = proxyServer
log.Info("ProxyName [%s] configure change, restart", name)
}
} else {
proxyServer.Init()
ProxyServers[name] = proxyServer
log.Info("ProxyName [%s] is new, init it", name)
}
}
// proxies created by PrivilegeMode won't be deleted
for name, oldProxyServer := range ProxyServers {
_, ok := loadProxyServers[name]
if !ok {
if !oldProxyServer.PrivilegeMode {
oldProxyServer.Close()
delete(ProxyServers, name)
log.Info("ProxyName [%s] deleted, close it", name)
} else {
log.Info("ProxyName [%s] created by PrivilegeMode, won't be closed", name)
}
}
}
ProxyServersMutex.Unlock()
return nil
}
func CreateProxy(s *ProxyServer) error {
ProxyServersMutex.Lock()
defer ProxyServersMutex.Unlock()
oldServer, ok := ProxyServers[s.Name]
if ok {
if oldServer.Status == consts.Working {
return fmt.Errorf("this proxy is already working now")
}
oldServer.Lock()
oldServer.Release()
oldServer.Unlock()
if oldServer.PrivilegeMode {
delete(ProxyServers, s.Name)
}
}
ProxyServers[s.Name] = s
metric.SetProxyInfo(s.Name, s.Type, s.BindAddr, s.UseEncryption, s.UseGzip,
s.PrivilegeMode, s.CustomDomains, s.Locations, s.ListenPort)
return nil
}
func DeleteProxy(proxyName string) {
ProxyServersMutex.Lock()
defer ProxyServersMutex.Unlock()
delete(ProxyServers, proxyName)
}
func GetProxyServer(proxyName string) (p *ProxyServer, ok bool) {
ProxyServersMutex.RLock()
defer ProxyServersMutex.RUnlock()
p, ok = ProxyServers[proxyName]
return
}

View File

@ -1,102 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"encoding/base64"
"fmt"
"net"
"net/http"
"strings"
"time"
"github.com/fatedier/frp/src/assets"
)
var (
httpServerReadTimeout = 10 * time.Second
httpServerWriteTimeout = 10 * time.Second
)
func RunDashboardServer(addr string, port int64) (err error) {
// url router
mux := http.NewServeMux()
// api, see dashboard_api.go
mux.HandleFunc("/api/reload", use(apiReload, basicAuth))
mux.HandleFunc("/api/proxies", apiProxies)
// view, see dashboard_view.go
mux.Handle("/favicon.ico", http.FileServer(assets.FileSystem))
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))
mux.HandleFunc("/", use(viewDashboard, basicAuth))
address := fmt.Sprintf("%s:%d", addr, port)
server := &http.Server{
Addr: address,
Handler: mux,
ReadTimeout: httpServerReadTimeout,
WriteTimeout: httpServerWriteTimeout,
}
if address == "" {
address = ":http"
}
ln, err := net.Listen("tcp", address)
if err != nil {
return err
}
go server.Serve(ln)
return
}
func use(h http.HandlerFunc, middleware ...func(http.HandlerFunc) http.HandlerFunc) http.HandlerFunc {
for _, m := range middleware {
h = m(h)
}
return h
}
func basicAuth(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
if len(s) != 2 {
http.Error(w, "Not authorized", 401)
return
}
b, err := base64.StdEncoding.DecodeString(s[1])
if err != nil {
http.Error(w, err.Error(), 401)
return
}
pair := strings.SplitN(string(b), ":", 2)
if len(pair) != 2 {
http.Error(w, "Not authorized", 401)
return
}
if pair[0] != DashboardUsername || pair[1] != DashboardPassword {
http.Error(w, "Not authorized", 401)
return
}
h.ServeHTTP(w, r)
}
}

View File

@ -1,67 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"encoding/json"
"fmt"
"net/http"
"github.com/fatedier/frp/src/models/metric"
"github.com/fatedier/frp/src/utils/log"
)
type GeneralResponse struct {
Code int64 `json:"code"`
Msg string `json:"msg"`
}
func apiReload(w http.ResponseWriter, r *http.Request) {
var buf []byte
res := &GeneralResponse{}
defer func() {
log.Info("Http response [/api/reload]: %s", string(buf))
}()
log.Info("Http request: [/api/reload]")
err := ReloadConf(ConfigFile)
if err != nil {
res.Code = 2
res.Msg = fmt.Sprintf("%v", err)
log.Error("frps reload error: %v", err)
}
buf, _ = json.Marshal(res)
w.Write(buf)
}
type ProxiesResponse struct {
Code int64 `json:"code"`
Msg string `json:"msg"`
Proxies []*metric.ServerMetric `json:"proxies"`
}
func apiProxies(w http.ResponseWriter, r *http.Request) {
var buf []byte
res := &ProxiesResponse{}
defer func() {
log.Info("Http response [/api/proxies]: code [%d]", res.Code)
}()
log.Info("Http request: [/api/proxies]")
res.Proxies = metric.GetAllProxyMetrics()
buf, _ = json.Marshal(res)
w.Write(buf)
}

View File

@ -1,40 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"html/template"
"net/http"
"github.com/fatedier/frp/src/assets"
"github.com/fatedier/frp/src/models/metric"
"github.com/fatedier/frp/src/utils/log"
)
func viewDashboard(w http.ResponseWriter, r *http.Request) {
metrics := metric.GetAllProxyMetrics()
dashboardTpl, err := assets.ReadFile("index.html")
if err != nil {
http.Error(w, "get dashboard template file error", http.StatusInternalServerError)
return
}
t := template.Must(template.New("index.html").Delims("<<<", ">>>").Parse(dashboardTpl))
err = t.Execute(w, metrics)
if err != nil {
log.Warn("parse template file [index.html] error: %v", err)
http.Error(w, "parse template file error", http.StatusInternalServerError)
}
}

View File

@ -1,484 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"fmt"
"net"
"sync"
"time"
"github.com/fatedier/frp/src/models/config"
"github.com/fatedier/frp/src/models/consts"
"github.com/fatedier/frp/src/models/metric"
"github.com/fatedier/frp/src/models/msg"
"github.com/fatedier/frp/src/utils/conn"
"github.com/fatedier/frp/src/utils/log"
"github.com/fatedier/frp/src/utils/pool"
)
type Listener interface {
Accept() (*conn.Conn, error)
Close() error
}
type ProxyServer struct {
config.BaseConf
BindAddr string
ListenPort int64
CustomDomains []string
Locations []string
Status int64
CtlConn *conn.Conn // control connection with frpc
WorkConnUdp *conn.Conn // work connection for udp
udpConn *net.UDPConn
listeners []Listener // accept new connection from remote users
ctlMsgChan chan int64 // every time accept a new user conn, put "1" to the channel
workConnChan chan *conn.Conn // get new work conns from control goroutine
udpSenderChan chan *msg.UdpPacket
mutex sync.RWMutex
closeChan chan struct{} // close this channel for notifying other goroutines that the proxy is closed
}
func NewProxyServer() (p *ProxyServer) {
p = &ProxyServer{
CustomDomains: make([]string, 0),
Locations: make([]string, 0),
}
return p
}
func NewProxyServerFromCtlMsg(req *msg.ControlReq) (p *ProxyServer) {
p = &ProxyServer{}
p.Name = req.ProxyName
p.Type = req.ProxyType
p.UseEncryption = req.UseEncryption
p.UseGzip = req.UseGzip
p.PrivilegeMode = req.PrivilegeMode
p.PrivilegeToken = PrivilegeToken
p.BindAddr = BindAddr
if p.Type == "tcp" || p.Type == "udp" {
p.ListenPort = req.RemotePort
} else if p.Type == "http" {
p.ListenPort = VhostHttpPort
} else if p.Type == "https" {
p.ListenPort = VhostHttpsPort
}
p.CustomDomains = req.CustomDomains
p.SubDomain = req.SubDomain
p.Locations = req.Locations
p.HostHeaderRewrite = req.HostHeaderRewrite
p.HttpUserName = req.HttpUserName
p.HttpPassWord = req.HttpPassWord
p.Init()
return
}
func (p *ProxyServer) Init() {
p.Lock()
p.Status = consts.Idle
metric.SetStatus(p.Name, p.Status)
p.workConnChan = make(chan *conn.Conn, p.PoolCount+10)
p.ctlMsgChan = make(chan int64, p.PoolCount+10)
p.udpSenderChan = make(chan *msg.UdpPacket, 1024)
p.listeners = make([]Listener, 0)
p.closeChan = make(chan struct{})
p.Unlock()
}
func (p *ProxyServer) Compare(p2 *ProxyServer) bool {
if p.Name != p2.Name || p.AuthToken != p2.AuthToken || p.Type != p2.Type ||
p.BindAddr != p2.BindAddr || p.ListenPort != p2.ListenPort || p.HostHeaderRewrite != p2.HostHeaderRewrite {
return false
}
if len(p.CustomDomains) != len(p2.CustomDomains) {
return false
}
for i, _ := range p.CustomDomains {
if p.CustomDomains[i] != p2.CustomDomains[i] {
return false
}
}
if len(p.Locations) != len(p2.Locations) {
return false
}
for i, _ := range p.Locations {
if p.Locations[i] != p2.Locations[i] {
return false
}
}
return true
}
func (p *ProxyServer) Lock() {
p.mutex.Lock()
}
func (p *ProxyServer) Unlock() {
p.mutex.Unlock()
}
// start listening for user conns
func (p *ProxyServer) Start(c *conn.Conn) (err error) {
p.CtlConn = c
p.Init()
if p.Type == "tcp" {
l, err := conn.Listen(p.BindAddr, p.ListenPort)
if err != nil {
return err
}
p.listeners = append(p.listeners, l)
} else if p.Type == "http" {
for _, domain := range p.CustomDomains {
if len(p.Locations) == 0 {
l, err := VhostHttpMuxer.Listen(domain, "", p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
if err != nil {
return err
}
log.Info("ProxyName [%s], type http listen for host [%s] location [%s]", p.Name, domain, "")
p.listeners = append(p.listeners, l)
} else {
for _, location := range p.Locations {
l, err := VhostHttpMuxer.Listen(domain, location, p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
if err != nil {
return err
}
log.Info("ProxyName [%s], type http listen for host [%s] location [%s]", p.Name, domain, location)
p.listeners = append(p.listeners, l)
}
}
}
if p.SubDomain != "" {
if len(p.Locations) == 0 {
l, err := VhostHttpMuxer.Listen(p.SubDomain, "", p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
if err != nil {
return err
}
log.Info("ProxyName [%s], type http listen for host [%s] location [%s]", p.Name, p.SubDomain, "")
p.listeners = append(p.listeners, l)
} else {
for _, location := range p.Locations {
l, err := VhostHttpMuxer.Listen(p.SubDomain, location, p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
if err != nil {
return err
}
log.Info("ProxyName [%s], type http listen for host [%s] location [%s]", p.Name, p.SubDomain, location)
p.listeners = append(p.listeners, l)
}
}
}
} else if p.Type == "https" {
for _, domain := range p.CustomDomains {
l, err := VhostHttpsMuxer.Listen(domain, "", p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
if err != nil {
return err
}
log.Info("ProxyName [%s], type https listen for host [%s]", p.Name, domain)
p.listeners = append(p.listeners, l)
}
if p.SubDomain != "" {
l, err := VhostHttpsMuxer.Listen(p.SubDomain, "", p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
if err != nil {
return err
}
log.Info("ProxyName [%s], type https listen for host [%s]", p.Name, p.SubDomain)
p.listeners = append(p.listeners, l)
}
}
p.Lock()
p.Status = consts.Working
p.Unlock()
metric.SetStatus(p.Name, p.Status)
if p.Type == "udp" {
// udp is special
p.udpConn, err = conn.ListenUDP(p.BindAddr, p.ListenPort)
if err != nil {
log.Warn("ProxyName [%s], listen udp port error: %v", p.Name, err)
return err
}
go func() {
for {
buf := pool.GetBuf(2048)
n, remoteAddr, err := p.udpConn.ReadFromUDP(buf)
if err != nil {
log.Info("ProxyName [%s], udp listener is closed", p.Name)
return
}
localAddr, _ := net.ResolveUDPAddr("udp", p.udpConn.LocalAddr().String())
udpPacket := msg.NewUdpPacket(buf[0:n], remoteAddr, localAddr)
select {
case p.udpSenderChan <- udpPacket:
default:
log.Warn("ProxyName [%s], udp sender channel is full", p.Name)
}
pool.PutBuf(buf)
}
}()
} else {
// create connection pool if needed
if p.PoolCount > 0 {
go p.connectionPoolManager(p.closeChan)
}
// start a goroutine for every listener to accept user connection
for _, listener := range p.listeners {
go func(l Listener) {
for {
// block
// if listener is closed, err returned
c, err := l.Accept()
if err != nil {
log.Info("ProxyName [%s], listener is closed", p.Name)
return
}
log.Debug("ProxyName [%s], get one new user conn [%s]", p.Name, c.GetRemoteAddr())
if p.Status != consts.Working {
log.Debug("ProxyName [%s] is not working, new user conn close", p.Name)
c.Close()
return
}
go func(userConn *conn.Conn) {
workConn, err := p.getWorkConn()
if err != nil {
return
}
// message will be transferred to another without modifying
// l means local, r means remote
log.Debug("Join two connections, (l[%s] r[%s]) (l[%s] r[%s])", workConn.GetLocalAddr(), workConn.GetRemoteAddr(),
userConn.GetLocalAddr(), userConn.GetRemoteAddr())
needRecord := true
go msg.JoinMore(userConn, workConn, p.BaseConf, needRecord)
}(c)
}
}(listener)
}
}
return nil
}
func (p *ProxyServer) Close() {
p.Lock()
defer p.Unlock()
oldStatus := p.Status
p.Release()
// if the proxy created by PrivilegeMode, delete it when closed
if p.PrivilegeMode && oldStatus == consts.Working {
// NOTE: this will take the global ProxyServerMap's lock
// if we only want to release resources, use Release() instead
DeleteProxy(p.Name)
}
}
func (p *ProxyServer) Release() {
if p.Status != consts.Closed {
p.Status = consts.Closed
for _, l := range p.listeners {
if l != nil {
l.Close()
}
}
if p.ctlMsgChan != nil {
close(p.ctlMsgChan)
p.ctlMsgChan = nil
}
if p.workConnChan != nil {
close(p.workConnChan)
p.workConnChan = nil
}
if p.udpSenderChan != nil {
close(p.udpSenderChan)
p.udpSenderChan = nil
}
if p.closeChan != nil {
close(p.closeChan)
p.closeChan = nil
}
if p.CtlConn != nil {
p.CtlConn.Close()
}
if p.WorkConnUdp != nil {
p.WorkConnUdp.Close()
}
if p.udpConn != nil {
p.udpConn.Close()
p.udpConn = nil
}
}
metric.SetStatus(p.Name, p.Status)
}
func (p *ProxyServer) WaitUserConn() (closeFlag bool) {
closeFlag = false
_, ok := <-p.ctlMsgChan
if !ok {
closeFlag = true
}
return
}
func (p *ProxyServer) RegisterNewWorkConn(c *conn.Conn) {
select {
case p.workConnChan <- c:
default:
log.Debug("ProxyName [%s], workConnChan is full, so close this work connection", p.Name)
c.Close()
}
}
// create a tcp connection for forwarding udp packages
func (p *ProxyServer) RegisterNewWorkConnUdp(c *conn.Conn) {
if p.WorkConnUdp != nil && !p.WorkConnUdp.IsClosed() {
p.WorkConnUdp.Close()
}
p.WorkConnUdp = c
// read
go func() {
var (
buf string
err error
)
for {
buf, err = c.ReadLine()
if err != nil {
log.Warn("ProxyName [%s], work connection for udp closed", p.Name)
return
}
udpPacket := &msg.UdpPacket{}
err = udpPacket.UnPack([]byte(buf))
if err != nil {
log.Warn("ProxyName [%s], unpack udp packet error: %v", p.Name, err)
continue
}
// send to user
_, err = p.udpConn.WriteToUDP(udpPacket.Content, udpPacket.Dst)
if err != nil {
continue
}
}
}()
// write
go func() {
for {
udpPacket, ok := <-p.udpSenderChan
if !ok {
return
}
err := c.WriteString(string(udpPacket.Pack()) + "\n")
if err != nil {
log.Debug("ProxyName [%s], write to work connection for udp error: %v", p.Name, err)
return
}
}
}()
}
// When frps get one user connection, we get one work connection from the pool and return it.
// If no workConn available in the pool, send message to frpc to get one or more
// and wait until it is available.
// return an error if wait timeout
func (p *ProxyServer) getWorkConn() (workConn *conn.Conn, err error) {
var ok bool
// get a work connection from the pool
for {
select {
case workConn, ok = <-p.workConnChan:
if !ok {
err = fmt.Errorf("ProxyName [%s], no work connections available, control is closing", p.Name)
return
}
log.Debug("ProxyName [%s], get work connection from pool", p.Name)
default:
// no work connections available in the poll, send message to frpc to get more
p.ctlMsgChan <- 1
select {
case workConn, ok = <-p.workConnChan:
if !ok {
err = fmt.Errorf("ProxyName [%s], no work connections available, control is closing", p.Name)
return
}
case <-time.After(time.Duration(UserConnTimeout) * time.Second):
log.Warn("ProxyName [%s], timeout trying to get work connection", p.Name)
err = fmt.Errorf("ProxyName [%s], timeout trying to get work connection", p.Name)
return
}
}
// if connection pool is not used, we don't check the status
// function CheckClosed will consume at least 1 millisecond if the connection isn't closed
if p.PoolCount == 0 || !workConn.CheckClosed() {
break
} else {
log.Warn("ProxyName [%s], connection got from pool, but it's already closed", p.Name)
}
}
return
}
func (p *ProxyServer) connectionPoolManager(closeCh <-chan struct{}) {
defer func() {
if r := recover(); r != nil {
log.Warn("ProxyName [%s], connectionPoolManager panic %v", p.Name, r)
}
}()
for {
// check if we need more work connections and send messages to frpc to get more
time.Sleep(time.Duration(2) * time.Second)
select {
// if the channel closed, it means the proxy is closed, so just return
case <-closeCh:
log.Info("ProxyName [%s], connectionPoolManager exit", p.Name)
return
default:
curWorkConnNum := int64(len(p.workConnChan))
diff := p.PoolCount - curWorkConnNum
if diff > 0 {
if diff < p.PoolCount/5 {
diff = p.PoolCount*4/5 + 1
} else if diff < p.PoolCount/2 {
diff = p.PoolCount/4 + 1
} else if diff < p.PoolCount*4/5 {
diff = p.PoolCount/5 + 1
} else {
diff = p.PoolCount/10 + 1
}
if diff+curWorkConnNum > p.PoolCount {
diff = p.PoolCount - curWorkConnNum
}
for i := 0; i < int(diff); i++ {
p.ctlMsgChan <- 1
}
}
}
}
}

View File

@ -1,87 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package broadcast
type Broadcast struct {
listeners []chan interface{}
reg chan (chan interface{})
unreg chan (chan interface{})
in chan interface{}
stop chan int64
stopStatus bool
}
func NewBroadcast() *Broadcast {
b := &Broadcast{
listeners: make([]chan interface{}, 0),
reg: make(chan (chan interface{})),
unreg: make(chan (chan interface{})),
in: make(chan interface{}),
stop: make(chan int64),
stopStatus: false,
}
go func() {
for {
select {
case l := <-b.unreg:
// remove L from b.listeners
// this operation is slow: O(n) but not used frequently
// unlike iterating over listeners
oldListeners := b.listeners
b.listeners = make([]chan interface{}, 0, len(oldListeners))
for _, oldL := range oldListeners {
if l != oldL {
b.listeners = append(b.listeners, oldL)
}
}
case l := <-b.reg:
b.listeners = append(b.listeners, l)
case item := <-b.in:
for _, l := range b.listeners {
l <- item
}
case _ = <-b.stop:
b.stopStatus = true
break
}
}
}()
return b
}
func (b *Broadcast) In() chan interface{} {
return b.in
}
func (b *Broadcast) Reg() chan interface{} {
listener := make(chan interface{})
b.reg <- listener
return listener
}
func (b *Broadcast) UnReg(listener chan interface{}) {
b.unreg <- listener
}
func (b *Broadcast) Close() {
if b.stopStatus == false {
b.stop <- 1
}
}

View File

@ -1,77 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package broadcast
import (
"sync"
"testing"
"time"
)
var (
totalNum int = 5
succNum int = 0
mutex sync.Mutex
)
func TestBroadcast(t *testing.T) {
b := NewBroadcast()
if b == nil {
t.Fatalf("New Broadcast error, nil return")
}
defer b.Close()
var wait sync.WaitGroup
wait.Add(totalNum)
for i := 0; i < totalNum; i++ {
go worker(b, &wait)
}
time.Sleep(1e6 * 20)
msg := "test"
b.In() <- msg
wait.Wait()
if succNum != totalNum {
t.Fatalf("TotalNum %d, FailNum(timeout) %d", totalNum, totalNum-succNum)
}
}
func worker(b *Broadcast, wait *sync.WaitGroup) {
defer wait.Done()
msgChan := b.Reg()
// exit if nothing got in 2 seconds
timeout := make(chan bool, 1)
go func() {
time.Sleep(time.Duration(2) * time.Second)
timeout <- true
}()
select {
case item := <-msgChan:
msg := item.(string)
if msg == "test" {
mutex.Lock()
succNum++
mutex.Unlock()
} else {
break
}
case <-timeout:
break
}
}

View File

@ -1,314 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package conn
import (
"bufio"
"bytes"
"encoding/base64"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strings"
"sync"
"time"
"github.com/fatedier/frp/src/utils/pool"
)
type Listener struct {
addr net.Addr
l *net.TCPListener
accept chan *Conn
closeFlag bool
}
func Listen(bindAddr string, bindPort int64) (l *Listener, err error) {
tcpAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
if err != nil {
return l, err
}
listener, err := net.ListenTCP("tcp", tcpAddr)
if err != nil {
return l, err
}
l = &Listener{
addr: listener.Addr(),
l: listener,
accept: make(chan *Conn),
closeFlag: false,
}
go func() {
for {
conn, err := l.l.AcceptTCP()
if err != nil {
if l.closeFlag {
return
}
continue
}
c := NewConn(conn)
l.accept <- c
}
}()
return l, err
}
// wait util get one new connection or listener is closed
// if listener is closed, err returned
func (l *Listener) Accept() (*Conn, error) {
conn, ok := <-l.accept
if !ok {
return conn, fmt.Errorf("channel close")
}
return conn, nil
}
func (l *Listener) Close() error {
if l.l != nil && l.closeFlag == false {
l.closeFlag = true
l.l.Close()
close(l.accept)
}
return nil
}
// wrap for TCPConn
type Conn struct {
TcpConn net.Conn
Reader *bufio.Reader
buffer *bytes.Buffer
closeFlag bool
mutex sync.RWMutex
}
func NewConn(conn net.Conn) (c *Conn) {
c = &Conn{
TcpConn: conn,
buffer: nil,
closeFlag: false,
}
c.Reader = bufio.NewReader(c.TcpConn)
return
}
func ConnectServer(addr string) (c *Conn, err error) {
servertAddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
return
}
conn, err := net.DialTCP("tcp", nil, servertAddr)
if err != nil {
return
}
c = NewConn(conn)
return c, nil
}
func ConnectServerByHttpProxy(httpProxy string, serverAddr string) (c *Conn, err error) {
var proxyUrl *url.URL
if proxyUrl, err = url.Parse(httpProxy); err != nil {
return
}
var proxyAuth string
if proxyUrl.User != nil {
proxyAuth = "Basic " + base64.StdEncoding.EncodeToString([]byte(proxyUrl.User.String()))
}
if proxyUrl.Scheme != "http" {
err = fmt.Errorf("Proxy URL scheme must be http, not [%s]", proxyUrl.Scheme)
return
}
if c, err = ConnectServer(proxyUrl.Host); err != nil {
return
}
req, err := http.NewRequest("CONNECT", "http://"+serverAddr, nil)
if err != nil {
return
}
if proxyAuth != "" {
req.Header.Set("Proxy-Authorization", proxyAuth)
}
req.Header.Set("User-Agent", "Mozilla/5.0")
req.Write(c.TcpConn)
resp, err := http.ReadResponse(bufio.NewReader(c), req)
if err != nil {
return
}
resp.Body.Close()
if resp.StatusCode != 200 {
err = fmt.Errorf("ConnectServer using proxy error, StatusCode [%d]", resp.StatusCode)
return
}
return
}
// if the tcpConn is different with c.TcpConn
// you should call c.Close() first
func (c *Conn) SetTcpConn(tcpConn net.Conn) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.TcpConn = tcpConn
c.closeFlag = false
c.Reader = bufio.NewReader(c.TcpConn)
}
func (c *Conn) GetRemoteAddr() (addr string) {
return c.TcpConn.RemoteAddr().String()
}
func (c *Conn) GetLocalAddr() (addr string) {
return c.TcpConn.LocalAddr().String()
}
func (c *Conn) Read(p []byte) (n int, err error) {
c.mutex.RLock()
if c.buffer == nil {
c.mutex.RUnlock()
return c.Reader.Read(p)
}
c.mutex.RUnlock()
n, err = c.buffer.Read(p)
if err == io.EOF {
c.mutex.Lock()
c.buffer = nil
c.mutex.Unlock()
var n2 int
n2, err = c.Reader.Read(p[n:])
n += n2
}
return
}
func (c *Conn) ReadLine() (buff string, err error) {
buff, err = c.Reader.ReadString('\n')
if err != nil {
// wsarecv error in windows means connection closed?
if err == io.EOF || strings.Contains(err.Error(), "wsarecv") {
c.mutex.Lock()
c.closeFlag = true
c.mutex.Unlock()
}
}
return buff, err
}
func (c *Conn) Write(content []byte) (n int, err error) {
n, err = c.TcpConn.Write(content)
return
}
func (c *Conn) WriteString(content string) (err error) {
_, err = c.TcpConn.Write([]byte(content))
return err
}
func (c *Conn) AppendReaderBuffer(content []byte) {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.buffer == nil {
c.buffer = bytes.NewBuffer(make([]byte, 0, 2048))
}
c.buffer.Write(content)
}
func (c *Conn) SetDeadline(t time.Time) error {
return c.TcpConn.SetDeadline(t)
}
func (c *Conn) SetReadDeadline(t time.Time) error {
return c.TcpConn.SetReadDeadline(t)
}
func (c *Conn) Close() error {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.TcpConn != nil && c.closeFlag == false {
c.closeFlag = true
c.TcpConn.Close()
}
return nil
}
func (c *Conn) IsClosed() (closeFlag bool) {
c.mutex.RLock()
defer c.mutex.RUnlock()
closeFlag = c.closeFlag
return
}
// when you call this function, you should make sure that
// no bytes were read before
func (c *Conn) CheckClosed() bool {
c.mutex.RLock()
if c.closeFlag {
c.mutex.RUnlock()
return true
}
c.mutex.RUnlock()
tmp := pool.GetBuf(2048)
defer pool.PutBuf(tmp)
err := c.TcpConn.SetReadDeadline(time.Now().Add(time.Millisecond))
if err != nil {
c.Close()
return true
}
n, err := c.TcpConn.Read(tmp)
if err == io.EOF {
return true
}
var tmp2 []byte = make([]byte, 1)
err = c.TcpConn.SetReadDeadline(time.Now().Add(time.Millisecond))
if err != nil {
c.Close()
return true
}
n2, err := c.TcpConn.Read(tmp2)
if err == io.EOF {
return true
}
err = c.TcpConn.SetReadDeadline(time.Time{})
if err != nil {
c.Close()
return true
}
if n > 0 {
c.AppendReaderBuffer(tmp[:n])
}
if n2 > 0 {
c.AppendReaderBuffer(tmp2[:n2])
}
return false
}

View File

@ -1,139 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pcrypto
import (
"bytes"
"compress/gzip"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
)
type Pcrypto struct {
pkey []byte
paes cipher.Block
}
func (pc *Pcrypto) Init(key []byte) error {
var err error
pc.pkey = pkKeyPadding(key)
pc.paes, err = aes.NewCipher(pc.pkey)
return err
}
func (pc *Pcrypto) Encrypt(src []byte) ([]byte, error) {
// aes
src = pKCS5Padding(src, aes.BlockSize)
ciphertext := make([]byte, aes.BlockSize+len(src))
// The IV needs to be unique, but not secure. Therefore it's common to
// include it at the beginning of the ciphertext.
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
blockMode := cipher.NewCBCEncrypter(pc.paes, iv)
blockMode.CryptBlocks(ciphertext[aes.BlockSize:], src)
return ciphertext, nil
}
func (pc *Pcrypto) Decrypt(str []byte) ([]byte, error) {
// aes
ciphertext, err := hex.DecodeString(fmt.Sprintf("%x", str))
if err != nil {
return nil, err
}
if len(ciphertext) < aes.BlockSize {
return nil, fmt.Errorf("ciphertext too short")
}
iv := ciphertext[:aes.BlockSize]
ciphertext = ciphertext[aes.BlockSize:]
if len(ciphertext)%aes.BlockSize != 0 {
return nil, fmt.Errorf("crypto/cipher: ciphertext is not a multiple of the block size")
}
blockMode := cipher.NewCBCDecrypter(pc.paes, iv)
blockMode.CryptBlocks(ciphertext, ciphertext)
return pKCS5UnPadding(ciphertext), nil
}
func (pc *Pcrypto) Compression(src []byte) ([]byte, error) {
var zbuf bytes.Buffer
zwr, err := gzip.NewWriterLevel(&zbuf, gzip.DefaultCompression)
if err != nil {
return nil, err
}
defer zwr.Close()
zwr.Write(src)
zwr.Flush()
return zbuf.Bytes(), nil
}
func (pc *Pcrypto) Decompression(src []byte) ([]byte, error) {
zbuf := bytes.NewBuffer(src)
zrd, err := gzip.NewReader(zbuf)
if err != nil {
return nil, err
}
defer zrd.Close()
str, _ := ioutil.ReadAll(zrd)
return str, nil
}
func pkKeyPadding(key []byte) []byte {
l := len(key)
if l == 16 || l == 24 || l == 32 {
return key
}
if l < 16 {
return append(key, bytes.Repeat([]byte{byte(0)}, 16-l)...)
} else if l < 24 {
return append(key, bytes.Repeat([]byte{byte(0)}, 24-l)...)
} else if l < 32 {
return append(key, bytes.Repeat([]byte{byte(0)}, 32-l)...)
} else {
md5Ctx := md5.New()
md5Ctx.Write(key)
md5Str := md5Ctx.Sum(nil)
return []byte(hex.EncodeToString(md5Str))
}
}
func pKCS5Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func pKCS5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
func GetAuthKey(str string) (authKey string) {
md5Ctx := md5.New()
md5Ctx.Write([]byte(str))
md5Str := md5Ctx.Sum(nil)
return hex.EncodeToString(md5Str)
}

View File

@ -1,77 +0,0 @@
// Copyright 2016 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pcrypto
import (
"testing"
)
var (
pp *Pcrypto
)
func init() {
pp = &Pcrypto{}
pp.Init([]byte("12234567890123451223456789012345321:wq"))
}
func TestEncrypt(t *testing.T) {
testStr := "Test Encrypt!"
res, err := pp.Encrypt([]byte(testStr))
if err != nil {
t.Fatalf("encrypt error: %v", err)
}
res, err = pp.Decrypt([]byte(res))
if err != nil {
t.Fatalf("decrypt error: %v", err)
}
if string(res) != testStr {
t.Fatalf("test encrypt error, from [%s] to [%s]", testStr, string(res))
}
}
func TestCompression(t *testing.T) {
testStr := "Test Compression!"
res, err := pp.Compression([]byte(testStr))
if err != nil {
t.Fatalf("compression error: %v", err)
}
res, err = pp.Decompression(res)
if err != nil {
t.Fatalf("decompression error: %v", err)
}
if string(res) != testStr {
t.Fatalf("test compression error, from [%s] to [%s]", testStr, string(res))
}
}
func BenchmarkEncrypt(b *testing.B) {
testStr := "Test Encrypt!"
for i := 0; i < b.N; i++ {
pp.Encrypt([]byte(testStr))
}
}
func BenchmarkDecrypt(b *testing.B) {
testStr := "Test Encrypt!"
res, _ := pp.Encrypt([]byte(testStr))
for i := 0; i < b.N; i++ {
pp.Decrypt([]byte(res))
}
}

View File

@ -1,17 +0,0 @@
[common]
bind_addr = 0.0.0.0
bind_port = 10700
vhost_http_port = 10710
log_file = ./frps.log
log_level = debug
[echo]
type = tcp
auth_token = 123
bind_addr = 0.0.0.0
listen_port = 10711
[web]
type = http
auth_token = 123
custom_domains = 127.0.0.1

View File

@ -1,45 +0,0 @@
package main
import (
"fmt"
"io"
"github.com/fatedier/frp/src/utils/conn"
)
var (
PORT int64 = 10701
)
func main() {
l, err := conn.Listen("0.0.0.0", PORT)
if err != nil {
fmt.Printf("echo server listen error: %v\n", err)
return
}
for {
c, err := l.Accept()
if err != nil {
fmt.Printf("echo server accept error: %v\n", err)
return
}
go echoWorker(c)
}
}
func echoWorker(c *conn.Conn) {
for {
buff, err := c.ReadLine()
if err == io.EOF {
break
}
if err != nil {
fmt.Printf("echo server read error: %v\n", err)
return
}
c.WriteString(buff)
}
}

View File

@ -1,20 +0,0 @@
package main
import (
"fmt"
"net/http"
)
var (
PORT int64 = 10702
HTTP_RES_STR string = "Hello World"
)
func main() {
http.HandleFunc("/", request)
http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", PORT), nil)
}
func request(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(HTTP_RES_STR))
}

View File

@ -1,34 +0,0 @@
#!/bin/bash
./bin/echo_server &
./bin/http_server &
./../bin/frps -c ./conf/auto_test_frps.ini &
sleep 1
./../bin/frpc -c ./conf/auto_test_frpc.ini &
# wait until proxies are connected
for((i=1; i<15; i++))
do
sleep 1
str=`ss -ant|grep 10700|grep LISTEN`
if [ -z "${str}" ]; then
echo "wait"
continue
fi
str=`ss -ant|grep 10710|grep LISTEN`
if [ -z "${str}" ]; then
echo "wait"
continue
fi
str=`ss -ant|grep 10711|grep LISTEN`
if [ -z "${str}" ]; then
echo "wait"
continue
fi
break
done
sleep 1

View File

@ -1,15 +1,5 @@
#!/bin/bash
pid=`ps aux|grep './bin/echo_server'|grep -v grep|awk {'print $2'}`
if [ -n "${pid}" ]; then
kill ${pid}
fi
pid=`ps aux|grep './bin/http_server'|grep -v grep|awk {'print $2'}`
if [ -n "${pid}" ]; then
kill ${pid}
fi
pid=`ps aux|grep './../bin/frps -c ./conf/auto_test_frps.ini'|grep -v grep|awk {'print $2'}`
if [ -n "${pid}" ]; then
kill ${pid}

View File

@ -4,17 +4,26 @@ server_port = 10700
log_file = ./frpc.log
# debug, info, warn, error
log_level = debug
auth_token = 123
privilege_token = 123456
[echo]
type = tcp
local_ip = 127.0.0.1
local_port = 10701
remote_port = 10711
use_encryption = true
use_gzip = true
use_compression = true
[web]
type = http
local_ip = 127.0.0.1
local_port = 10702
use_gzip = true
use_encryption = true
use_compression = true
custom_domains = 127.0.0.1
[udp]
type = udp
local_ip = 127.0.0.1
local_port = 10703
remote_port = 10712

View File

@ -0,0 +1,7 @@
[common]
bind_addr = 0.0.0.0
bind_port = 10700
vhost_http_port = 10710
log_file = ./frps.log
log_level = debug
privilege_token = 123456

61
tests/echo_server.go Normal file
View File

@ -0,0 +1,61 @@
package tests
import (
"bufio"
"fmt"
"io"
"github.com/fatedier/frp/utils/net"
)
func StartEchoServer() {
l, err := net.ListenTcp("127.0.0.1", 10701)
if err != nil {
fmt.Printf("echo server listen error: %v\n", err)
return
}
for {
c, err := l.Accept()
if err != nil {
fmt.Printf("echo server accept error: %v\n", err)
return
}
go echoWorker(c)
}
}
func StartUdpEchoServer() {
l, err := net.ListenUDP("127.0.0.1", 10703)
if err != nil {
fmt.Printf("udp echo server listen error: %v\n", err)
return
}
for {
c, err := l.Accept()
if err != nil {
fmt.Printf("udp echo server accept error: %v\n", err)
return
}
go echoWorker(c)
}
}
func echoWorker(c net.Conn) {
br := bufio.NewReader(c)
for {
buf, err := br.ReadString('\n')
if err == io.EOF {
break
}
if err != nil {
fmt.Printf("echo server read error: %v\n", err)
return
}
c.Write([]byte(buf + "\n"))
}
}

View File

@ -1,40 +1,52 @@
package test
package tests
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"net"
"net/http"
"strings"
"testing"
"time"
"github.com/fatedier/frp/src/utils/conn"
frpNet "github.com/fatedier/frp/utils/net"
)
var (
ECHO_PORT int64 = 10711
UDP_ECHO_PORT int64 = 10712
HTTP_PORT int64 = 10710
ECHO_TEST_STR string = "Hello World\n"
HTTP_RES_STR string = "Hello World"
)
func init() {
go StartEchoServer()
go StartUdpEchoServer()
go StartHttpServer()
time.Sleep(500 * time.Millisecond)
}
func TestEchoServer(t *testing.T) {
c, err := conn.ConnectServer(fmt.Sprintf("0.0.0.0:%d", ECHO_PORT))
c, err := frpNet.ConnectTcpServer(fmt.Sprintf("127.0.0.1:%d", ECHO_PORT))
if err != nil {
t.Fatalf("connect to echo server error: %v", err)
}
timer := time.Now().Add(time.Duration(5) * time.Second)
c.SetDeadline(timer)
c.WriteString(ECHO_TEST_STR)
c.Write([]byte(ECHO_TEST_STR + "\n"))
buff, err := c.ReadLine()
br := bufio.NewReader(c)
buf, err := br.ReadString('\n')
if err != nil {
t.Fatalf("read from echo server error: %v", err)
}
if ECHO_TEST_STR != buff {
t.Fatalf("content error, send [%s], get [%s]", strings.Trim(ECHO_TEST_STR, "\n"), strings.Trim(buff, "\n"))
if ECHO_TEST_STR != buf {
t.Fatalf("content error, send [%s], get [%s]", strings.Trim(ECHO_TEST_STR, "\n"), strings.Trim(buf, "\n"))
}
}
@ -58,3 +70,28 @@ func TestHttpServer(t *testing.T) {
t.Fatalf("http code from http server error [%d]", res.StatusCode)
}
}
func TestUdpEchoServer(t *testing.T) {
addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:10712")
if err != nil {
t.Fatalf("do udp request error: %v", err)
}
conn, err := net.DialUDP("udp", nil, addr)
if err != nil {
t.Fatalf("dial udp server error: %v", err)
}
defer conn.Close()
_, err = conn.Write([]byte("hello frp\n"))
if err != nil {
t.Fatalf("write to udp server error: %v", err)
}
data := make([]byte, 20)
n, err := conn.Read(data)
if err != nil {
t.Fatalf("read from udp server error: %v", err)
}
if string(bytes.TrimSpace(data[:n])) != "hello frp" {
t.Fatalf("message got from udp server error, get %s", string(data[:n-1]))
}
}

15
tests/http_server.go Normal file
View File

@ -0,0 +1,15 @@
package tests
import (
"fmt"
"net/http"
)
func StartHttpServer() {
http.HandleFunc("/", request)
http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", 10702), nil)
}
func request(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(HTTP_RES_STR))
}

8
tests/run_test.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/bash
./../bin/frps -c ./conf/auto_test_frps.ini &
sleep 1
./../bin/frpc -c ./conf/auto_test_frpc.ini &
# wait until proxies are connected
sleep 2

View File

@ -0,0 +1,41 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package crypto
import (
"bytes"
"io"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCrypto(t *testing.T) {
assert := assert.New(t)
text := "1234567890abcdefghigklmnopqrstuvwxyzeeeeeeeeeeeeeeeeeeeeeewwwwwwwwwwwwwwwwwwwwwwwwwwzzzzzzzzzzzzzzzzzzzzzzzzdddddddddddddddddddddddddddddddddddddrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrllllllllllllllllllllllllllllllllllqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeewwwwwwwwwwwwwwwwwwwwww"
key := "123456"
buffer := bytes.NewBuffer(nil)
encWriter, err := NewWriter(buffer, []byte(key))
assert.NoError(err)
decReader := NewReader(buffer, []byte(key))
encWriter.Write([]byte(text))
c := bytes.NewBuffer(nil)
io.Copy(c, decReader)
assert.Equal(text, string(c.Bytes()))
}

75
utils/crypto/decode.go Normal file
View File

@ -0,0 +1,75 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package crypto
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha1"
"io"
"golang.org/x/crypto/pbkdf2"
)
// NewReader returns a new Reader that decrypts bytes from r
func NewReader(r io.Reader, key []byte) *Reader {
key = pbkdf2.Key(key, []byte(salt), 64, aes.BlockSize, sha1.New)
return &Reader{
r: r,
key: key,
}
}
// Reader is an io.Reader that can read encrypted bytes.
// Now it only supports aes-128-cfb.
type Reader struct {
r io.Reader
dec *cipher.StreamReader
key []byte
iv []byte
err error
}
// Read satisfies the io.Reader interface.
func (r *Reader) Read(p []byte) (nRet int, errRet error) {
if r.err != nil {
return 0, r.err
}
if r.dec == nil {
iv := make([]byte, aes.BlockSize)
if _, errRet = io.ReadFull(r.r, iv); errRet != nil {
return
}
r.iv = iv
block, err := aes.NewCipher(r.key)
if err != nil {
errRet = err
return
}
r.dec = &cipher.StreamReader{
S: cipher.NewCFBDecrypter(block, iv),
R: r.r,
}
}
nRet, errRet = r.dec.Read(p)
if errRet != nil {
r.err = errRet
}
return
}

89
utils/crypto/encode.go Normal file
View File

@ -0,0 +1,89 @@
// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package crypto
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha1"
"io"
"golang.org/x/crypto/pbkdf2"
)
const (
salt = "frp"
)
// NewWriter returns a new Writer that encrypts bytes to w.
func NewWriter(w io.Writer, key []byte) (*Writer, error) {
key = pbkdf2.Key(key, []byte(salt), 64, aes.BlockSize, sha1.New)
// random iv
iv := make([]byte, aes.BlockSize)
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return &Writer{
w: w,
enc: &cipher.StreamWriter{
S: cipher.NewCFBEncrypter(block, iv),
W: w,
},
key: key,
iv: iv,
}, nil
}
// Writer is an io.Writer that can write encrypted bytes.
// Now it only support aes-128-cfb.
type Writer struct {
w io.Writer
enc *cipher.StreamWriter
key []byte
iv []byte
ivSend bool
err error
}
// Write satisfies the io.Writer interface.
func (w *Writer) Write(p []byte) (nRet int, errRet error) {
if w.err != nil {
return 0, w.err
}
// When write is first called, iv will be written to w.w
if !w.ivSend {
w.ivSend = true
_, errRet = w.w.Write(w.iv)
if errRet != nil {
w.err = errRet
return
}
}
nRet, errRet = w.enc.Write(p)
if errRet != nil {
w.err = errRet
}
return
}

View File

@ -12,18 +12,25 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package conn
package errors
import (
"errors"
"fmt"
"net"
)
func ListenUDP(bindAddr string, bindPort int64) (conn *net.UDPConn, err error) {
udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
if err != nil {
return conn, err
}
conn, err = net.ListenUDP("udp", udpAddr)
var (
ErrMsgType = errors.New("message type error")
ErrCtlClosed = errors.New("control is closed")
)
func PanicToError(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("Panic error: %v", r)
}
}()
fn()
return
}

Some files were not shown because too many files have changed in this diff Show More