mirror of
https://github.com/fatedier/frp.git
synced 2026-03-08 10:59:11 +08:00
Compare commits
100 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0903d4121 | ||
|
|
b403e4142b | ||
|
|
46716acd8e | ||
|
|
c7f85bcdd3 | ||
|
|
ddd2acfe9f | ||
|
|
e3bf7e2b2b | ||
|
|
4914472215 | ||
|
|
5d9300c1e9 | ||
|
|
b4a577b0d7 | ||
|
|
32d0ce9ea0 | ||
|
|
2d30a6e8a7 | ||
|
|
740691b080 | ||
|
|
11fe4b1d8b | ||
|
|
c64931fce9 | ||
|
|
d4ecc2218d | ||
|
|
9c0ca8675d | ||
|
|
5cdb84c666 | ||
|
|
060277308b | ||
|
|
31dfd5101f | ||
|
|
4300169041 | ||
|
|
3ab9850871 | ||
|
|
d813b953dd | ||
|
|
1da81ad7d3 | ||
|
|
3b06d771ac | ||
|
|
7f386fc042 | ||
|
|
df8edefa56 | ||
|
|
ecb6ad4885 | ||
|
|
785dcaad44 | ||
|
|
fd3c97a0e9 | ||
|
|
8f5f0b0a9a | ||
|
|
452e02adab | ||
|
|
d2e1cfa5bc | ||
|
|
6dd51e0951 | ||
|
|
e0f2993b70 | ||
|
|
4067591a4d | ||
|
|
926d0b74a9 | ||
|
|
4f49458af0 | ||
|
|
fd6b94908b | ||
|
|
dee4cbd48c | ||
|
|
9a3564f29c | ||
|
|
ac09ba3982 | ||
|
|
a9bf25f255 | ||
|
|
6bc05de58e | ||
|
|
5265b79957 | ||
|
|
fefc0a38c3 | ||
|
|
c387138006 | ||
|
|
36f8beee3d | ||
|
|
366a0c898d | ||
|
|
d747f9207e | ||
|
|
5400366036 | ||
|
|
9dae7ad6fe | ||
|
|
a4e051d494 | ||
|
|
28251a8104 | ||
|
|
e99357da4e | ||
|
|
e580c7b6e6 | ||
|
|
ba74934a1f | ||
|
|
1bad5c6561 | ||
|
|
f968f3eace | ||
|
|
b14441d5cd | ||
|
|
0a50c3bd82 | ||
|
|
ef5702213f | ||
|
|
c5e4b24f8f | ||
|
|
1987a399c1 | ||
|
|
ab6c5c813e | ||
|
|
51eaec14ab | ||
|
|
f3876d69bb | ||
|
|
817f4463f4 | ||
|
|
654981019d | ||
|
|
740fb05b21 | ||
|
|
e8c830e5c8 | ||
|
|
2640c0b570 | ||
|
|
04014bb78f | ||
|
|
150c4beef8 | ||
|
|
5febee6201 | ||
|
|
ee8786a6b3 | ||
|
|
d569a60eff | ||
|
|
14607b352d | ||
|
|
bc7ad2bb20 | ||
|
|
cd59bbdad6 | ||
|
|
f404a0a5ee | ||
|
|
da7c473288 | ||
|
|
ea323084ad | ||
|
|
c680d87edc | ||
|
|
d3c4401473 | ||
|
|
7a9a675d58 | ||
|
|
040841db48 | ||
|
|
f804330dbf | ||
|
|
d39d745e43 | ||
|
|
c10321ead6 | ||
|
|
d7797cbd18 | ||
|
|
0b9d823168 | ||
|
|
deb750652f | ||
|
|
14ba38a1b4 | ||
|
|
f650d3f330 | ||
|
|
2c39719cc0 | ||
|
|
6874688e07 | ||
|
|
fdd7436736 | ||
|
|
0f326449e8 | ||
|
|
7c3e00ed28 | ||
|
|
d5913fc77b |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -25,6 +25,8 @@ _testmain.go
|
||||
|
||||
# Self
|
||||
bin/
|
||||
packages/
|
||||
test/bin/
|
||||
|
||||
# Cache
|
||||
*.swp
|
||||
|
||||
@@ -2,11 +2,12 @@ sudo: false
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.4.2
|
||||
- 1.5.3
|
||||
- 1.5.4
|
||||
- 1.6.3
|
||||
- 1.7rc6
|
||||
|
||||
install:
|
||||
- make
|
||||
|
||||
script:
|
||||
- make test
|
||||
- make alltest
|
||||
|
||||
14
Dockerfile
14
Dockerfile
@@ -1,14 +0,0 @@
|
||||
FROM golang:1.5
|
||||
|
||||
MAINTAINER fatedier
|
||||
|
||||
RUN echo "[common]\nbind_addr = 0.0.0.0\nbind_port = 7000\n[test]\npasswd = 123\nbind_addr = 0.0.0.0\nlisten_port = 80" > /usr/share/frps.ini
|
||||
|
||||
ADD ./ /usr/share/frp/
|
||||
|
||||
RUN cd /usr/share/frp && make
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 7000
|
||||
|
||||
CMD ["/usr/share/frp/bin/frps", "-c", "/usr/share/frps.ini"]
|
||||
16
Godeps/Godeps.json
generated
16
Godeps/Godeps.json
generated
@@ -1,20 +1,26 @@
|
||||
{
|
||||
"ImportPath": "frp",
|
||||
"GoVersion": "go1.4",
|
||||
"ImportPath": "github.com/fatedier/frp",
|
||||
"GoVersion": "go1.6",
|
||||
"GodepVersion": "v74",
|
||||
"Packages": [
|
||||
"./..."
|
||||
"./src/..."
|
||||
],
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/astaxie/beego/logs",
|
||||
"Comment": "v1.5.0-9-gfb7314f",
|
||||
"Rev": "fb7314f8ac86b83ccd34386518d97cf2363e2ae5"
|
||||
"Comment": "v1.6.1-5-g88c5dfa",
|
||||
"Rev": "88c5dfa6ead42e624c2e7d9e04eab6cb2d07412a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docopt/docopt-go",
|
||||
"Comment": "0.6.2",
|
||||
"Rev": "784ddc588536785e7299f7272f39101f7faccc3f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rakyll/statik/fs",
|
||||
"Comment": "v0.1.0",
|
||||
"Rev": "274df120e9065bdd08eb1120e0375e3dc1ae8465"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/vaughan0/go-ini",
|
||||
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
|
||||
|
||||
2
Godeps/_workspace/.gitignore
generated
vendored
2
Godeps/_workspace/.gitignore
generated
vendored
@@ -1,2 +0,0 @@
|
||||
/pkg
|
||||
/bin
|
||||
95
Godeps/_workspace/src/github.com/astaxie/beego/logs/console.go
generated
vendored
95
Godeps/_workspace/src/github.com/astaxie/beego/logs/console.go
generated
vendored
@@ -1,95 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// 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 logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type Brush func(string) string
|
||||
|
||||
func NewBrush(color string) Brush {
|
||||
pre := "\033["
|
||||
reset := "\033[0m"
|
||||
return func(text string) string {
|
||||
return pre + color + "m" + text + reset
|
||||
}
|
||||
}
|
||||
|
||||
var colors = []Brush{
|
||||
NewBrush("1;37"), // Emergency white
|
||||
NewBrush("1;36"), // Alert cyan
|
||||
NewBrush("1;35"), // Critical magenta
|
||||
NewBrush("1;31"), // Error red
|
||||
NewBrush("1;33"), // Warning yellow
|
||||
NewBrush("1;32"), // Notice green
|
||||
NewBrush("1;34"), // Informational blue
|
||||
NewBrush("1;34"), // Debug blue
|
||||
}
|
||||
|
||||
// ConsoleWriter implements LoggerInterface and writes messages to terminal.
|
||||
type ConsoleWriter struct {
|
||||
lg *log.Logger
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
// create ConsoleWriter returning as LoggerInterface.
|
||||
func NewConsole() LoggerInterface {
|
||||
cw := &ConsoleWriter{
|
||||
lg: log.New(os.Stdout, "", log.Ldate|log.Ltime),
|
||||
Level: LevelDebug,
|
||||
}
|
||||
return cw
|
||||
}
|
||||
|
||||
// init console logger.
|
||||
// jsonconfig like '{"level":LevelTrace}'.
|
||||
func (c *ConsoleWriter) Init(jsonconfig string) error {
|
||||
if len(jsonconfig) == 0 {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal([]byte(jsonconfig), c)
|
||||
}
|
||||
|
||||
// write message in console.
|
||||
func (c *ConsoleWriter) WriteMsg(msg string, level int) error {
|
||||
if level > c.Level {
|
||||
return nil
|
||||
}
|
||||
if goos := runtime.GOOS; goos == "windows" {
|
||||
c.lg.Println(msg)
|
||||
return nil
|
||||
}
|
||||
c.lg.Println(colors[level](msg))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// implementing method. empty.
|
||||
func (c *ConsoleWriter) Destroy() {
|
||||
|
||||
}
|
||||
|
||||
// implementing method. empty.
|
||||
func (c *ConsoleWriter) Flush() {
|
||||
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("console", NewConsole)
|
||||
}
|
||||
76
Godeps/_workspace/src/github.com/astaxie/beego/logs/es/es.go
generated
vendored
76
Godeps/_workspace/src/github.com/astaxie/beego/logs/es/es.go
generated
vendored
@@ -1,76 +0,0 @@
|
||||
package es
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/belogik/goes"
|
||||
)
|
||||
|
||||
func NewES() logs.LoggerInterface {
|
||||
cw := &esLogger{
|
||||
Level: logs.LevelDebug,
|
||||
}
|
||||
return cw
|
||||
}
|
||||
|
||||
type esLogger struct {
|
||||
*goes.Connection
|
||||
DSN string `json:"dsn"`
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
// {"dsn":"http://localhost:9200/","level":1}
|
||||
func (el *esLogger) Init(jsonconfig string) error {
|
||||
err := json.Unmarshal([]byte(jsonconfig), el)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if el.DSN == "" {
|
||||
return errors.New("empty dsn")
|
||||
} else if u, err := url.Parse(el.DSN); err != nil {
|
||||
return err
|
||||
} else if u.Path == "" {
|
||||
return errors.New("missing prefix")
|
||||
} else if host, port, err := net.SplitHostPort(u.Host); err != nil {
|
||||
return err
|
||||
} else {
|
||||
conn := goes.NewConnection(host, port)
|
||||
el.Connection = conn
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (el *esLogger) WriteMsg(msg string, level int) error {
|
||||
if level > el.Level {
|
||||
return nil
|
||||
}
|
||||
t := time.Now()
|
||||
vals := make(map[string]interface{})
|
||||
vals["@timestamp"] = t.Format(time.RFC3339)
|
||||
vals["@msg"] = msg
|
||||
d := goes.Document{
|
||||
Index: fmt.Sprintf("%04d.%02d.%02d", t.Year(), t.Month(), t.Day()),
|
||||
Type: "logs",
|
||||
Fields: vals,
|
||||
}
|
||||
_, err := el.Index(d, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (el *esLogger) Destroy() {
|
||||
|
||||
}
|
||||
|
||||
func (el *esLogger) Flush() {
|
||||
|
||||
}
|
||||
|
||||
func init() {
|
||||
logs.Register("es", NewES)
|
||||
}
|
||||
283
Godeps/_workspace/src/github.com/astaxie/beego/logs/file.go
generated
vendored
283
Godeps/_workspace/src/github.com/astaxie/beego/logs/file.go
generated
vendored
@@ -1,283 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// 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 logs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FileLogWriter implements LoggerInterface.
|
||||
// It writes messages by lines limit, file size limit, or time frequency.
|
||||
type FileLogWriter struct {
|
||||
*log.Logger
|
||||
mw *MuxWriter
|
||||
// The opened file
|
||||
Filename string `json:"filename"`
|
||||
|
||||
Maxlines int `json:"maxlines"`
|
||||
maxlines_curlines int
|
||||
|
||||
// Rotate at size
|
||||
Maxsize int `json:"maxsize"`
|
||||
maxsize_cursize int
|
||||
|
||||
// Rotate daily
|
||||
Daily bool `json:"daily"`
|
||||
Maxdays int64 `json:"maxdays"`
|
||||
daily_opendate int
|
||||
|
||||
Rotate bool `json:"rotate"`
|
||||
|
||||
startLock sync.Mutex // Only one log can write to the file
|
||||
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
// an *os.File writer with locker.
|
||||
type MuxWriter struct {
|
||||
sync.Mutex
|
||||
fd *os.File
|
||||
}
|
||||
|
||||
// write to os.File.
|
||||
func (l *MuxWriter) Write(b []byte) (int, error) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
return l.fd.Write(b)
|
||||
}
|
||||
|
||||
// set os.File in writer.
|
||||
func (l *MuxWriter) SetFd(fd *os.File) {
|
||||
if l.fd != nil {
|
||||
l.fd.Close()
|
||||
}
|
||||
l.fd = fd
|
||||
}
|
||||
|
||||
// create a FileLogWriter returning as LoggerInterface.
|
||||
func NewFileWriter() LoggerInterface {
|
||||
w := &FileLogWriter{
|
||||
Filename: "",
|
||||
Maxlines: 1000000,
|
||||
Maxsize: 1 << 28, //256 MB
|
||||
Daily: true,
|
||||
Maxdays: 7,
|
||||
Rotate: true,
|
||||
Level: LevelTrace,
|
||||
}
|
||||
// use MuxWriter instead direct use os.File for lock write when rotate
|
||||
w.mw = new(MuxWriter)
|
||||
// set MuxWriter as Logger's io.Writer
|
||||
w.Logger = log.New(w.mw, "", log.Ldate|log.Ltime)
|
||||
return w
|
||||
}
|
||||
|
||||
// Init file logger with json config.
|
||||
// jsonconfig like:
|
||||
// {
|
||||
// "filename":"logs/beego.log",
|
||||
// "maxlines":10000,
|
||||
// "maxsize":1<<30,
|
||||
// "daily":true,
|
||||
// "maxdays":15,
|
||||
// "rotate":true
|
||||
// }
|
||||
func (w *FileLogWriter) Init(jsonconfig string) error {
|
||||
err := json.Unmarshal([]byte(jsonconfig), w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(w.Filename) == 0 {
|
||||
return errors.New("jsonconfig must have filename")
|
||||
}
|
||||
err = w.startLogger()
|
||||
return err
|
||||
}
|
||||
|
||||
// start file logger. create log file and set to locker-inside file writer.
|
||||
func (w *FileLogWriter) startLogger() error {
|
||||
fd, err := w.createLogFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.mw.SetFd(fd)
|
||||
return w.initFd()
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) docheck(size int) {
|
||||
w.startLock.Lock()
|
||||
defer w.startLock.Unlock()
|
||||
if w.Rotate && ((w.Maxlines > 0 && w.maxlines_curlines >= w.Maxlines) ||
|
||||
(w.Maxsize > 0 && w.maxsize_cursize >= w.Maxsize) ||
|
||||
(w.Daily && time.Now().Day() != w.daily_opendate)) {
|
||||
if err := w.DoRotate(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
w.maxlines_curlines++
|
||||
w.maxsize_cursize += size
|
||||
}
|
||||
|
||||
// write logger message into file.
|
||||
func (w *FileLogWriter) WriteMsg(msg string, level int) error {
|
||||
if level > w.Level {
|
||||
return nil
|
||||
}
|
||||
n := 24 + len(msg) // 24 stand for the length "2013/06/23 21:00:22 [T] "
|
||||
w.docheck(n)
|
||||
w.Logger.Println(msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) createLogFile() (*os.File, error) {
|
||||
// Open the log file
|
||||
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
|
||||
return fd, err
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) initFd() error {
|
||||
fd := w.mw.fd
|
||||
finfo, err := fd.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get stat err: %s\n", err)
|
||||
}
|
||||
w.maxsize_cursize = int(finfo.Size())
|
||||
w.daily_opendate = time.Now().Day()
|
||||
w.maxlines_curlines = 0
|
||||
if finfo.Size() > 0 {
|
||||
count, err := w.lines()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.maxlines_curlines = count
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) lines() (int, error) {
|
||||
fd, err := os.Open(w.Filename)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
buf := make([]byte, 32768) // 32k
|
||||
count := 0
|
||||
lineSep := []byte{'\n'}
|
||||
|
||||
for {
|
||||
c, err := fd.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
return count, err
|
||||
}
|
||||
|
||||
count += bytes.Count(buf[:c], lineSep)
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// DoRotate means it need to write file in new file.
|
||||
// new file name like xx.log.2013-01-01.2
|
||||
func (w *FileLogWriter) DoRotate() error {
|
||||
_, err := os.Lstat(w.Filename)
|
||||
if err == nil { // file exists
|
||||
// Find the next available number
|
||||
num := 1
|
||||
fname := ""
|
||||
for ; err == nil && num <= 999; num++ {
|
||||
fname = w.Filename + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), num)
|
||||
_, err = os.Lstat(fname)
|
||||
}
|
||||
// return error if the last file checked still existed
|
||||
if err == nil {
|
||||
return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.Filename)
|
||||
}
|
||||
|
||||
// block Logger's io.Writer
|
||||
w.mw.Lock()
|
||||
defer w.mw.Unlock()
|
||||
|
||||
fd := w.mw.fd
|
||||
fd.Close()
|
||||
|
||||
// close fd before rename
|
||||
// Rename the file to its newfound home
|
||||
err = os.Rename(w.Filename, fname)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Rotate: %s\n", err)
|
||||
}
|
||||
|
||||
// re-start logger
|
||||
err = w.startLogger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Rotate StartLogger: %s\n", err)
|
||||
}
|
||||
|
||||
go w.deleteOldLog()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) deleteOldLog() {
|
||||
dir := filepath.Dir(w.Filename)
|
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
returnErr = fmt.Errorf("Unable to delete old log '%s', error: %+v", path, r)
|
||||
fmt.Println(returnErr)
|
||||
}
|
||||
}()
|
||||
|
||||
if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.Maxdays) {
|
||||
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.Filename)) {
|
||||
os.Remove(path)
|
||||
}
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// destroy file logger, close file writer.
|
||||
func (w *FileLogWriter) Destroy() {
|
||||
w.mw.fd.Close()
|
||||
}
|
||||
|
||||
// flush file logger.
|
||||
// there are no buffering messages in file logger in memory.
|
||||
// flush file means sync file from disk.
|
||||
func (w *FileLogWriter) Flush() {
|
||||
w.mw.fd.Sync()
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("file", NewFileWriter)
|
||||
}
|
||||
19
Godeps/_workspace/src/github.com/astaxie/beego/utils/captcha/LICENSE
generated
vendored
19
Godeps/_workspace/src/github.com/astaxie/beego/utils/captcha/LICENSE
generated
vendored
@@ -1,19 +0,0 @@
|
||||
Copyright (c) 2011-2014 Dmitry Chestnykh <dmitry@codingrobots.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
54
Makefile
54
Makefile
@@ -1,21 +1,55 @@
|
||||
export PATH := $(GOPATH)/bin:$(PATH)
|
||||
export NEW_GOPATH := $(shell pwd)
|
||||
export GO15VENDOREXPERIMENT := 1
|
||||
|
||||
all: build
|
||||
all: fmt build
|
||||
|
||||
build: godep fmt frps frpc
|
||||
build: frps frpc build_test
|
||||
|
||||
godep:
|
||||
@go get github.com/tools/godep
|
||||
build_test: echo_server http_server
|
||||
|
||||
# compile assets into binary file
|
||||
assets:
|
||||
go get -d github.com/rakyll/statik
|
||||
@go install github.com/rakyll/statik
|
||||
@rm -rf ./src/assets/statik
|
||||
go generate ./src/...
|
||||
|
||||
fmt:
|
||||
GOPATH=$(NEW_GOPATH) godep go fmt ./...
|
||||
go fmt ./src/...
|
||||
@go fmt ./test/echo_server.go
|
||||
@go fmt ./test/http_server.go
|
||||
@go fmt ./test/func_test.go
|
||||
|
||||
frps:
|
||||
GOPATH=$(NEW_GOPATH) godep go build -o bin/frps ./src/frp/cmd/frps
|
||||
go build -o bin/frps ./src/cmd/frps
|
||||
@cp -rf ./src/assets/static ./bin
|
||||
|
||||
frpc:
|
||||
GOPATH=$(NEW_GOPATH) godep go build -o bin/frpc ./src/frp/cmd/frpc
|
||||
go build -o bin/frpc ./src/cmd/frpc
|
||||
|
||||
test:
|
||||
@GOPATH=$(NEW_GOPATH) godep go test -v ./...
|
||||
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
|
||||
|
||||
test: gotest
|
||||
|
||||
gotest:
|
||||
go test -v ./src/...
|
||||
|
||||
alltest:
|
||||
cd ./test && ./run_test.sh && cd -
|
||||
go test -v ./src/...
|
||||
go test -v ./test/func_test.go
|
||||
cd ./test && ./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/...
|
||||
|
||||
12
Makefile.cross-compiles
Normal file
12
Makefile.cross-compiles
Normal file
@@ -0,0 +1,12 @@
|
||||
export PATH := $(GOPATH)/bin:$(PATH)
|
||||
export GO15VENDOREXPERIMENT := 1
|
||||
|
||||
all: build
|
||||
|
||||
build: gox app
|
||||
|
||||
gox:
|
||||
go get github.com/mitchellh/gox
|
||||
|
||||
app:
|
||||
gox -osarch "darwin/386 darwin/amd64 linux/386 linux/amd64 linux/arm windows/386 windows/amd64" ./src/...
|
||||
321
README.md
321
README.md
@@ -6,37 +6,334 @@
|
||||
|
||||
## What is frp?
|
||||
|
||||
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet.
|
||||
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet.Now, it supports tcp, http and https protocol when requests can be forwarded by domains to backward web services.
|
||||
|
||||
## Catalog
|
||||
|
||||
* [What can I do with frp?](#what-can-i-do-with-frp)
|
||||
* [Status](#status)
|
||||
* [Architecture](#architecture)
|
||||
* [Example Usage](#example-usage)
|
||||
* [Communicate with your computer in LAN by SSH](#communicate-with-your-computer-in-lan-by-ssh)
|
||||
* [Visit your web service in LAN by custom domains](#visit-your-web-service-in-lan-by-custom-domains)
|
||||
* [Features](#features)
|
||||
* [Dashboard](#dashboard)
|
||||
* [Authentication](#authentication)
|
||||
* [Encryption and Compression](#encryption-and-compression)
|
||||
* [Reload configures without frps stopped](#reload-configures-without-frps-stopped)
|
||||
* [Privilege Mode](#privilege-mode)
|
||||
* [Port White List](#port-white-list)
|
||||
* [Connection Pool](#connection-pool)
|
||||
* [Rewriting the Host Header](#rewriting-the-host-header)
|
||||
* [Development Plan](#development-plan)
|
||||
* [Contributing](#contributing)
|
||||
* [Contributors](#contributors)
|
||||
|
||||
## 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).
|
||||
|
||||
## Status
|
||||
|
||||
frp is under development and you can try it with latest release version.Master branch for releasing stable version when dev branch for developing.
|
||||
|
||||
**We may change any protocol and can't promise backward compatible before version 1.x.**
|
||||
|
||||
## Quick Start
|
||||
|
||||
Read the [QuickStart](doc/quick_start_en.md) | [使用文档](doc/quick_start_zh.md)
|
||||
**We may change any protocol and can't promise backward compatible.Please check the release log when upgrading.**
|
||||
|
||||
## Architecture
|
||||
|
||||

|
||||

|
||||
|
||||
## What can I do with frp?
|
||||
## Example Usage
|
||||
|
||||
* Expose any http service behind a NAT or firewall to the internet by a server with public IP address.
|
||||
* 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).
|
||||
Firstly, download the latest programs from [Release](https://github.com/fatedier/frp/releases) page according to your os and arch.
|
||||
|
||||
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
|
||||
|
||||
1. Modify frps.ini, configure a reverse proxy named [ssh]:
|
||||
|
||||
```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:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
auth_token = 123
|
||||
|
||||
[ssh]
|
||||
local_port = 22
|
||||
```
|
||||
|
||||
4. Start frpc:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. Connect to server in LAN by ssh assuming that username is test:
|
||||
|
||||
`ssh -oPort=6000 test@x.x.x.x`
|
||||
|
||||
### Visit your web service in LAN by custom domains
|
||||
|
||||
Sometimes we want to expose a local web service behind a NAT network to others for testing with your own domain name and unfortunately we can't resolve a domain name to a local ip.
|
||||
|
||||
Howerver, 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`:
|
||||
|
||||
```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:
|
||||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. Modify frpc.ini and set remote frps server's IP as x.x.x.x. The `local_port` is the port of your web service:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
auth_token = 123
|
||||
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
```
|
||||
|
||||
4. Start frpc:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. Resolve A record of `www.yourdomain.com` to IP `x.x.x.x` or CNAME record to your origin domain.
|
||||
|
||||
6. Now visit your local web service using url `http://www.yourdomain.com:8080`.
|
||||
|
||||
## Features
|
||||
|
||||
### Dashboard
|
||||
|
||||
Check frp's status and proxies's statistics information by Dashboard.
|
||||
|
||||
Configure a port for dashboard to enable this feature:
|
||||
|
||||
```ini
|
||||
[common]
|
||||
dashboard_port = 7500
|
||||
```
|
||||
|
||||
Then visit `http://[server_addr]:7500` to see dashboard.
|
||||
|
||||

|
||||
|
||||
### Authentication
|
||||
|
||||
`auth_token` in frps.ini is configured for each proxy and check for authentication when frpc login in.
|
||||
|
||||
Client that want's to register must set a global `auth_token` equals to frps.ini.
|
||||
|
||||
Note that time duration bewtween frpc and frps mustn't exceed 15 minutes because timestamp is used for authentication.
|
||||
|
||||
### Encryption and Compression
|
||||
|
||||
Defalut value is false, you could decide if the proxy will use encryption or compression whether the type is:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[ssh]
|
||||
type = tcp
|
||||
listen_port = 6000
|
||||
auth_token = 123
|
||||
use_encryption = true
|
||||
use_gzip = true
|
||||
```
|
||||
|
||||
### Reload configures without frps stopped
|
||||
|
||||
If your 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.
|
||||
|
||||
### 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`
|
||||
|
||||
#### Port White List
|
||||
|
||||
`privilege_allow_ports` in frps.ini is used for preventing abuse of ports in privilege mode:
|
||||
|
||||
```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 `,`.
|
||||
|
||||
### 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.
|
||||
|
||||
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
|
||||
# frps.ini
|
||||
[common]
|
||||
max_pool_count = 50
|
||||
```
|
||||
|
||||
2. Enable and specify the number of connection pool:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[ssh]
|
||||
type = tcp
|
||||
local_port = 22
|
||||
pool_count = 10
|
||||
```
|
||||
|
||||
### 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
|
||||
[web]
|
||||
privilege_mode = true
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = test.yourdomain.com
|
||||
host_header_rewrite = dev.yourdomain.com
|
||||
```
|
||||
|
||||
If `host_header_rewrite` is specified, the Host header will be rewritten to match the hostname portion of the forwarding address.
|
||||
|
||||
## Development Plan
|
||||
|
||||
* Support udp protocol.
|
||||
* Support wildcard domain name.
|
||||
* Url router.
|
||||
* 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.
|
||||
|
||||
## Contributing
|
||||
|
||||
Interested in getting involved? We would love to help you!
|
||||
Interested in getting involved? We would like to help you!
|
||||
|
||||
* Take a look at our [issues list](https://github.com/fatedier/frp/issues) and consider submitting a patch
|
||||
* If you have some wanderful ideas, send email to fatedier@gmail.com.
|
||||
|
||||
**Note: We prefer you to give your advise in [issues](https://github.com/fatedier/frp/issues), so others with a same question can search it quickly and we don't need to answer them repeatly.**
|
||||
|
||||
## Contributors
|
||||
|
||||
* [fatedier](https://github.com/fatedier)
|
||||
* [Hurricanezwf](https://github.com/Hurricanezwf)
|
||||
* [vashstorm](https://github.com/vashstorm)
|
||||
* [maodanp](https://github.com/maodanp)
|
||||
|
||||
332
README_zh.md
332
README_zh.md
@@ -4,37 +4,343 @@
|
||||
|
||||
[README](README.md) | [中文文档](README_zh.md)
|
||||
|
||||
>frp 是一个高性能的反向代理应用,可以帮助你轻松的进行内网穿透,对外网提供服务。
|
||||
frp 是一个高性能的反向代理应用,可以帮助您轻松地进行内网穿透,对外网提供服务,支持 tcp, http, https 等协议类型,并且 web 服务支持根据域名进行路由转发。
|
||||
|
||||
## 目录
|
||||
|
||||
* [frp 的作用](#frp-的作用)
|
||||
* [开发状态](#开发状态)
|
||||
* [架构](#架构)
|
||||
* [使用示例](#使用示例)
|
||||
* [通过 ssh 访问公司内网机器](#通过-ssh-访问公司内网机器)
|
||||
* [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务)
|
||||
* [功能说明](#功能说明)
|
||||
* [Dashboard](#dashboard)
|
||||
* [身份验证](#身份验证)
|
||||
* [加密与压缩](#加密与压缩)
|
||||
* [服务器端热加载配置文件](#服务器端热加载配置文件)
|
||||
* [特权模式](#特权模式)
|
||||
* [端口白名单](#端口白名单)
|
||||
* [连接池](#连接池)
|
||||
* [修改 Host Header](#修改-host-header)
|
||||
* [开发计划](#开发计划)
|
||||
* [贡献代码](#贡献代码)
|
||||
* [贡献者](#贡献者)
|
||||
|
||||
## frp 的作用
|
||||
|
||||
* 利用处于内网或防火墙后的机器,对外网环境提供 http 或 https 服务。
|
||||
* 对于 http 服务支持基于域名的虚拟主机,支持自定义域名绑定,使多个域名可以共用一个80端口。
|
||||
* 利用处于内网或防火墙后的机器,对外网环境提供 tcp 服务,例如在家里通过 ssh 访问处于公司内网环境内的主机。
|
||||
* 可查看通过代理的所有 http 请求和响应的详细信息。(待开发)
|
||||
|
||||
## 开发状态
|
||||
|
||||
frp 目前正在前期开发阶段,master分支用于发布稳定版本,dev分支用于开发,您可以尝试下载最新的 release 版本进行测试。
|
||||
frp 目前正在前期开发阶段,master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。
|
||||
|
||||
**在 1.x 版本以前,交互协议都可能会被改变,不能保证向后兼容。**
|
||||
|
||||
## 快速开始
|
||||
|
||||
[QuickStart](doc/quick_start_en.md) | [使用文档](doc/quick_start_zh.md)
|
||||
**目前的交互协议可能随时改变,不能保证向后兼容,升级新版本时需要注意公告说明。**
|
||||
|
||||
## 架构
|
||||
|
||||

|
||||

|
||||
|
||||
## frp 的作用?
|
||||
## 使用示例
|
||||
|
||||
* 利用处于内网或防火墙后的机器,对外网环境提供http服务。(针对http的优化正在开发中)
|
||||
* 利用处于内网或防火墙后的机器,对外网环境提供tcp服务。
|
||||
* 可查看通过代理的所有http请求和响应信息。(待开发)
|
||||
根据对应的操作系统及架构,从 [Release](https://github.com/fatedier/frp/releases) 页面下载最新版本的程序。
|
||||
|
||||
将 **frps** 及 **frps.ini** 放到有公网 IP 的机器上。
|
||||
|
||||
将 **frpc** 及 **frpc.ini** 放到处于内网环境的机器上。
|
||||
|
||||
### 通过 ssh 访问公司内网机器
|
||||
|
||||
1. 修改 frps.ini 文件,配置一个名为 ssh 的反向代理:
|
||||
|
||||
```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;
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
auth_token = 123
|
||||
|
||||
[ssh]
|
||||
local_port = 22
|
||||
```
|
||||
|
||||
4. 启动 frpc:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. 通过 ssh 访问内网机器,假设用户名为 test:
|
||||
|
||||
`ssh -oPort=6000 test@x.x.x.x`
|
||||
|
||||
### 通过自定义域名访问部署于内网的 web 服务
|
||||
|
||||
有时想要让其他人通过域名访问或者测试我们在本地搭建的 web 服务,但是由于本地机器没有公网 IP,无法将域名解析到本地的机器,通过 frp 就可以实现这一功能,以下示例为 http 服务,https 服务配置方法相同, vhost_http_port 替换为 vhost_https_port, type 设置为 https 即可。
|
||||
|
||||
1. 修改 frps.ini 文件,配置一个名为 web 的 http 反向代理,设置 http 访问端口为 8080,绑定自定义域名 `www.yourdomain.com`:
|
||||
|
||||
```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.x,local_port 为本地机器上 web 服务对应的端口:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
auth_token = 123
|
||||
|
||||
[web]
|
||||
type = http
|
||||
local_port = 80
|
||||
```
|
||||
|
||||
4. 启动 frpc:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. 将 `www.yourdomain.com` 的域名 A 记录解析到 IP `x.x.x.x`,如果服务器已经有对应的域名,也可以将 CNAME 记录解析到服务器原先的域名。
|
||||
|
||||
6. 通过浏览器访问 `http://www.yourdomain.com:8080` 即可访问到处于内网机器上的 web 服务。
|
||||
|
||||
## 功能说明
|
||||
|
||||
### Dashboard
|
||||
|
||||
通过浏览器查看 frp 的状态以及代理统计信息展示。
|
||||
|
||||
需要在 frps.ini 中指定 dashboard 服务使用的端口,即可开启此功能:
|
||||
|
||||
```ini
|
||||
[common]
|
||||
dashboard_port = 7500
|
||||
```
|
||||
|
||||
打开浏览器通过 `http://[server_addr]:7500` 访问 dashboard 界面。
|
||||
|
||||

|
||||
|
||||
### 身份验证
|
||||
|
||||
出于安全性的考虑,服务器端可以在 frps.ini 中为每一个代理设置一个 auth_token 用于对客户端连接进行身份验证,例如上文中的 [ssh] 和 [web] 两个代理的 auth_token 都为 123。
|
||||
|
||||
客户端需要在 frpc.ini 中配置自己的 auth_token,与服务器中的配置一致才能正常运行。
|
||||
|
||||
需要注意的是 frpc 所在机器和 frps 所在机器的时间相差不能超过 15 分钟,因为时间戳会被用于加密验证中,防止报文被劫持后被其他人利用。
|
||||
|
||||
### 加密与压缩
|
||||
|
||||
这两个功能默认是不开启的,需要在 frpc.ini 中通过配置来为指定的代理启用加密与压缩的功能,无论类型是 tcp, http 还是 https:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[ssh]
|
||||
type = tcp
|
||||
listen_port = 6000
|
||||
auth_token = 123
|
||||
use_encryption = true
|
||||
use_gzip = true
|
||||
```
|
||||
|
||||
如果公司内网防火墙对外网访问进行了流量识别与屏蔽,例如禁止了 ssh 协议等,通过设置 `use_encryption = true`,将 frpc 与 frps 之间的通信内容加密传输,将会有效防止流量被拦截。
|
||||
|
||||
如果传输的报文长度较长,通过设置 `use_gzip = 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] 代理已经可以使用。
|
||||
|
||||
### 特权模式
|
||||
|
||||
如果想要避免每次增加代理都需要操作服务器端,可以启用特权模式。
|
||||
|
||||
特权模式被启用后,代理的所有配置信息都可以在 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`
|
||||
|
||||
#### 端口白名单
|
||||
|
||||
启用特权模式后为了防止端口被滥用,可以手动指定允许哪些端口被使用,在 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 可以配置允许使用的某个指定端口或者是一个范围内的所有端口,以 `,` 分隔,指定的范围以 `-` 分隔。
|
||||
|
||||
### 连接池
|
||||
|
||||
默认情况下,当用户请求建立连接后,frps 才会请求 frpc 主动与后端服务建立一个连接。当为指定的代理启用连接池后,frp 会预先和后端服务建立起指定数量的连接,每次接收到用户请求后,会从连接池中取出一个连接和用户连接关联起来,避免了等待与后端服务建立连接以及 frpc 和 frps 之间传递控制信息的时间。
|
||||
|
||||
这一功能比较适合有大量短连接请求时开启。
|
||||
|
||||
1. 首先可以在 frps.ini 中设置每个代理可以创建的连接池上限,避免大量资源占用,默认为 100,客户端设置超过此配置后会被调整到当前值:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
[common]
|
||||
max_pool_count = 50
|
||||
```
|
||||
|
||||
2. 在 frpc.ini 中为指定代理启用连接池,指定预创建连接的数量:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[ssh]
|
||||
type = tcp
|
||||
local_port = 22
|
||||
pool_count = 10
|
||||
```
|
||||
|
||||
### 修改 Host Header
|
||||
|
||||
通常情况下 frp 不会修改转发的任何数据。但有一些后端服务会根据 http 请求 header 中的 host 字段来展现不同的网站,例如 nginx 的虚拟主机服务,启用 host-header 的修改功能可以动态修改 http 请求中的 host 字段。该功能仅限于 http 类型的代理。
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
privilege_mode = true
|
||||
type = http
|
||||
local_port = 80
|
||||
custom_domains = test.yourdomain.com
|
||||
host_header_rewrite = dev.yourdomain.com
|
||||
```
|
||||
|
||||
原来 http 请求中的 host 字段 `test.yourdomain.com` 转发到后端服务时会被替换为 `dev.yourdomain.com`。
|
||||
|
||||
## 开发计划
|
||||
|
||||
计划在后续版本中加入的功能与优化,排名不分先后,如果有其他功能建议欢迎在 [issues](https://github.com/fatedier/frp/issues) 中反馈。
|
||||
|
||||
* 支持 udp 协议。
|
||||
* 支持泛域名。
|
||||
* 支持 url 路由转发。
|
||||
* frpc 支持负载均衡到后端不同服务。
|
||||
* frpc debug 模式,控制台显示代理状态,类似 ngrok 启动后的界面。
|
||||
* frpc http 请求及响应信息展示。
|
||||
* frpc 支持直接作为 webserver 访问指定静态页面。
|
||||
* frpc 完全控制模式,通过 dashboard 对 frpc 进行在线操作。
|
||||
* 支持 udp 打洞的方式,提供两边内网机器直接通信,流量不经过服务器转发。
|
||||
|
||||
## 贡献代码
|
||||
|
||||
如果您对这个项目感兴趣,并且想要参与其中,我们非常欢迎!
|
||||
如果您对这个项目感兴趣,我们非常欢迎您参与其中!
|
||||
|
||||
* 如果您需要提交问题,可以通过 [issues](https://github.com/fatedier/frp/issues) 来完成。
|
||||
* 如果您有新的功能需求,可以反馈至 fatedier@gmail.com 共同讨论。
|
||||
|
||||
**提醒:和项目相关的问题最好在 [issues](https://github.com/fatedier/frp/issues) 中反馈,这样方便其他有类似问题的人可以快速查找解决方法,并且也避免了我们重复回答一些问题。**
|
||||
|
||||
## 贡献者
|
||||
|
||||
* [fatedier](https://github.com/fatedier)
|
||||
* [Hurricanezwf](https://github.com/Hurricanezwf)
|
||||
* [vashstorm](https://github.com/vashstorm)
|
||||
* [maodanp](https://github.com/maodanp)
|
||||
|
||||
@@ -1,17 +1,60 @@
|
||||
# [common] is integral section
|
||||
[common]
|
||||
# A literal address or host name for IPv6 must be enclosed
|
||||
# in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80"
|
||||
server_addr = 0.0.0.0
|
||||
server_port = 7000
|
||||
# console or real logFile path like ./frpc.log
|
||||
log_file = console
|
||||
log_file = ./frpc.log
|
||||
# debug, info, warn, error
|
||||
log_level = debug
|
||||
log_level = info
|
||||
log_max_days = 3
|
||||
# for authentication
|
||||
auth_token = 123
|
||||
# for privilege mode
|
||||
privilege_token = 12345678
|
||||
|
||||
# test1 is the proxy name same as server's configuration
|
||||
[test1]
|
||||
# ssh is the proxy name same as server's configuration
|
||||
[ssh]
|
||||
# tcp | http, 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 = true
|
||||
# default is false
|
||||
use_gzip = false
|
||||
# connections will be established in advance, default value is zero
|
||||
pool_count = 10
|
||||
|
||||
# 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
|
||||
[web01]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 80
|
||||
use_gzip = true
|
||||
pool_count = 20
|
||||
|
||||
[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
|
||||
host_header_rewrite = example.com
|
||||
|
||||
@@ -1,14 +1,45 @@
|
||||
# [common] is integral section
|
||||
[common]
|
||||
# A literal address or host name for IPv6 must be enclosed
|
||||
# in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80"
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = 7000
|
||||
# if you want to support virtual host, you must set the http port for listening (optional)
|
||||
vhost_http_port = 80
|
||||
vhost_https_port = 443
|
||||
# if you want to configure or reload frps by dashboard, dashboard_port must be set
|
||||
dashboard_port = 7500
|
||||
# dashboard assets directory(only for debug mode)
|
||||
# assets_dir = ./static
|
||||
# console or real logFile path like ./frps.log
|
||||
log_file = console
|
||||
log_file = ./frps.log
|
||||
# debug, info, warn, error
|
||||
log_level = debug
|
||||
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_token = 12345678
|
||||
# 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
|
||||
|
||||
# test1 is the proxy name, client will use this name and auth_token to connect to server
|
||||
[test1]
|
||||
# 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
|
||||
|
||||
[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
|
||||
|
||||
59
cross_compiles_package.sh
Executable file
59
cross_compiles_package.sh
Executable file
@@ -0,0 +1,59 @@
|
||||
# compile for version
|
||||
make
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "make error"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
frp_version=`./bin/frps --version`
|
||||
echo "build version: $frp_version"
|
||||
|
||||
# cross_compiles
|
||||
make -f ./Makefile.cross-compiles
|
||||
|
||||
rm -rf ./packages
|
||||
mkdir ./packages
|
||||
|
||||
os_all='linux windows darwin'
|
||||
arch_all='386 amd64 arm'
|
||||
|
||||
for os in $os_all; do
|
||||
for arch in $arch_all; do
|
||||
frp_dir_name="frp_${frp_version}_${os}_${arch}"
|
||||
frp_path="./packages/frp_${frp_version}_${os}_${arch}"
|
||||
|
||||
if [ "x${os}" = x"windows" ]; then
|
||||
if [ ! -f "./frpc_${os}_${arch}.exe" ]; then
|
||||
continue
|
||||
fi
|
||||
if [ ! -f "./frps_${os}_${arch}.exe" ]; then
|
||||
continue
|
||||
fi
|
||||
mkdir ${frp_path}
|
||||
mv ./frpc_${os}_${arch}.exe ${frp_path}/frpc.exe
|
||||
mv ./frps_${os}_${arch}.exe ${frp_path}/frps.exe
|
||||
else
|
||||
if [ ! -f "./frpc_${os}_${arch}" ]; then
|
||||
continue
|
||||
fi
|
||||
if [ ! -f "./frps_${os}_${arch}" ]; then
|
||||
continue
|
||||
fi
|
||||
mkdir ${frp_path}
|
||||
mv ./frpc_${os}_${arch} ${frp_path}/frpc
|
||||
mv ./frps_${os}_${arch} ${frp_path}/frps
|
||||
fi
|
||||
cp ./LICENSE ${frp_path}
|
||||
cp ./conf/* ${frp_path}
|
||||
|
||||
# packages
|
||||
cd ./packages
|
||||
if [ "x${os}" = x"windows" ]; then
|
||||
zip -rq ${frp_dir_name}.zip ${frp_dir_name}
|
||||
else
|
||||
tar -zcf ${frp_dir_name}.tar.gz ${frp_dir_name}
|
||||
fi
|
||||
cd ..
|
||||
rm -rf ${frp_path}
|
||||
done
|
||||
done
|
||||
BIN
doc/pic/dashboard.png
Normal file
BIN
doc/pic/dashboard.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -2,7 +2,10 @@
|
||||
|
||||
frp is easier to use compared with other similar projects.
|
||||
|
||||
We will use a simple demo to demonstrate how to create a connection to server A's ssh port by server B with public IP address x.x.x.x(replace to the real IP address of your server).
|
||||
We will use two simple demo to demonstrate how to use frp.
|
||||
|
||||
1. How to create a connection to **server A**'s **ssh port** by **server B** with **public IP address** x.x.x.x(replace to the real IP address of your server).
|
||||
2. How to visit web service in **server A**'s **8000 port** and **8001 port** by **web01.yourdomain.com** and **web02.yourdomain.com** through **server B** with public ID address.
|
||||
|
||||
### Download SourceCode
|
||||
|
||||
@@ -10,6 +13,8 @@ We will use a simple demo to demonstrate how to create a connection to server A'
|
||||
|
||||
Or you can use `git clone https://github.com/fatedier/frp.git $GOPATH/src/github.com/fatedier/frp`.
|
||||
|
||||
If you want to try it quickly, download the compiled program and configuration files from [https://github.com/fatedier/frp/releases](https://github.com/fatedier/frp/releases).
|
||||
|
||||
### Compile
|
||||
|
||||
Enter the root directory and execute `make`, then wait until finished.
|
||||
@@ -19,16 +24,18 @@ Enter the root directory and execute `make`, then wait until finished.
|
||||
### Pre-requirement
|
||||
|
||||
* Go environment. Version of go >= 1.4.
|
||||
* Godep (if not exist, go get will be executed to download godep when compiling)
|
||||
* Godep (if not exist, `go get` will be executed to download godep when compiling)
|
||||
|
||||
### Deploy
|
||||
|
||||
1. Move `./bin/frps` and `./conf/frps.ini` to any directory of server B.
|
||||
2. Move `./bin/frpc` and `./conf/frpc.ini` to any directory of server A.
|
||||
1. Move `./bin/frps` and `./conf/frps.ini` to any directory of **server B**.
|
||||
2. Move `./bin/frpc` and `./conf/frpc.ini` to any directory of **server A**.
|
||||
3. Modify all configuration files, details in next paragraph.
|
||||
4. Execute `nohup ./frps &` or `nohup ./frps -c ./frps.ini &` in server B.
|
||||
5. Execute `nohup ./frpc &` or `nohup ./frpc -c ./frpc.ini &` in server A.
|
||||
6. Use `ssh -oPort=6000 {user}@x.x.x.x` to test if frp is work(replace {user} to real username in server A).
|
||||
4. Execute `nohup ./frps &` or `nohup ./frps -c ./frps.ini &` in **server B**.
|
||||
5. Execute `nohup ./frpc &` or `nohup ./frpc -c ./frpc.ini &` in **server A**.
|
||||
6. Use `ssh -oPort=6000 {user}@x.x.x.x` to test if frp is work(replace {user} to real username in **server A**), or visit custom domains by browser.
|
||||
|
||||
## Tcp port forwarding
|
||||
|
||||
### Configuration files
|
||||
|
||||
@@ -42,8 +49,8 @@ bind_port = 7000
|
||||
log_file = ./frps.log
|
||||
log_level = info
|
||||
|
||||
# test is the custom name of proxy and there can be many proxies with unique name in one configure file
|
||||
[test]
|
||||
# ssh is the custom name of proxy and there can be many proxies with unique name in one configure file
|
||||
[ssh]
|
||||
auth_token = 123
|
||||
bind_addr = 0.0.0.0
|
||||
# finally we connect to server A by this port
|
||||
@@ -62,10 +69,67 @@ log_level = info
|
||||
# for authentication
|
||||
auth_token = 123
|
||||
|
||||
# test is proxy name same with configure in frps.ini
|
||||
[test]
|
||||
# ssh is proxy name same with configure in frps.ini
|
||||
[ssh]
|
||||
# local port which need to be transferred
|
||||
local_port = 22
|
||||
# if use_encryption equals true, messages between frpc and frps will be encrypted, default is false
|
||||
use_encryption = true
|
||||
```
|
||||
|
||||
## Http port forwarding and Custom domains binding
|
||||
|
||||
If you only want to forward port one by one, you just need refer to [Tcp port forwarding](/doc/quick_start_en.md#Tcp-port-forwarding).If you want to visit different web pages deployed in different web servers by **server B**'s **80 port**, you should specify the type as **http**.
|
||||
|
||||
You also need to resolve your **A record** of your custom domain to [server_addr], or resolve your **CNAME record** to [server_addr] if [server_addr] is a domain.
|
||||
|
||||
After that, you can visit your web pages in local server by custom domains.
|
||||
|
||||
### Configuration files
|
||||
|
||||
#### frps.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = 7000
|
||||
# if you want to support vhost, specify one port for http services
|
||||
vhost_http_port = 80
|
||||
log_file = ./frps.log
|
||||
log_level = info
|
||||
|
||||
[web01]
|
||||
type = http
|
||||
auth_token = 123
|
||||
# # if proxy type equals http, custom_domains must be set separated by commas
|
||||
custom_domains = web01.yourdomain.com
|
||||
|
||||
[web02]
|
||||
type = http
|
||||
auth_token = 123
|
||||
custom_domains = web02.yourdomain.com
|
||||
```
|
||||
|
||||
#### frpc.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
log_file = ./frpc.log
|
||||
log_level = info
|
||||
auth_token = 123
|
||||
|
||||
# custom domains are set in frps.ini
|
||||
[web01]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 8000
|
||||
# encryption is optional, default is false
|
||||
use_encryption = true
|
||||
|
||||
[web02]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 8001
|
||||
```
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# frp 使用文档
|
||||
|
||||
frp 相比于其他项目而言非常易于部署和使用,这里我们用一个简单的示例演示如何通过一台拥有公网IP地址的服务器B,访问处于内网环境中的服务器A的ssh端口,服务器B的IP地址为 x.x.x.x(测试时替换为真实的IP地址)。
|
||||
相比于其他项目而言 frp 更易于部署和使用,这里我们用两个简单的示例来演示 frp 的使用过程。
|
||||
|
||||
1. 如何通过一台拥有公网IP地址的**服务器B**,访问处于公司内部网络环境中的**服务器A**的**ssh**端口,**服务器B**的IP地址为 x.x.x.x(测试时替换为真实的IP地址)。
|
||||
2. 如何利用一台拥有公网IP地址的**服务器B**,使通过 **web01.yourdomain.com** 可以访问内网环境中**服务器A**上**8000端口**的web服务,**web02.yourdomain.com** 可以访问**服务器A**上**8001端口**的web服务。
|
||||
|
||||
### 下载源码
|
||||
|
||||
@@ -8,6 +11,8 @@ frp 相比于其他项目而言非常易于部署和使用,这里我们用一
|
||||
|
||||
或者可以使用 `git clone https://github.com/fatedier/frp.git $GOPATH/src/github.com/fatedier/frp` 拷贝到相应目录下。
|
||||
|
||||
如果您想快速进行测试,也可以根据您服务器的操作系统及架构直接下载编译好的程序及示例配置文件,[https://github.com/fatedier/frp/releases](https://github.com/fatedier/frp/releases)。
|
||||
|
||||
### 编译
|
||||
|
||||
进入下载后的源码根目录,执行 `make` 命令,等待编译完成。
|
||||
@@ -17,16 +22,20 @@ frp 相比于其他项目而言非常易于部署和使用,这里我们用一
|
||||
### 依赖
|
||||
|
||||
* go 1.4 以上版本
|
||||
* godep (如果检查不存在,编译时会通过 go get 命令安装)
|
||||
* godep (如果检查不存在,编译时会通过 `go get` 命令安装)
|
||||
|
||||
### 部署
|
||||
|
||||
1. 将 ./bin/frps 和 ./conf/frps.ini 拷贝至服务器B任意目录。
|
||||
2. 将 ./bin/frpc 和 ./conf/frpc.ini 拷贝至服务器A任意目录。
|
||||
3. 修改两边的配置文件,见下一节说明。
|
||||
1. 将 ./bin/frps 和 ./conf/frps.ini 拷贝至**服务器B**任意目录。
|
||||
2. 将 ./bin/frpc 和 ./conf/frpc.ini 拷贝至**服务器A**任意目录。
|
||||
3. 根据要实现的功能修改两边的配置文件,详细内容见后续章节说明。
|
||||
4. 在服务器B执行 `nohup ./frps &` 或者 `nohup ./frps -c ./frps.ini &`。
|
||||
5. 在服务器A执行 `nohup ./frpc &` 或者 `nohup ./frpc -c ./frpc.ini &`。
|
||||
6. 通过 `ssh -oPort=6000 {user}@x.x.x.x` 测试是否能够成功连接服务器A({user}替换为服务器A上存在的真实用户)。
|
||||
6. 通过 `ssh -oPort=6000 {user}@x.x.x.x` 测试是否能够成功连接**服务器A**({user}替换为**服务器A**上存在的真实用户),或通过浏览器访问自定义域名验证 http 服务是否转发成功。
|
||||
|
||||
## tcp 端口转发
|
||||
|
||||
转发 tcp 端口需要按照需求修改 frps 和 frpc 的配置文件。
|
||||
|
||||
### 配置文件
|
||||
|
||||
@@ -40,9 +49,9 @@ bind_port = 7000
|
||||
log_file = ./frps.log
|
||||
log_level = info
|
||||
|
||||
# test 为代理的自定义名称,可以有多个,不能重复,和frpc中名称对应
|
||||
[test]
|
||||
auth_token = 123
|
||||
# ssh 为代理的自定义名称,可以有多个,不能重复,和frpc中名称对应
|
||||
[ssh]
|
||||
auth_token = 123
|
||||
bind_addr = 0.0.0.0
|
||||
# 最后将通过此端口访问后端服务
|
||||
listen_port = 6000
|
||||
@@ -58,12 +67,71 @@ server_port = 7000
|
||||
log_file = ./frpc.log
|
||||
log_level = info
|
||||
# 用于身份验证
|
||||
auth_token = 123
|
||||
auth_token = 123
|
||||
|
||||
# test需要和 frps.ini 中配置一致
|
||||
[test]
|
||||
# ssh 需要和 frps.ini 中配置一致
|
||||
[ssh]
|
||||
# 需要转发的本地端口
|
||||
local_port = 22
|
||||
# 启用加密,frpc与frps之间通信加密,默认为 false
|
||||
use_encryption = true
|
||||
```
|
||||
|
||||
## http 端口转发,自定义域名绑定
|
||||
|
||||
如果只需要一对一的转发,例如**服务器B**的**80端口**转发**服务器A**的**8000端口**,则只需要配置 [tcp 端口转发](/doc/quick_start_zh.md#tcp-端口转发) 即可,如果需要使**服务器B**的**80端口**可以转发至**多个**web服务端口,则需要指定代理的类型为 http,并且在 frps 的配置文件中配置用于提供 http 转发服务的端口。
|
||||
|
||||
按照如下的内容修改配置文件后,需要将自定义域名的 **A 记录**解析到 [server_addr],如果 [server_addr] 是域名也可以将自定义域名的 **CNAME 记录**解析到 [server_addr]。
|
||||
|
||||
之后就可以通过自定义域名访问到本地的多个 web 服务。
|
||||
|
||||
### 配置文件
|
||||
|
||||
#### frps.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = 7000
|
||||
# 如果需要支持http类型的代理则需要指定一个端口
|
||||
vhost_http_port = 80
|
||||
log_file = ./frps.log
|
||||
log_level = info
|
||||
|
||||
[web01]
|
||||
# type 默认为 tcp,这里需要特别指定为 http
|
||||
type = http
|
||||
auth_token = 123
|
||||
# 自定义域名绑定,如果需要同时绑定多个以英文逗号分隔
|
||||
custom_domains = web01.yourdomain.com
|
||||
|
||||
[web02]
|
||||
type = http
|
||||
auth_token = 123
|
||||
custom_domains = web02.yourdomain.com
|
||||
```
|
||||
|
||||
#### frpc.ini
|
||||
|
||||
```ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
log_file = ./frpc.log
|
||||
log_level = info
|
||||
auth_token = 123
|
||||
|
||||
|
||||
# 自定义域名在 frps.ini 中配置,方便做统一管理
|
||||
[web01]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 8000
|
||||
# 可选是否加密
|
||||
use_encryption = true
|
||||
|
||||
[web02]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 8001
|
||||
```
|
||||
|
||||
75
src/assets/assets.go
Normal file
75
src/assets/assets.go
Normal file
@@ -0,0 +1,75 @@
|
||||
// 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 assets
|
||||
|
||||
//go:generate statik -src=./static
|
||||
//go:generate go fmt statik/statik.go
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/rakyll/statik/fs"
|
||||
|
||||
_ "github.com/fatedier/frp/src/assets/statik"
|
||||
)
|
||||
|
||||
var (
|
||||
// store static files in memory by statik
|
||||
FileSystem http.FileSystem
|
||||
|
||||
// if prefix is not empty, we get file content from disk
|
||||
prefixPath string
|
||||
)
|
||||
|
||||
// if path is empty, load assets in memory
|
||||
// or set FileSystem using disk files
|
||||
func Load(path string) (err error) {
|
||||
prefixPath = path
|
||||
if prefixPath != "" {
|
||||
FileSystem = http.Dir(prefixPath)
|
||||
return nil
|
||||
} else {
|
||||
FileSystem, err = fs.New()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func ReadFile(file string) (content string, err error) {
|
||||
if prefixPath == "" {
|
||||
file, err := FileSystem.Open(path.Join("/", file))
|
||||
if err != nil {
|
||||
return content, err
|
||||
}
|
||||
buf, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return content, err
|
||||
}
|
||||
content = string(buf)
|
||||
} else {
|
||||
file, err := os.Open(path.Join(prefixPath, file))
|
||||
if err != nil {
|
||||
return content, err
|
||||
}
|
||||
buf, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return content, err
|
||||
}
|
||||
content = string(buf)
|
||||
}
|
||||
return content, err
|
||||
}
|
||||
5
src/assets/static/css/bootstrap.min.css
vendored
Normal file
5
src/assets/static/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
src/assets/static/css/font-awesome.min.css
vendored
Normal file
4
src/assets/static/css/font-awesome.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
18
src/assets/static/css/iconfont.css
Normal file
18
src/assets/static/css/iconfont.css
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
@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"; }
|
||||
BIN
src/assets/static/favicon.ico
Normal file
BIN
src/assets/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
BIN
src/assets/static/font/iconfont.eot
Normal file
BIN
src/assets/static/font/iconfont.eot
Normal file
Binary file not shown.
39
src/assets/static/font/iconfont.svg
Normal file
39
src/assets/static/font/iconfont.svg
Normal file
@@ -0,0 +1,39 @@
|
||||
<?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="" horiz-adv-x="1024"
|
||||
d="M922 440l-39 -39l-207 200v-684h-55v766h54zM349 -83l-247 243l39 39l207 -200v684h55v-766h-54z" />
|
||||
</font>
|
||||
</defs></svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/assets/static/font/iconfont.ttf
Normal file
BIN
src/assets/static/font/iconfont.ttf
Normal file
Binary file not shown.
BIN
src/assets/static/font/iconfont.woff
Normal file
BIN
src/assets/static/font/iconfont.woff
Normal file
Binary file not shown.
303
src/assets/static/index.html
Normal file
303
src/assets/static/index.html
Normal file
@@ -0,0 +1,303 @@
|
||||
<!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"></i></th>
|
||||
<th class="tab_info" ng-click="col='type';desc=!desc">Type<i class="iconfont pull-right"></i></th>
|
||||
<th class="tab_info" ng-click="col='listen_port';desc=!desc">Port<i class="iconfont pull-right"></i></th>
|
||||
<th class="tab_info" ng-click="col='status';desc=!desc">Status<i class="iconfont pull-right"></i></th>
|
||||
<th class="tab_info" ng-click="col='current_conns';desc=!desc">CurCon<i class="iconfont pull-right"></i></th>
|
||||
<th class="tab_info" ng-click="col='daily[daily.length-1].flow_out';desc=!desc">FlowOut<i class="iconfont pull-right"></i></th>
|
||||
<th class="tab_info" ng-click="col='daily[daily.length-1].flow_in';desc=!desc">FlowIn<i class="iconfont pull-right"></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>
|
||||
</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 >>> ],
|
||||
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>";
|
||||
}
|
||||
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>
|
||||
317
src/assets/static/js/angular.min.js
vendored
Normal file
317
src/assets/static/js/angular.min.js
vendored
Normal file
@@ -0,0 +1,317 @@
|
||||
/*
|
||||
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(/</,"<").replace(/>/,">"));}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>');
|
||||
7
src/assets/static/js/bootstrap.min.js
vendored
Normal file
7
src/assets/static/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
35
src/assets/static/js/echarts.min.js
vendored
Normal file
35
src/assets/static/js/echarts.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
src/assets/static/js/jquery.min.js
vendored
Normal file
4
src/assets/static/js/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
10
src/assets/statik/statik.go
Normal file
10
src/assets/statik/statik.go
Normal file
File diff suppressed because one or more lines are too long
@@ -21,12 +21,12 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"frp/models/client"
|
||||
"frp/models/consts"
|
||||
"frp/models/msg"
|
||||
"frp/utils/conn"
|
||||
"frp/utils/log"
|
||||
"frp/utils/pcrypto"
|
||||
"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) {
|
||||
@@ -72,7 +72,10 @@ func msgReader(cli *client.ProxyClient, c *conn.Conn, msgSendChan chan interface
|
||||
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)
|
||||
break
|
||||
}
|
||||
|
||||
@@ -81,6 +84,7 @@ func msgReader(cli *client.ProxyClient, c *conn.Conn, msgSendChan chan interface
|
||||
}
|
||||
time.Sleep(delayTime * time.Second)
|
||||
}
|
||||
continue
|
||||
} else if err != nil {
|
||||
log.Warn("ProxyName [%s], read from frps error: %v", cli.Name, err)
|
||||
continue
|
||||
@@ -98,7 +102,8 @@ func msgReader(cli *client.ProxyClient, c *conn.Conn, msgSendChan chan interface
|
||||
timer.Reset(time.Duration(client.HeartBeatTimeout) * time.Second)
|
||||
case consts.NoticeUserConn:
|
||||
log.Debug("ProxyName [%s], new user connection", cli.Name)
|
||||
cli.StartTunnel(client.ServerAddr, client.ServerPort)
|
||||
// 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)
|
||||
}
|
||||
@@ -117,7 +122,7 @@ func msgSender(cli *client.ProxyClient, c *conn.Conn, msgSendChan chan interface
|
||||
buf, _ := json.Marshal(msg)
|
||||
err := c.Write(string(buf) + "\n")
|
||||
if err != nil {
|
||||
log.Warn("ProxyName [%s], write to client error, proxy exit", cli.Name)
|
||||
log.Warn("ProxyName [%s], write to server error, proxy exit", cli.Name)
|
||||
c.Close()
|
||||
break
|
||||
}
|
||||
@@ -132,14 +137,26 @@ func loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) {
|
||||
}
|
||||
|
||||
nowTime := time.Now().Unix()
|
||||
authKey := pcrypto.GetAuthKey(cli.Name + cli.AuthToken + fmt.Sprintf("%d", nowTime))
|
||||
req := &msg.ControlReq{
|
||||
Type: consts.NewCtlConn,
|
||||
ProxyName: cli.Name,
|
||||
AuthKey: authKey,
|
||||
UseEncryption: cli.UseEncryption,
|
||||
Timestamp: nowTime,
|
||||
Type: consts.NewCtlConn,
|
||||
ProxyName: cli.Name,
|
||||
UseEncryption: cli.UseEncryption,
|
||||
UseGzip: cli.UseGzip,
|
||||
PrivilegeMode: cli.PrivilegeMode,
|
||||
ProxyType: cli.Type,
|
||||
HostHeaderRewrite: cli.HostHeaderRewrite,
|
||||
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.PrivilegeKey = privilegeKey
|
||||
} else {
|
||||
authKey := pcrypto.GetAuthKey(cli.Name + cli.AuthToken + fmt.Sprintf("%d", nowTime))
|
||||
req.AuthKey = authKey
|
||||
}
|
||||
|
||||
buf, _ := json.Marshal(req)
|
||||
err = c.Write(string(buf) + "\n")
|
||||
if err != nil {
|
||||
@@ -165,7 +182,7 @@ func loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) {
|
||||
return c, fmt.Errorf("%s", ctlRes.Msg)
|
||||
}
|
||||
|
||||
log.Debug("ProxyName [%s], connect to server [%s:%d] success!", cli.Name, client.ServerAddr, client.ServerPort)
|
||||
log.Info("ProxyName [%s], connect to server [%s:%d] success!", cli.Name, client.ServerAddr, client.ServerPort)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -183,5 +200,5 @@ func heartbeatSender(c *conn.Conn, msgSendChan chan interface{}) {
|
||||
break
|
||||
}
|
||||
}
|
||||
log.Debug("Heartbeat goroutine exit")
|
||||
log.Info("Heartbeat goroutine exit")
|
||||
}
|
||||
@@ -23,9 +23,9 @@ import (
|
||||
|
||||
docopt "github.com/docopt/docopt-go"
|
||||
|
||||
"frp/models/client"
|
||||
"frp/utils/log"
|
||||
"frp/utils/version"
|
||||
"github.com/fatedier/frp/src/models/client"
|
||||
"github.com/fatedier/frp/src/utils/log"
|
||||
"github.com/fatedier/frp/src/utils/version"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -36,7 +36,8 @@ var usage string = `frpc is the client of frp
|
||||
|
||||
Usage:
|
||||
frpc [-c config_file] [-L log_file] [--log-level=<log_level>] [--server-addr=<server_addr>]
|
||||
frpc -h | --help | --version
|
||||
frpc -h | --help
|
||||
frpc -v | --version
|
||||
|
||||
Options:
|
||||
-c config_file set config file
|
||||
@@ -88,7 +89,14 @@ func main() {
|
||||
client.ServerPort = serverPort
|
||||
}
|
||||
|
||||
log.InitLog(client.LogWay, client.LogFile, client.LogLevel)
|
||||
if args["-v"] != nil {
|
||||
if args["-v"].(bool) {
|
||||
fmt.Println(version.Full())
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
log.InitLog(client.LogWay, client.LogFile, client.LogLevel, client.LogMaxDays)
|
||||
|
||||
// wait until all control goroutine exit
|
||||
var wait sync.WaitGroup
|
||||
@@ -20,17 +20,17 @@ import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"frp/models/consts"
|
||||
"frp/models/msg"
|
||||
"frp/models/server"
|
||||
"frp/utils/conn"
|
||||
"frp/utils/log"
|
||||
"frp/utils/pcrypto"
|
||||
"github.com/fatedier/frp/src/models/consts"
|
||||
"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.GetConn()
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -67,13 +67,8 @@ func controlWorker(c *conn.Conn) {
|
||||
return
|
||||
}
|
||||
|
||||
// do login when type is NewCtlConn or NewWorkConn
|
||||
// login when type is NewCtlConn or NewWorkConn
|
||||
ret, info := doLogin(cliReq, c)
|
||||
s, ok := server.ProxyServers[cliReq.ProxyName]
|
||||
if !ok {
|
||||
log.Warn("ProxyName [%s] is not exist", cliReq.ProxyName)
|
||||
return
|
||||
}
|
||||
// if login type is NewWorkConn, nothing will be send to frpc
|
||||
if cliReq.Type != consts.NewWorkConn {
|
||||
cliRes := &msg.ControlRes{
|
||||
@@ -84,8 +79,7 @@ func controlWorker(c *conn.Conn) {
|
||||
byteBuf, _ := json.Marshal(cliRes)
|
||||
err = c.Write(string(byteBuf) + "\n")
|
||||
if err != nil {
|
||||
log.Warn("ProxyName [%s], write to client error, proxy exit", s.Name)
|
||||
time.Sleep(1 * time.Second)
|
||||
log.Warn("ProxyName [%s], write to client error, proxy exit", cliReq.ProxyName)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
@@ -93,6 +87,17 @@ func controlWorker(c *conn.Conn) {
|
||||
return
|
||||
}
|
||||
|
||||
// if login failed, just return
|
||||
if ret > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
s, ok := server.ProxyServers[cliReq.ProxyName]
|
||||
if !ok {
|
||||
log.Warn("ProxyName [%s], is not exist now", cliReq.ProxyName)
|
||||
return
|
||||
}
|
||||
|
||||
// create a channel for sending messages
|
||||
msgSendChan := make(chan interface{}, 1024)
|
||||
go msgSender(s, c, msgSendChan)
|
||||
@@ -129,7 +134,6 @@ func msgReader(s *server.ProxyServer, c *conn.Conn, msgSendChan chan interface{}
|
||||
timer := time.AfterFunc(time.Duration(server.HeartBeatTimeout)*time.Second, func() {
|
||||
heartbeatTimeout = true
|
||||
s.Close()
|
||||
c.Close()
|
||||
log.Error("ProxyName [%s], client heartbeat timeout", s.Name)
|
||||
})
|
||||
defer timer.Stop()
|
||||
@@ -158,7 +162,7 @@ func msgReader(s *server.ProxyServer, c *conn.Conn, msgSendChan chan interface{}
|
||||
case consts.HeartbeatReq:
|
||||
log.Debug("ProxyName [%s], get heartbeat", s.Name)
|
||||
timer.Reset(time.Duration(server.HeartBeatTimeout) * time.Second)
|
||||
heartbeatRes := msg.ControlRes{
|
||||
heartbeatRes := &msg.ControlRes{
|
||||
Type: consts.HeartbeatRes,
|
||||
}
|
||||
msgSendChan <- heartbeatRes
|
||||
@@ -190,47 +194,121 @@ func msgSender(s *server.ProxyServer, c *conn.Conn, msgSendChan chan interface{}
|
||||
// if success, ret equals 0, otherwise greater than 0
|
||||
func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) {
|
||||
ret = 1
|
||||
// check if proxy name exist
|
||||
s, ok := server.ProxyServers[req.ProxyName]
|
||||
if !ok {
|
||||
info = fmt.Sprintf("ProxyName [%s] is not exist", req.ProxyName)
|
||||
log.Warn(info)
|
||||
// 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
|
||||
}
|
||||
|
||||
// check authKey
|
||||
var (
|
||||
s *server.ProxyServer
|
||||
ok bool
|
||||
)
|
||||
s, ok = server.ProxyServers[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()
|
||||
authKey := pcrypto.GetAuthKey(req.ProxyName + s.AuthToken + fmt.Sprintf("%d", req.Timestamp))
|
||||
// authKey avaiable in 15 minutes
|
||||
if nowTime-req.Timestamp > 15*60 {
|
||||
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)
|
||||
return
|
||||
if req.PrivilegeMode {
|
||||
privilegeKey := pcrypto.GetAuthKey(req.ProxyName + server.PrivilegeToken + fmt.Sprintf("%d", req.Timestamp))
|
||||
// privilegeKey avaiable in 15 minutes
|
||||
if nowTime-req.Timestamp > 15*60 {
|
||||
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)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
authKey := pcrypto.GetAuthKey(req.ProxyName + s.AuthToken + fmt.Sprintf("%d", req.Timestamp))
|
||||
// authKey avaiable in 15 minutes
|
||||
if nowTime-req.Timestamp > 15*60 {
|
||||
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)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// control conn
|
||||
if req.Type == consts.NewCtlConn {
|
||||
if s.Status == consts.Working {
|
||||
info = fmt.Sprintf("ProxyName [%s], already in use", req.ProxyName)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
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.UseEncryption = req.UseEncryption
|
||||
s.UseGzip = req.UseGzip
|
||||
s.HostHeaderRewrite = req.HostHeaderRewrite
|
||||
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
|
||||
}
|
||||
|
||||
// start proxy and listen for user connections, no block
|
||||
err := s.Start()
|
||||
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 {
|
||||
@@ -238,7 +316,7 @@ func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) {
|
||||
return
|
||||
}
|
||||
// the connection will close after join over
|
||||
s.RecvNewWorkConn(c)
|
||||
s.RegisterNewWorkConn(c)
|
||||
} else {
|
||||
info = fmt.Sprintf("Unsupport login message type [%d]", req.Type)
|
||||
log.Warn("Unsupport login message type [%d]", req.Type)
|
||||
187
src/cmd/frps/main.go
Normal file
187
src/cmd/frps/main.go
Normal file
@@ -0,0 +1,187 @@
|
||||
// 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/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 --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) {
|
||||
resp, err := http.Get("http://" + server.BindAddr + ":" + fmt.Sprintf("%d", server.DashboardPort) + "/api/reload")
|
||||
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: %v\n", err)
|
||||
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)
|
||||
}
|
||||
@@ -1,101 +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 (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
docopt "github.com/docopt/docopt-go"
|
||||
|
||||
"frp/models/server"
|
||||
"frp/utils/conn"
|
||||
"frp/utils/log"
|
||||
"frp/utils/version"
|
||||
)
|
||||
|
||||
var (
|
||||
configFile string = "./frps.ini"
|
||||
)
|
||||
|
||||
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 | --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
|
||||
--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 {
|
||||
configFile = args["-c"].(string)
|
||||
}
|
||||
err = server.LoadConf(configFile)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
log.InitLog(server.LogWay, server.LogFile, server.LogLevel)
|
||||
|
||||
l, err := conn.Listen(server.BindAddr, server.BindPort)
|
||||
if err != nil {
|
||||
log.Error("Create listener error, %v", err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
log.Info("Start frps success")
|
||||
ProcessControlConn(l)
|
||||
}
|
||||
@@ -1,124 +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"
|
||||
"strconv"
|
||||
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
// common config
|
||||
var (
|
||||
ServerAddr string = "0.0.0.0"
|
||||
ServerPort int64 = 7000
|
||||
LogFile string = "console"
|
||||
LogWay string = "console"
|
||||
LogLevel string = "info"
|
||||
HeartBeatInterval int64 = 20
|
||||
HeartBeatTimeout int64 = 90
|
||||
)
|
||||
|
||||
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", "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
|
||||
}
|
||||
|
||||
var authToken string
|
||||
tmpStr, ok = conf.Get("common", "auth_token")
|
||||
if ok {
|
||||
authToken = tmpStr
|
||||
} else {
|
||||
return fmt.Errorf("auth_token not found")
|
||||
}
|
||||
|
||||
// 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
|
||||
portStr, ok := section["local_port"]
|
||||
if ok {
|
||||
proxyClient.LocalPort, err = strconv.ParseInt(portStr, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Parse ini file error: proxy [%s] local_port error", proxyClient.Name)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Parse ini file error: proxy [%s] local_port not found", proxyClient.Name)
|
||||
}
|
||||
|
||||
// use_encryption
|
||||
proxyClient.UseEncryption = false
|
||||
useEncryptionStr, ok := section["use_encryption"]
|
||||
if ok && useEncryptionStr == "true" {
|
||||
proxyClient.UseEncryption = true
|
||||
}
|
||||
|
||||
ProxyClients[proxyClient.Name] = proxyClient
|
||||
}
|
||||
}
|
||||
|
||||
if len(ProxyClients) == 0 {
|
||||
return fmt.Errorf("Parse ini file error: no proxy config found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,108 +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"
|
||||
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
// common config
|
||||
var (
|
||||
BindAddr string = "0.0.0.0"
|
||||
BindPort int64 = 7000
|
||||
LogFile string = "console"
|
||||
LogWay string = "console" // console or file
|
||||
LogLevel string = "info"
|
||||
HeartBeatTimeout int64 = 90
|
||||
UserConnTimeout int64 = 10
|
||||
)
|
||||
|
||||
var ProxyServers map[string]*ProxyServer = make(map[string]*ProxyServer)
|
||||
|
||||
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", "bind_addr")
|
||||
if ok {
|
||||
BindAddr = tmpStr
|
||||
}
|
||||
|
||||
tmpStr, ok = conf.Get("common", "bind_port")
|
||||
if ok {
|
||||
BindPort, _ = strconv.ParseInt(tmpStr, 10, 64)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// servers
|
||||
for name, section := range conf {
|
||||
if name != "common" {
|
||||
proxyServer := &ProxyServer{}
|
||||
proxyServer.Name = name
|
||||
|
||||
proxyServer.AuthToken, ok = section["auth_token"]
|
||||
if !ok {
|
||||
return fmt.Errorf("Parse ini file error: proxy [%s] no auth_token found", proxyServer.Name)
|
||||
}
|
||||
|
||||
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 fmt.Errorf("Parse ini file error: proxy [%s] listen_port error", proxyServer.Name)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Parse ini file error: proxy [%s] listen_port not found", proxyServer.Name)
|
||||
}
|
||||
|
||||
proxyServer.Init()
|
||||
ProxyServers[proxyServer.Name] = proxyServer
|
||||
}
|
||||
}
|
||||
|
||||
if len(ProxyServers) == 0 {
|
||||
return fmt.Errorf("Parse ini file error: no proxy config found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,174 +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 (
|
||||
"container/list"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"frp/models/consts"
|
||||
"frp/utils/conn"
|
||||
"frp/utils/log"
|
||||
)
|
||||
|
||||
type ProxyServer struct {
|
||||
Name string
|
||||
AuthToken string
|
||||
UseEncryption bool
|
||||
BindAddr string
|
||||
ListenPort int64
|
||||
Status int64
|
||||
|
||||
listener *conn.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
|
||||
userConnList *list.List // store user conns
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func (p *ProxyServer) Init() {
|
||||
p.Status = consts.Idle
|
||||
p.workConnChan = make(chan *conn.Conn)
|
||||
p.ctlMsgChan = make(chan int64)
|
||||
p.userConnList = list.New()
|
||||
}
|
||||
|
||||
func (p *ProxyServer) Lock() {
|
||||
p.mutex.Lock()
|
||||
}
|
||||
|
||||
func (p *ProxyServer) Unlock() {
|
||||
p.mutex.Unlock()
|
||||
}
|
||||
|
||||
// start listening for user conns
|
||||
func (p *ProxyServer) Start() (err error) {
|
||||
p.Init()
|
||||
p.listener, err = conn.Listen(p.BindAddr, p.ListenPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.Status = consts.Working
|
||||
|
||||
// start a goroutine for listener to accept user connection
|
||||
go func() {
|
||||
for {
|
||||
// block
|
||||
// if listener is closed, err returned
|
||||
c, err := p.listener.GetConn()
|
||||
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())
|
||||
|
||||
// insert into list
|
||||
p.Lock()
|
||||
if p.Status != consts.Working {
|
||||
log.Debug("ProxyName [%s] is not working, new user conn close", p.Name)
|
||||
c.Close()
|
||||
p.Unlock()
|
||||
return
|
||||
}
|
||||
p.userConnList.PushBack(c)
|
||||
p.Unlock()
|
||||
|
||||
// put msg to control conn
|
||||
p.ctlMsgChan <- 1
|
||||
|
||||
// set timeout
|
||||
time.AfterFunc(time.Duration(UserConnTimeout)*time.Second, func() {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
element := p.userConnList.Front()
|
||||
if element == nil {
|
||||
return
|
||||
}
|
||||
|
||||
userConn := element.Value.(*conn.Conn)
|
||||
if userConn == c {
|
||||
log.Warn("ProxyName [%s], user conn [%s] timeout", p.Name, c.GetRemoteAddr())
|
||||
}
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
// start another goroutine for join two conns from client and user
|
||||
go func() {
|
||||
for {
|
||||
workConn, ok := <-p.workConnChan
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
p.Lock()
|
||||
element := p.userConnList.Front()
|
||||
|
||||
var userConn *conn.Conn
|
||||
if element != nil {
|
||||
userConn = element.Value.(*conn.Conn)
|
||||
p.userConnList.Remove(element)
|
||||
} else {
|
||||
workConn.Close()
|
||||
p.Unlock()
|
||||
continue
|
||||
}
|
||||
p.Unlock()
|
||||
|
||||
// msg will transfer 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())
|
||||
|
||||
if p.UseEncryption {
|
||||
go conn.JoinMore(userConn, workConn, p.AuthToken)
|
||||
} else {
|
||||
go conn.Join(userConn, workConn)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ProxyServer) Close() {
|
||||
p.Lock()
|
||||
if p.Status != consts.Closed {
|
||||
p.Status = consts.Closed
|
||||
if p.listener != nil {
|
||||
p.listener.Close()
|
||||
}
|
||||
close(p.ctlMsgChan)
|
||||
close(p.workConnChan)
|
||||
p.userConnList = list.New()
|
||||
}
|
||||
p.Unlock()
|
||||
}
|
||||
|
||||
func (p *ProxyServer) WaitUserConn() (closeFlag bool) {
|
||||
closeFlag = false
|
||||
|
||||
_, ok := <-p.ctlMsgChan
|
||||
if !ok {
|
||||
closeFlag = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *ProxyServer) RecvNewWorkConn(c *conn.Conn) {
|
||||
p.workConnChan <- c
|
||||
}
|
||||
@@ -1,255 +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"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"frp/utils/log"
|
||||
"frp/utils/pcrypto"
|
||||
)
|
||||
|
||||
type Listener struct {
|
||||
addr net.Addr
|
||||
l *net.TCPListener
|
||||
conns chan *Conn
|
||||
closeFlag bool
|
||||
}
|
||||
|
||||
func Listen(bindAddr string, bindPort int64) (l *Listener, err error) {
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", bindAddr, bindPort))
|
||||
listener, err := net.ListenTCP("tcp", tcpAddr)
|
||||
if err != nil {
|
||||
return l, err
|
||||
}
|
||||
|
||||
l = &Listener{
|
||||
addr: listener.Addr(),
|
||||
l: listener,
|
||||
conns: make(chan *Conn),
|
||||
closeFlag: false,
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
conn, err := l.l.AcceptTCP()
|
||||
if err != nil {
|
||||
if l.closeFlag {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
c := &Conn{
|
||||
TcpConn: conn,
|
||||
closeFlag: false,
|
||||
}
|
||||
c.Reader = bufio.NewReader(c.TcpConn)
|
||||
l.conns <- c
|
||||
}
|
||||
}()
|
||||
return l, err
|
||||
}
|
||||
|
||||
// wait util get one new connection or listener is closed
|
||||
// if listener is closed, err returned
|
||||
func (l *Listener) GetConn() (conn *Conn, err error) {
|
||||
var ok bool
|
||||
conn, ok = <-l.conns
|
||||
if !ok {
|
||||
return conn, fmt.Errorf("channel close")
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (l *Listener) Close() {
|
||||
if l.l != nil && l.closeFlag == false {
|
||||
l.closeFlag = true
|
||||
l.l.Close()
|
||||
close(l.conns)
|
||||
}
|
||||
}
|
||||
|
||||
// wrap for TCPConn
|
||||
type Conn struct {
|
||||
TcpConn *net.TCPConn
|
||||
Reader *bufio.Reader
|
||||
closeFlag bool
|
||||
}
|
||||
|
||||
func ConnectServer(host string, port int64) (c *Conn, err error) {
|
||||
c = &Conn{}
|
||||
servertAddr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", host, port))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
conn, err := net.DialTCP("tcp", nil, servertAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.TcpConn = conn
|
||||
c.Reader = bufio.NewReader(c.TcpConn)
|
||||
c.closeFlag = false
|
||||
return c, nil
|
||||
}
|
||||
|
||||
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) ReadLine() (buff string, err error) {
|
||||
buff, err = c.Reader.ReadString('\n')
|
||||
if err == io.EOF {
|
||||
c.closeFlag = true
|
||||
}
|
||||
return buff, err
|
||||
}
|
||||
|
||||
func (c *Conn) Write(content string) (err error) {
|
||||
_, err = c.TcpConn.Write([]byte(content))
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
func (c *Conn) Close() {
|
||||
if c.TcpConn != nil && c.closeFlag == false {
|
||||
c.closeFlag = true
|
||||
c.TcpConn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) IsClosed() bool {
|
||||
return c.closeFlag
|
||||
}
|
||||
|
||||
// will block until connection close
|
||||
func Join(c1 *Conn, c2 *Conn) {
|
||||
var wait sync.WaitGroup
|
||||
pipe := func(to *Conn, from *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
|
||||
}
|
||||
|
||||
// messages from c1 to c2 will be encrypted
|
||||
// and from c2 to c1 will be decrypted
|
||||
func JoinMore(c1 *Conn, c2 *Conn, cryptKey string) {
|
||||
var wait sync.WaitGroup
|
||||
encryptPipe := func(from *Conn, to *Conn, key string) {
|
||||
defer from.Close()
|
||||
defer to.Close()
|
||||
defer wait.Done()
|
||||
|
||||
// we don't care about errors here
|
||||
PipeEncrypt(from.TcpConn, to.TcpConn, key)
|
||||
}
|
||||
|
||||
decryptPipe := func(to *Conn, from *Conn, key string) {
|
||||
defer from.Close()
|
||||
defer to.Close()
|
||||
defer wait.Done()
|
||||
|
||||
// we don't care about errors here
|
||||
PipeDecrypt(to.TcpConn, from.TcpConn, key)
|
||||
}
|
||||
|
||||
wait.Add(2)
|
||||
go encryptPipe(c1, c2, cryptKey)
|
||||
go decryptPipe(c2, c1, cryptKey)
|
||||
wait.Wait()
|
||||
log.Debug("One tunnel stopped")
|
||||
return
|
||||
}
|
||||
|
||||
// decrypt msg from reader, then write into writer
|
||||
func PipeDecrypt(r net.Conn, w net.Conn, key string) error {
|
||||
laes := new(pcrypto.Pcrypto)
|
||||
if err := laes.Init([]byte(key)); err != nil {
|
||||
log.Error("Pcrypto Init error: %v", err)
|
||||
return fmt.Errorf("Pcrypto Init error: %v", err)
|
||||
}
|
||||
|
||||
nreader := bufio.NewReader(r)
|
||||
for {
|
||||
buf, err := nreader.ReadBytes('\n')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := laes.Decrypt(buf)
|
||||
if err != nil {
|
||||
log.Error("Decrypt [%s] error, %v", string(buf), err)
|
||||
return fmt.Errorf("Decrypt [%s] error: %v", string(buf), err)
|
||||
}
|
||||
|
||||
_, err = w.Write(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// recvive msg from reader, then encrypt msg into write
|
||||
func PipeEncrypt(r net.Conn, w net.Conn, key string) error {
|
||||
laes := new(pcrypto.Pcrypto)
|
||||
if err := laes.Init([]byte(key)); err != nil {
|
||||
log.Error("Pcrypto Init error: %v", err)
|
||||
return fmt.Errorf("Pcrypto Init error: %v", err)
|
||||
}
|
||||
|
||||
nreader := bufio.NewReader(r)
|
||||
buf := make([]byte, 10*1024)
|
||||
|
||||
for {
|
||||
n, err := nreader.Read(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := laes.Encrypt(buf[:n])
|
||||
if err != nil {
|
||||
log.Error("Encrypt error: %v", err)
|
||||
return fmt.Errorf("Encrypt error: %v", err)
|
||||
}
|
||||
|
||||
res = append(res, '\n')
|
||||
_, err = w.Write(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -19,19 +19,21 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"frp/models/consts"
|
||||
"frp/models/msg"
|
||||
"frp/utils/conn"
|
||||
"frp/utils/log"
|
||||
"frp/utils/pcrypto"
|
||||
"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 {
|
||||
Name string
|
||||
AuthToken string
|
||||
LocalIp string
|
||||
LocalPort int64
|
||||
UseEncryption bool
|
||||
config.BaseConf
|
||||
LocalIp string
|
||||
LocalPort int64
|
||||
|
||||
RemotePort int64
|
||||
CustomDomains []string
|
||||
}
|
||||
|
||||
func (p *ProxyClient) GetLocalConn() (c *conn.Conn, err error) {
|
||||
@@ -56,12 +58,18 @@ func (p *ProxyClient) GetRemoteConn(addr string, port int64) (c *conn.Conn, err
|
||||
}
|
||||
|
||||
nowTime := time.Now().Unix()
|
||||
authKey := pcrypto.GetAuthKey(p.Name + p.AuthToken + fmt.Sprintf("%d", nowTime))
|
||||
req := &msg.ControlReq{
|
||||
Type: consts.NewWorkConn,
|
||||
ProxyName: p.Name,
|
||||
AuthKey: authKey,
|
||||
Timestamp: nowTime,
|
||||
Type: consts.NewWorkConn,
|
||||
ProxyName: p.Name,
|
||||
PrivilegeMode: p.PrivilegeMode,
|
||||
Timestamp: nowTime,
|
||||
}
|
||||
if p.PrivilegeMode == true {
|
||||
privilegeKey := pcrypto.GetAuthKey(p.Name + PrivilegeToken + fmt.Sprintf("%d", nowTime))
|
||||
req.PrivilegeKey = privilegeKey
|
||||
} else {
|
||||
authKey := pcrypto.GetAuthKey(p.Name + p.AuthToken + fmt.Sprintf("%d", nowTime))
|
||||
req.AuthKey = authKey
|
||||
}
|
||||
|
||||
buf, _ := json.Marshal(req)
|
||||
@@ -88,11 +96,8 @@ func (p *ProxyClient) StartTunnel(serverAddr string, serverPort int64) (err erro
|
||||
// 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())
|
||||
if p.UseEncryption {
|
||||
go conn.JoinMore(localConn, remoteConn, p.AuthToken)
|
||||
} else {
|
||||
go conn.Join(localConn, remoteConn)
|
||||
}
|
||||
needRecord := false
|
||||
go msg.JoinMore(localConn, remoteConn, p.BaseConf, needRecord)
|
||||
|
||||
return nil
|
||||
}
|
||||
228
src/models/client/config.go
Normal file
228
src/models/client/config.go
Normal file
@@ -0,0 +1,228 @@
|
||||
// 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"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
ini "github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
// common config
|
||||
var (
|
||||
ServerAddr string = "0.0.0.0"
|
||||
ServerPort int64 = 7000
|
||||
LogFile string = "console"
|
||||
LogWay string = "console"
|
||||
LogLevel string = "info"
|
||||
LogMaxDays int64 = 3
|
||||
PrivilegeToken string = ""
|
||||
HeartBeatInterval int64 = 20
|
||||
HeartBeatTimeout int64 = 90
|
||||
)
|
||||
|
||||
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", "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
|
||||
}
|
||||
|
||||
// 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" {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 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" {
|
||||
// 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
|
||||
domainStr, ok := section["custom_domains"]
|
||||
if ok {
|
||||
proxyClient.CustomDomains = strings.Split(domainStr, ",")
|
||||
if len(proxyClient.CustomDomains) == 0 {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals http", proxyClient.Name)
|
||||
}
|
||||
for i, domain := range proxyClient.CustomDomains {
|
||||
proxyClient.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain))
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals http", proxyClient.Name)
|
||||
}
|
||||
} else if proxyClient.Type == "https" {
|
||||
// custom_domains
|
||||
domainStr, ok := section["custom_domains"]
|
||||
if ok {
|
||||
proxyClient.CustomDomains = strings.Split(domainStr, ",")
|
||||
if len(proxyClient.CustomDomains) == 0 {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals https", proxyClient.Name)
|
||||
}
|
||||
for i, domain := range proxyClient.CustomDomains {
|
||||
proxyClient.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain))
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals http", proxyClient.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProxyClients[proxyClient.Name] = proxyClient
|
||||
}
|
||||
}
|
||||
|
||||
if len(ProxyClients) == 0 {
|
||||
return fmt.Errorf("Parse conf error: no proxy config found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -12,36 +12,16 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pcrypto
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEncrypt(t *testing.T) {
|
||||
pp := new(Pcrypto)
|
||||
pp.Init([]byte("Hana"))
|
||||
res, err := pp.Encrypt([]byte("Just One Test!"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("[%x]\n", res)
|
||||
}
|
||||
|
||||
func TestDecrypt(t *testing.T) {
|
||||
pp := new(Pcrypto)
|
||||
pp.Init([]byte("Hana"))
|
||||
res, err := pp.Encrypt([]byte("Just One Test!"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err = pp.Decrypt(res)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("[%s]\n", string(res))
|
||||
type BaseConf struct {
|
||||
Name string
|
||||
AuthToken string
|
||||
Type string
|
||||
UseEncryption bool
|
||||
UseGzip bool
|
||||
PrivilegeMode bool
|
||||
PrivilegeToken string
|
||||
PoolCount int64
|
||||
HostHeaderRewrite string
|
||||
}
|
||||
@@ -21,6 +21,14 @@ const (
|
||||
Closed
|
||||
)
|
||||
|
||||
var (
|
||||
StatusStr = []string{
|
||||
"idle",
|
||||
"working",
|
||||
"closed",
|
||||
}
|
||||
)
|
||||
|
||||
// msg type
|
||||
const (
|
||||
NewCtlConn = iota
|
||||
225
src/models/metric/server.go
Normal file
225
src/models/metric/server.go
Normal file
@@ -0,0 +1,225 @@
|
||||
// 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"`
|
||||
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 ©
|
||||
}
|
||||
|
||||
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,
|
||||
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
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -19,13 +19,23 @@ type GeneralRes struct {
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
// messages between control connection of frpc and frps
|
||||
// messages between control connections of frpc and frps
|
||||
type ControlReq struct {
|
||||
Type int64 `json:"type"`
|
||||
ProxyName string `json:"proxy_name,omitempty"`
|
||||
AuthKey string `json:"auth_key, omitempty"`
|
||||
UseEncryption bool `json:"use_encryption, omitempty"`
|
||||
Timestamp int64 `json:"timestamp, omitempty"`
|
||||
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"`
|
||||
HostHeaderRewrite string `json:"host_header_rewrite"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
type ControlRes struct {
|
||||
257
src/models/msg/process.go
Normal file
257
src/models/msg/process.go
Normal file
@@ -0,0 +1,257 @@
|
||||
// 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 *conn.Conn, c2 *conn.Conn, conf config.BaseConf, needRecord bool) {
|
||||
var wait sync.WaitGroup
|
||||
encryptPipe := func(from *conn.Conn, to *conn.Conn) {
|
||||
defer from.Close()
|
||||
defer to.Close()
|
||||
defer wait.Done()
|
||||
|
||||
// we don't care about errors here
|
||||
pipeEncrypt(from, to, conf, needRecord)
|
||||
}
|
||||
|
||||
decryptPipe := func(to *conn.Conn, from *conn.Conn) {
|
||||
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 *conn.Conn, w *conn.Conn, 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.WriteBytes(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 *conn.Conn, w *conn.Conn, 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.WriteBytes(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
370
src/models/server/config.go
Normal file
370
src/models/server/config.go
Normal file
@@ -0,0 +1,370 @@
|
||||
// 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
|
||||
AssetsDir string = ""
|
||||
LogFile string = "console"
|
||||
LogWay string = "console" // console or file
|
||||
LogLevel string = "info"
|
||||
LogMaxDays int64 = 3
|
||||
PrivilegeMode bool = false
|
||||
PrivilegeToken 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 = 90
|
||||
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", "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
|
||||
}
|
||||
}
|
||||
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" {
|
||||
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
|
||||
if proxyServer.Type == "tcp" {
|
||||
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, ",")
|
||||
if len(proxyServer.CustomDomains) == 0 {
|
||||
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals http", proxyServer.Name)
|
||||
}
|
||||
for i, domain := range proxyServer.CustomDomains {
|
||||
proxyServer.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain))
|
||||
}
|
||||
} else {
|
||||
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals http", proxyServer.Name)
|
||||
}
|
||||
} else if proxyServer.Type == "https" {
|
||||
// for https
|
||||
proxyServer.ListenPort = VhostHttpsPort
|
||||
|
||||
domainStr, ok := section["custom_domains"]
|
||||
if ok {
|
||||
proxyServer.CustomDomains = strings.Split(domainStr, ",")
|
||||
if len(proxyServer.CustomDomains) == 0 {
|
||||
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals https", proxyServer.Name)
|
||||
}
|
||||
for i, domain := range proxyServer.CustomDomains {
|
||||
proxyServer.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain))
|
||||
}
|
||||
} else {
|
||||
return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type equals 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.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.Close()
|
||||
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.ListenPort)
|
||||
s.Init()
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteProxy(proxyName string) {
|
||||
ProxyServersMutex.Lock()
|
||||
defer ProxyServersMutex.Unlock()
|
||||
delete(ProxyServers, proxyName)
|
||||
}
|
||||
60
src/models/server/dashboard.go
Normal file
60
src/models/server/dashboard.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// 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"
|
||||
"net/http"
|
||||
"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", apiReload)
|
||||
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("/", viewDashboard)
|
||||
|
||||
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
|
||||
}
|
||||
67
src/models/server/dashboard_api.go
Normal file
67
src/models/server/dashboard_api.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// 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)
|
||||
}
|
||||
40
src/models/server/dashboard_view.go
Normal file
40
src/models/server/dashboard_view.go
Normal file
@@ -0,0 +1,40 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
306
src/models/server/server.go
Normal file
306
src/models/server/server.go
Normal file
@@ -0,0 +1,306 @@
|
||||
// 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"
|
||||
"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"
|
||||
)
|
||||
|
||||
type Listener interface {
|
||||
Accept() (*conn.Conn, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
type ProxyServer struct {
|
||||
config.BaseConf
|
||||
BindAddr string
|
||||
ListenPort int64
|
||||
CustomDomains []string
|
||||
|
||||
Status int64
|
||||
CtlConn *conn.Conn // control connection with frpc
|
||||
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
|
||||
mutex sync.RWMutex
|
||||
closeChan chan struct{} // for notify other goroutines that the proxy is closed by close this channel
|
||||
}
|
||||
|
||||
func NewProxyServer() (p *ProxyServer) {
|
||||
p = &ProxyServer{
|
||||
CustomDomains: 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.ListenPort = req.RemotePort
|
||||
} else if p.Type == "http" {
|
||||
p.ListenPort = VhostHttpPort
|
||||
} else if p.Type == "https" {
|
||||
p.ListenPort = VhostHttpsPort
|
||||
}
|
||||
p.CustomDomains = req.CustomDomains
|
||||
p.HostHeaderRewrite = req.HostHeaderRewrite
|
||||
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.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
|
||||
}
|
||||
}
|
||||
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 {
|
||||
l, err := VhostHttpMuxer.Listen(domain, p.HostHeaderRewrite)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.listeners = append(p.listeners, l)
|
||||
}
|
||||
} else if p.Type == "https" {
|
||||
for _, domain := range p.CustomDomains {
|
||||
l, err := VhostHttpsMuxer.Listen(domain, p.HostHeaderRewrite)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.listeners = append(p.listeners, l)
|
||||
}
|
||||
}
|
||||
|
||||
p.Lock()
|
||||
p.Status = consts.Working
|
||||
p.Unlock()
|
||||
metric.SetStatus(p.Name, p.Status)
|
||||
|
||||
// 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()
|
||||
if p.Status != consts.Closed {
|
||||
p.Status = consts.Closed
|
||||
for _, l := range p.listeners {
|
||||
if l != nil {
|
||||
l.Close()
|
||||
}
|
||||
}
|
||||
close(p.ctlMsgChan)
|
||||
close(p.workConnChan)
|
||||
close(p.closeChan)
|
||||
if p.CtlConn != nil {
|
||||
p.CtlConn.Close()
|
||||
}
|
||||
}
|
||||
metric.SetStatus(p.Name, p.Status)
|
||||
// if the proxy created by PrivilegeMode, delete it when closed
|
||||
if p.PrivilegeMode {
|
||||
DeleteProxy(p.Name)
|
||||
}
|
||||
p.Unlock()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
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{}) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
221
src/utils/conn/conn.go
Normal file
221
src/utils/conn/conn.go
Normal file
@@ -0,0 +1,221 @@
|
||||
// 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"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
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 := &Conn{
|
||||
TcpConn: conn,
|
||||
closeFlag: false,
|
||||
}
|
||||
c.Reader = bufio.NewReader(c.TcpConn)
|
||||
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
|
||||
closeFlag bool
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func NewConn(conn net.Conn) (c *Conn) {
|
||||
c = &Conn{}
|
||||
c.TcpConn = conn
|
||||
c.Reader = bufio.NewReader(c.TcpConn)
|
||||
c.closeFlag = false
|
||||
return c
|
||||
}
|
||||
|
||||
func ConnectServer(host string, port int64) (c *Conn, err error) {
|
||||
c = &Conn{}
|
||||
servertAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", host, port))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
conn, err := net.DialTCP("tcp", nil, servertAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.TcpConn = conn
|
||||
c.Reader = bufio.NewReader(c.TcpConn)
|
||||
c.closeFlag = false
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
n, err = c.Reader.Read(p)
|
||||
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) WriteBytes(content []byte) (n int, err error) {
|
||||
n, err = c.TcpConn.Write(content)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Conn) Write(content string) (err error) {
|
||||
_, err = c.TcpConn.Write([]byte(content))
|
||||
return err
|
||||
}
|
||||
|
||||
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() {
|
||||
c.mutex.Lock()
|
||||
if c.TcpConn != nil && c.closeFlag == false {
|
||||
c.closeFlag = true
|
||||
c.TcpConn.Close()
|
||||
}
|
||||
c.mutex.Unlock()
|
||||
}
|
||||
|
||||
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
|
||||
// remote client won't send any bytes to this socket
|
||||
func (c *Conn) CheckClosed() bool {
|
||||
c.mutex.RLock()
|
||||
if c.closeFlag {
|
||||
return true
|
||||
}
|
||||
c.mutex.RUnlock()
|
||||
|
||||
// err := c.TcpConn.SetReadDeadline(time.Now().Add(100 * time.Microsecond))
|
||||
err := c.TcpConn.SetReadDeadline(time.Now().Add(time.Millisecond))
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return true
|
||||
}
|
||||
|
||||
var tmp []byte = make([]byte, 1)
|
||||
_, err = c.TcpConn.Read(tmp)
|
||||
if err == io.EOF {
|
||||
return true
|
||||
}
|
||||
|
||||
err = c.TcpConn.SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/astaxie/beego/logs"
|
||||
)
|
||||
|
||||
@@ -26,24 +27,24 @@ func init() {
|
||||
Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
|
||||
}
|
||||
|
||||
func InitLog(logWay string, logFile string, logLevel string) {
|
||||
SetLogFile(logWay, logFile)
|
||||
func InitLog(logWay string, logFile string, logLevel string, maxdays int64) {
|
||||
SetLogFile(logWay, logFile, maxdays)
|
||||
SetLogLevel(logLevel)
|
||||
}
|
||||
|
||||
// logWay: such as file or console
|
||||
func SetLogFile(logWay string, logFile string) {
|
||||
// logWay: file or console
|
||||
func SetLogFile(logWay string, logFile string, maxdays int64) {
|
||||
if logWay == "console" {
|
||||
Log.SetLogger("console", "")
|
||||
} else {
|
||||
Log.SetLogger("file", `{"filename": "`+logFile+`"}`)
|
||||
params := fmt.Sprintf(`{"filename": "%s", "maxdays": %d}`, logFile, maxdays)
|
||||
Log.SetLogger("file", params)
|
||||
}
|
||||
}
|
||||
|
||||
// value: error, warning, info, debug
|
||||
func SetLogLevel(logLevel string) {
|
||||
level := 4 // warning
|
||||
|
||||
switch logLevel {
|
||||
case "error":
|
||||
level = 3
|
||||
@@ -56,7 +57,6 @@ func SetLogLevel(logLevel string) {
|
||||
default:
|
||||
level = 4
|
||||
}
|
||||
|
||||
Log.SetLevel(level)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -36,40 +35,21 @@ func (pc *Pcrypto) Init(key []byte) error {
|
||||
var err error
|
||||
pc.pkey = pKCS7Padding(key, aes.BlockSize)
|
||||
pc.paes, err = aes.NewCipher(pc.pkey)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (pc *Pcrypto) Encrypt(src []byte) ([]byte, error) {
|
||||
// gzip
|
||||
var zbuf bytes.Buffer
|
||||
zwr, err := gzip.NewWriterLevel(&zbuf, -1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer zwr.Close()
|
||||
zwr.Write(src)
|
||||
zwr.Flush()
|
||||
|
||||
// aes
|
||||
src = pKCS7Padding(zbuf.Bytes(), aes.BlockSize)
|
||||
src = pKCS7Padding(src, aes.BlockSize)
|
||||
blockMode := cipher.NewCBCEncrypter(pc.paes, pc.pkey)
|
||||
crypted := make([]byte, len(src))
|
||||
blockMode.CryptBlocks(crypted, src)
|
||||
|
||||
// base64
|
||||
return []byte(base64.StdEncoding.EncodeToString(crypted)), nil
|
||||
return crypted, nil
|
||||
}
|
||||
|
||||
func (pc *Pcrypto) Decrypt(str []byte) ([]byte, error) {
|
||||
// base64
|
||||
data, err := base64.StdEncoding.DecodeString(string(str))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// aes
|
||||
decryptText, err := hex.DecodeString(fmt.Sprintf("%x", data))
|
||||
decryptText, err := hex.DecodeString(fmt.Sprintf("%x", str))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -81,18 +61,30 @@ func (pc *Pcrypto) Decrypt(str []byte) ([]byte, error) {
|
||||
blockMode := cipher.NewCBCDecrypter(pc.paes, pc.pkey)
|
||||
|
||||
blockMode.CryptBlocks(decryptText, decryptText)
|
||||
decryptText = pKCS7UnPadding(decryptText)
|
||||
return pKCS7UnPadding(decryptText), nil
|
||||
}
|
||||
|
||||
// gunzip
|
||||
zbuf := bytes.NewBuffer(decryptText)
|
||||
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()
|
||||
data, _ = ioutil.ReadAll(zrd)
|
||||
|
||||
return data, nil
|
||||
str, _ := ioutil.ReadAll(zrd)
|
||||
return str, nil
|
||||
}
|
||||
|
||||
func pKCS7Padding(ciphertext []byte, blockSize int) []byte {
|
||||
62
src/utils/pcrypto/pcrypto_test.go
Normal file
62
src/utils/pcrypto/pcrypto_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// 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("Hana"))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
58
src/utils/pool/pool.go
Normal file
58
src/utils/pool/pool.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// 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 pool
|
||||
|
||||
import "sync"
|
||||
|
||||
var (
|
||||
bufPool5k sync.Pool
|
||||
bufPool2k sync.Pool
|
||||
bufPool1k sync.Pool
|
||||
bufPool sync.Pool
|
||||
)
|
||||
|
||||
func GetBuf(size int) []byte {
|
||||
var x interface{}
|
||||
if size >= 5*1024 {
|
||||
x = bufPool5k.Get()
|
||||
} else if size >= 2*1024 {
|
||||
x = bufPool2k.Get()
|
||||
} else if size >= 1*1024 {
|
||||
x = bufPool1k.Get()
|
||||
} else {
|
||||
x = bufPool.Get()
|
||||
}
|
||||
if x == nil {
|
||||
return make([]byte, size)
|
||||
}
|
||||
buf := x.([]byte)
|
||||
if cap(buf) < size {
|
||||
return make([]byte, size)
|
||||
}
|
||||
return buf[:size]
|
||||
}
|
||||
|
||||
func PutBuf(buf []byte) {
|
||||
size := cap(buf)
|
||||
if size >= 5*1024 {
|
||||
bufPool5k.Put(buf)
|
||||
} else if size >= 2*1024 {
|
||||
bufPool2k.Put(buf)
|
||||
} else if size >= 1*1024 {
|
||||
bufPool1k.Put(buf)
|
||||
} else {
|
||||
bufPool.Put(buf)
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var version string = "0.3.0"
|
||||
var version string = "0.8.0"
|
||||
|
||||
func Full() string {
|
||||
return version
|
||||
171
src/utils/vhost/http.go
Normal file
171
src/utils/vhost/http.go
Normal file
@@ -0,0 +1,171 @@
|
||||
// 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 vhost
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/src/utils/conn"
|
||||
"github.com/fatedier/frp/src/utils/pool"
|
||||
)
|
||||
|
||||
type HttpMuxer struct {
|
||||
*VhostMuxer
|
||||
}
|
||||
|
||||
func GetHttpHostname(c *conn.Conn) (_ net.Conn, routerName string, err error) {
|
||||
sc, rd := newShareConn(c.TcpConn)
|
||||
|
||||
request, err := http.ReadRequest(bufio.NewReader(rd))
|
||||
if err != nil {
|
||||
return sc, "", err
|
||||
}
|
||||
tmpArr := strings.Split(request.Host, ":")
|
||||
routerName = tmpArr[0]
|
||||
request.Body.Close()
|
||||
return sc, routerName, nil
|
||||
}
|
||||
|
||||
func NewHttpMuxer(listener *conn.Listener, timeout time.Duration) (*HttpMuxer, error) {
|
||||
mux, err := NewVhostMuxer(listener, GetHttpHostname, HttpHostNameRewrite, timeout)
|
||||
return &HttpMuxer{mux}, err
|
||||
}
|
||||
|
||||
func HttpHostNameRewrite(c *conn.Conn, rewriteHost string) (_ net.Conn, err error) {
|
||||
sc, rd := newShareConn(c.TcpConn)
|
||||
var buff []byte
|
||||
if buff, err = hostNameRewrite(rd, rewriteHost); err != nil {
|
||||
return sc, err
|
||||
}
|
||||
err = sc.WriteBuff(buff)
|
||||
return sc, err
|
||||
}
|
||||
|
||||
func hostNameRewrite(request io.Reader, rewriteHost string) (_ []byte, err error) {
|
||||
buf := pool.GetBuf(1024)
|
||||
defer pool.PutBuf(buf)
|
||||
|
||||
request.Read(buf)
|
||||
retBuffer, err := parseRequest(buf, rewriteHost)
|
||||
return retBuffer, err
|
||||
}
|
||||
|
||||
func parseRequest(org []byte, rewriteHost string) (ret []byte, err error) {
|
||||
tp := bytes.NewBuffer(org)
|
||||
// First line: GET /index.html HTTP/1.0
|
||||
var b []byte
|
||||
if b, err = tp.ReadBytes('\n'); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req := new(http.Request)
|
||||
// we invoked ReadRequest in GetHttpHostname before, so we ignore error
|
||||
req.Method, req.RequestURI, req.Proto, _ = parseRequestLine(string(b))
|
||||
rawurl := req.RequestURI
|
||||
// CONNECT www.google.com:443 HTTP/1.1
|
||||
justAuthority := req.Method == "CONNECT" && !strings.HasPrefix(rawurl, "/")
|
||||
if justAuthority {
|
||||
rawurl = "http://" + rawurl
|
||||
}
|
||||
req.URL, _ = url.ParseRequestURI(rawurl)
|
||||
if justAuthority {
|
||||
// Strip the bogus "http://" back off.
|
||||
req.URL.Scheme = ""
|
||||
}
|
||||
|
||||
// RFC2616: first case
|
||||
// GET /index.html HTTP/1.1
|
||||
// Host: www.google.com
|
||||
if req.URL.Host == "" {
|
||||
changedBuf, err := changeHostName(tp, rewriteHost)
|
||||
buf := new(bytes.Buffer)
|
||||
buf.Write(b)
|
||||
buf.Write(changedBuf)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
// RFC2616: second case
|
||||
// GET http://www.google.com/index.html HTTP/1.1
|
||||
// Host: doesntmatter
|
||||
// In this case, any Host line is ignored.
|
||||
hostPort := strings.Split(req.URL.Host, ":")
|
||||
if len(hostPort) == 1 {
|
||||
req.URL.Host = rewriteHost
|
||||
} else if len(hostPort) == 2 {
|
||||
req.URL.Host = fmt.Sprintf("%s:%s", rewriteHost, hostPort[1])
|
||||
}
|
||||
firstLine := req.Method + " " + req.URL.String() + " " + req.Proto
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString(firstLine)
|
||||
tp.WriteTo(buf)
|
||||
return buf.Bytes(), err
|
||||
|
||||
}
|
||||
|
||||
// parseRequestLine parses "GET /foo HTTP/1.1" into its three parts.
|
||||
func parseRequestLine(line string) (method, requestURI, proto string, ok bool) {
|
||||
s1 := strings.Index(line, " ")
|
||||
s2 := strings.Index(line[s1+1:], " ")
|
||||
if s1 < 0 || s2 < 0 {
|
||||
return
|
||||
}
|
||||
s2 += s1 + 1
|
||||
return line[:s1], line[s1+1 : s2], line[s2+1:], true
|
||||
}
|
||||
|
||||
func changeHostName(buff *bytes.Buffer, rewriteHost string) (_ []byte, err error) {
|
||||
retBuf := new(bytes.Buffer)
|
||||
|
||||
peek := buff.Bytes()
|
||||
for len(peek) > 0 {
|
||||
i := bytes.IndexByte(peek, '\n')
|
||||
if i < 3 {
|
||||
// Not present (-1) or found within the next few bytes,
|
||||
// implying we're at the end ("\r\n\r\n" or "\n\n")
|
||||
return nil, err
|
||||
}
|
||||
kv := peek[:i]
|
||||
j := bytes.IndexByte(kv, ':')
|
||||
if j < 0 {
|
||||
return nil, fmt.Errorf("malformed MIME header line: " + string(kv))
|
||||
}
|
||||
if strings.Contains(strings.ToLower(string(kv[:j])), "host") {
|
||||
var hostHeader string
|
||||
portPos := bytes.IndexByte(kv[j+1:], ':')
|
||||
if portPos == -1 {
|
||||
hostHeader = fmt.Sprintf("Host: %s\n", rewriteHost)
|
||||
} else {
|
||||
hostHeader = fmt.Sprintf("Host: %s:%s\n", rewriteHost, kv[portPos+1:])
|
||||
}
|
||||
retBuf.WriteString(hostHeader)
|
||||
peek = peek[i+1:]
|
||||
break
|
||||
} else {
|
||||
retBuf.Write(peek[:i])
|
||||
retBuf.WriteByte('\n')
|
||||
}
|
||||
|
||||
peek = peek[i+1:]
|
||||
}
|
||||
retBuf.Write(peek)
|
||||
return retBuf.Bytes(), err
|
||||
}
|
||||
188
src/utils/vhost/https.go
Normal file
188
src/utils/vhost/https.go
Normal file
@@ -0,0 +1,188 @@
|
||||
// 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 vhost
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/src/utils/conn"
|
||||
"github.com/fatedier/frp/src/utils/pool"
|
||||
)
|
||||
|
||||
const (
|
||||
typeClientHello uint8 = 1 // Type client hello
|
||||
)
|
||||
|
||||
// TLS extension numbers
|
||||
const (
|
||||
extensionServerName uint16 = 0
|
||||
extensionStatusRequest uint16 = 5
|
||||
extensionSupportedCurves uint16 = 10
|
||||
extensionSupportedPoints uint16 = 11
|
||||
extensionSignatureAlgorithms uint16 = 13
|
||||
extensionALPN uint16 = 16
|
||||
extensionSCT uint16 = 18
|
||||
extensionSessionTicket uint16 = 35
|
||||
extensionNextProtoNeg uint16 = 13172 // not IANA assigned
|
||||
extensionRenegotiationInfo uint16 = 0xff01
|
||||
)
|
||||
|
||||
type HttpsMuxer struct {
|
||||
*VhostMuxer
|
||||
}
|
||||
|
||||
func NewHttpsMuxer(listener *conn.Listener, timeout time.Duration) (*HttpsMuxer, error) {
|
||||
mux, err := NewVhostMuxer(listener, GetHttpsHostname, nil, timeout)
|
||||
return &HttpsMuxer{mux}, err
|
||||
}
|
||||
|
||||
func readHandshake(rd io.Reader) (host string, err error) {
|
||||
data := pool.GetBuf(1024)
|
||||
origin := data
|
||||
defer pool.PutBuf(origin)
|
||||
length, err := rd.Read(data)
|
||||
if err != nil {
|
||||
return
|
||||
} else {
|
||||
if length < 47 {
|
||||
err = fmt.Errorf("readHandshake: proto length[%d] is too short", length)
|
||||
return
|
||||
}
|
||||
}
|
||||
data = data[:length]
|
||||
if uint8(data[5]) != typeClientHello {
|
||||
err = fmt.Errorf("readHandshake: type[%d] is not clientHello", uint16(data[5]))
|
||||
return
|
||||
}
|
||||
|
||||
// session
|
||||
sessionIdLen := int(data[43])
|
||||
if sessionIdLen > 32 || len(data) < 44+sessionIdLen {
|
||||
err = fmt.Errorf("readHandshake: sessionIdLen[%d] is long", sessionIdLen)
|
||||
return
|
||||
}
|
||||
data = data[44+sessionIdLen:]
|
||||
if len(data) < 2 {
|
||||
err = fmt.Errorf("readHandshake: dataLen[%d] after session is short", len(data))
|
||||
return
|
||||
}
|
||||
|
||||
// cipher suite numbers
|
||||
cipherSuiteLen := int(data[0])<<8 | int(data[1])
|
||||
if cipherSuiteLen%2 == 1 || len(data) < 2+cipherSuiteLen {
|
||||
err = fmt.Errorf("readHandshake: dataLen[%d] after cipher suite is short", len(data))
|
||||
return
|
||||
}
|
||||
data = data[2+cipherSuiteLen:]
|
||||
if len(data) < 1 {
|
||||
err = fmt.Errorf("readHandshake: cipherSuiteLen[%d] is long", cipherSuiteLen)
|
||||
return
|
||||
}
|
||||
|
||||
// compression method
|
||||
compressionMethodsLen := int(data[0])
|
||||
if len(data) < 1+compressionMethodsLen {
|
||||
err = fmt.Errorf("readHandshake: compressionMethodsLen[%d] is long", compressionMethodsLen)
|
||||
return
|
||||
}
|
||||
|
||||
data = data[1+compressionMethodsLen:]
|
||||
if len(data) == 0 {
|
||||
// ClientHello is optionally followed by extension data
|
||||
err = fmt.Errorf("readHandshake: there is no extension data to get servername")
|
||||
return
|
||||
}
|
||||
if len(data) < 2 {
|
||||
err = fmt.Errorf("readHandshake: extension dataLen[%d] is too short")
|
||||
return
|
||||
}
|
||||
|
||||
extensionsLength := int(data[0])<<8 | int(data[1])
|
||||
data = data[2:]
|
||||
if extensionsLength != len(data) {
|
||||
err = fmt.Errorf("readHandshake: extensionsLen[%d] is not equal to dataLen[%d]", extensionsLength, len(data))
|
||||
return
|
||||
}
|
||||
for len(data) != 0 {
|
||||
if len(data) < 4 {
|
||||
err = fmt.Errorf("readHandshake: extensionsDataLen[%d] is too short", len(data))
|
||||
return
|
||||
}
|
||||
extension := uint16(data[0])<<8 | uint16(data[1])
|
||||
length := int(data[2])<<8 | int(data[3])
|
||||
data = data[4:]
|
||||
if len(data) < length {
|
||||
err = fmt.Errorf("readHandshake: extensionLen[%d] is long", length)
|
||||
return
|
||||
}
|
||||
|
||||
switch extension {
|
||||
case extensionRenegotiationInfo:
|
||||
if length != 1 || data[0] != 0 {
|
||||
err = fmt.Errorf("readHandshake: extension reNegotiationInfoLen[%d] is short", length)
|
||||
return
|
||||
}
|
||||
case extensionNextProtoNeg:
|
||||
case extensionStatusRequest:
|
||||
case extensionServerName:
|
||||
d := data[:length]
|
||||
if len(d) < 2 {
|
||||
err = fmt.Errorf("readHandshake: remiaining dataLen[%d] is short", len(d))
|
||||
return
|
||||
}
|
||||
namesLen := int(d[0])<<8 | int(d[1])
|
||||
d = d[2:]
|
||||
if len(d) != namesLen {
|
||||
err = fmt.Errorf("readHandshake: nameListLen[%d] is not equal to dataLen[%d]", namesLen, len(d))
|
||||
return
|
||||
}
|
||||
for len(d) > 0 {
|
||||
if len(d) < 3 {
|
||||
err = fmt.Errorf("readHandshake: extension serverNameLen[%d] is short", len(d))
|
||||
return
|
||||
}
|
||||
nameType := d[0]
|
||||
nameLen := int(d[1])<<8 | int(d[2])
|
||||
d = d[3:]
|
||||
if len(d) < nameLen {
|
||||
err = fmt.Errorf("readHandshake: nameLen[%d] is not equal to dataLen[%d]", nameLen, len(d))
|
||||
return
|
||||
}
|
||||
if nameType == 0 {
|
||||
serverName := string(d[:nameLen])
|
||||
host = strings.TrimSpace(serverName)
|
||||
return host, nil
|
||||
}
|
||||
d = d[nameLen:]
|
||||
}
|
||||
}
|
||||
data = data[length:]
|
||||
}
|
||||
err = fmt.Errorf("Unknow error")
|
||||
return
|
||||
}
|
||||
|
||||
func GetHttpsHostname(c *conn.Conn) (sc net.Conn, routerName string, err error) {
|
||||
sc, rd := newShareConn(c.TcpConn)
|
||||
host, err := readHandshake(rd)
|
||||
if err != nil {
|
||||
return sc, "", err
|
||||
}
|
||||
return sc, host, nil
|
||||
}
|
||||
191
src/utils/vhost/vhost.go
Normal file
191
src/utils/vhost/vhost.go
Normal file
@@ -0,0 +1,191 @@
|
||||
// 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 vhost
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/src/utils/conn"
|
||||
)
|
||||
|
||||
type muxFunc func(*conn.Conn) (net.Conn, string, error)
|
||||
type hostRewriteFunc func(*conn.Conn, string) (net.Conn, error)
|
||||
|
||||
type VhostMuxer struct {
|
||||
listener *conn.Listener
|
||||
timeout time.Duration
|
||||
vhostFunc muxFunc
|
||||
rewriteFunc hostRewriteFunc
|
||||
registryMap map[string]*Listener
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func NewVhostMuxer(listener *conn.Listener, vhostFunc muxFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) {
|
||||
mux = &VhostMuxer{
|
||||
listener: listener,
|
||||
timeout: timeout,
|
||||
vhostFunc: vhostFunc,
|
||||
rewriteFunc: rewriteFunc,
|
||||
registryMap: make(map[string]*Listener),
|
||||
}
|
||||
go mux.run()
|
||||
return mux, nil
|
||||
}
|
||||
|
||||
// listen for a new domain name, if rewriteHost is not empty and rewriteFunc is not nil, then rewrite the host header to rewriteHost
|
||||
func (v *VhostMuxer) Listen(name string, rewriteHost string) (l *Listener, err error) {
|
||||
v.mutex.Lock()
|
||||
defer v.mutex.Unlock()
|
||||
if _, exist := v.registryMap[name]; exist {
|
||||
return nil, fmt.Errorf("domain name %s is already bound", name)
|
||||
}
|
||||
|
||||
l = &Listener{
|
||||
name: name,
|
||||
rewriteHost: rewriteHost,
|
||||
mux: v,
|
||||
accept: make(chan *conn.Conn),
|
||||
}
|
||||
v.registryMap[name] = l
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (v *VhostMuxer) getListener(name string) (l *Listener, exist bool) {
|
||||
v.mutex.RLock()
|
||||
defer v.mutex.RUnlock()
|
||||
l, exist = v.registryMap[name]
|
||||
return l, exist
|
||||
}
|
||||
|
||||
func (v *VhostMuxer) unRegister(name string) {
|
||||
v.mutex.Lock()
|
||||
defer v.mutex.Unlock()
|
||||
delete(v.registryMap, name)
|
||||
}
|
||||
|
||||
func (v *VhostMuxer) run() {
|
||||
for {
|
||||
conn, err := v.listener.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go v.handle(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VhostMuxer) handle(c *conn.Conn) {
|
||||
if err := c.SetDeadline(time.Now().Add(v.timeout)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sConn, name, err := v.vhostFunc(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
name = strings.ToLower(name)
|
||||
l, ok := v.getListener(name)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if err = sConn.SetDeadline(time.Time{}); err != nil {
|
||||
return
|
||||
}
|
||||
c.SetTcpConn(sConn)
|
||||
|
||||
l.accept <- c
|
||||
}
|
||||
|
||||
type Listener struct {
|
||||
name string
|
||||
rewriteHost string
|
||||
mux *VhostMuxer // for closing VhostMuxer
|
||||
accept chan *conn.Conn
|
||||
}
|
||||
|
||||
func (l *Listener) Accept() (*conn.Conn, error) {
|
||||
conn, ok := <-l.accept
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Listener closed")
|
||||
}
|
||||
|
||||
// if rewriteFunc is exist and rewriteHost is set
|
||||
// rewrite http requests with a modified host header
|
||||
if l.mux.rewriteFunc != nil && l.rewriteHost != "" {
|
||||
sConn, err := l.mux.rewriteFunc(conn, l.rewriteHost)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("http host header rewrite failed")
|
||||
}
|
||||
conn.SetTcpConn(sConn)
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (l *Listener) Close() error {
|
||||
l.mux.unRegister(l.name)
|
||||
close(l.accept)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Listener) Name() string {
|
||||
return l.name
|
||||
}
|
||||
|
||||
type sharedConn struct {
|
||||
net.Conn
|
||||
sync.Mutex
|
||||
buff *bytes.Buffer
|
||||
}
|
||||
|
||||
// the bytes you read in io.Reader, will be reserved in sharedConn
|
||||
func newShareConn(conn net.Conn) (*sharedConn, io.Reader) {
|
||||
sc := &sharedConn{
|
||||
Conn: conn,
|
||||
buff: bytes.NewBuffer(make([]byte, 0, 1024)),
|
||||
}
|
||||
return sc, io.TeeReader(conn, sc.buff)
|
||||
}
|
||||
|
||||
func (sc *sharedConn) Read(p []byte) (n int, err error) {
|
||||
sc.Lock()
|
||||
if sc.buff == nil {
|
||||
sc.Unlock()
|
||||
return sc.Conn.Read(p)
|
||||
}
|
||||
n, err = sc.buff.Read(p)
|
||||
|
||||
if err == io.EOF {
|
||||
sc.buff = nil
|
||||
var n2 int
|
||||
n2, err = sc.Conn.Read(p[n:])
|
||||
|
||||
n += n2
|
||||
}
|
||||
sc.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (sc *sharedConn) WriteBuff(buffer []byte) (err error) {
|
||||
sc.buff.Reset()
|
||||
_, err = sc.buff.Write(buffer)
|
||||
return err
|
||||
}
|
||||
24
test/clean_test.sh
Executable file
24
test/clean_test.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/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}
|
||||
fi
|
||||
|
||||
pid=`ps aux|grep './../bin/frpc -c ./conf/auto_test_frpc.ini'|grep -v grep|awk {'print $2'}`
|
||||
if [ -n "${pid}" ]; then
|
||||
kill ${pid}
|
||||
fi
|
||||
|
||||
rm -f ./frps.log
|
||||
rm -f ./frpc.log
|
||||
20
test/conf/auto_test_frpc.ini
Normal file
20
test/conf/auto_test_frpc.ini
Normal file
@@ -0,0 +1,20 @@
|
||||
[common]
|
||||
server_addr = 0.0.0.0
|
||||
server_port = 10700
|
||||
log_file = ./frpc.log
|
||||
# debug, info, warn, error
|
||||
log_level = debug
|
||||
auth_token = 123
|
||||
|
||||
[echo]
|
||||
type = tcp
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 10701
|
||||
use_encryption = true
|
||||
use_gzip = true
|
||||
|
||||
[web]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 10702
|
||||
use_gzip = true
|
||||
17
test/conf/auto_test_frps.ini
Normal file
17
test/conf/auto_test_frps.ini
Normal file
@@ -0,0 +1,17 @@
|
||||
[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
|
||||
45
test/echo_server.go
Normal file
45
test/echo_server.go
Normal file
@@ -0,0 +1,45 @@
|
||||
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.Write(buff)
|
||||
}
|
||||
}
|
||||
60
test/func_test.go
Normal file
60
test/func_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/src/utils/conn"
|
||||
)
|
||||
|
||||
var (
|
||||
ECHO_PORT int64 = 10711
|
||||
HTTP_PORT int64 = 10710
|
||||
ECHO_TEST_STR string = "Hello World\n"
|
||||
HTTP_RES_STR string = "Hello World"
|
||||
)
|
||||
|
||||
func TestEchoServer(t *testing.T) {
|
||||
c, err := conn.ConnectServer("0.0.0.0", 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.Write(ECHO_TEST_STR)
|
||||
|
||||
buff, err := c.ReadLine()
|
||||
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"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHttpServer(t *testing.T) {
|
||||
client := &http.Client{}
|
||||
req, _ := http.NewRequest("GET", fmt.Sprintf("http://127.0.0.1:%d", HTTP_PORT), nil)
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("do http request error: %v", err)
|
||||
}
|
||||
if res.StatusCode == 200 {
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("read from http server error: %v", err)
|
||||
}
|
||||
bodystr := string(body)
|
||||
if bodystr != HTTP_RES_STR {
|
||||
t.Fatalf("content from http server error [%s], correct string is [%s]", bodystr, HTTP_RES_STR)
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("http code from http server error [%d]", res.StatusCode)
|
||||
}
|
||||
}
|
||||
20
test/http_server.go
Normal file
20
test/http_server.go
Normal file
@@ -0,0 +1,20 @@
|
||||
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))
|
||||
}
|
||||
34
test/run_test.sh
Executable file
34
test/run_test.sh
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/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
|
||||
@@ -17,14 +17,14 @@ package logs
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ConnWriter implements LoggerInterface.
|
||||
// connWriter implements LoggerInterface.
|
||||
// it writes messages in keep-live tcp connection.
|
||||
type ConnWriter struct {
|
||||
lg *log.Logger
|
||||
type connWriter struct {
|
||||
lg *logWriter
|
||||
innerWriter io.WriteCloser
|
||||
ReconnectOnMsg bool `json:"reconnectOnMsg"`
|
||||
Reconnect bool `json:"reconnect"`
|
||||
@@ -33,26 +33,26 @@ type ConnWriter struct {
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
// create new ConnWrite returning as LoggerInterface.
|
||||
func NewConn() LoggerInterface {
|
||||
conn := new(ConnWriter)
|
||||
// NewConn create new ConnWrite returning as LoggerInterface.
|
||||
func NewConn() Logger {
|
||||
conn := new(connWriter)
|
||||
conn.Level = LevelTrace
|
||||
return conn
|
||||
}
|
||||
|
||||
// init connection writer with json config.
|
||||
// Init init connection writer with json config.
|
||||
// json config only need key "level".
|
||||
func (c *ConnWriter) Init(jsonconfig string) error {
|
||||
return json.Unmarshal([]byte(jsonconfig), c)
|
||||
func (c *connWriter) Init(jsonConfig string) error {
|
||||
return json.Unmarshal([]byte(jsonConfig), c)
|
||||
}
|
||||
|
||||
// write message in connection.
|
||||
// WriteMsg write message in connection.
|
||||
// if connection is down, try to re-connect.
|
||||
func (c *ConnWriter) WriteMsg(msg string, level int) error {
|
||||
func (c *connWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||
if level > c.Level {
|
||||
return nil
|
||||
}
|
||||
if c.neddedConnectOnMsg() {
|
||||
if c.needToConnectOnMsg() {
|
||||
err := c.connect()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -62,23 +62,24 @@ func (c *ConnWriter) WriteMsg(msg string, level int) error {
|
||||
if c.ReconnectOnMsg {
|
||||
defer c.innerWriter.Close()
|
||||
}
|
||||
c.lg.Println(msg)
|
||||
|
||||
c.lg.println(when, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// implementing method. empty.
|
||||
func (c *ConnWriter) Flush() {
|
||||
// Flush implementing method. empty.
|
||||
func (c *connWriter) Flush() {
|
||||
|
||||
}
|
||||
|
||||
// destroy connection writer and close tcp listener.
|
||||
func (c *ConnWriter) Destroy() {
|
||||
// Destroy destroy connection writer and close tcp listener.
|
||||
func (c *connWriter) Destroy() {
|
||||
if c.innerWriter != nil {
|
||||
c.innerWriter.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ConnWriter) connect() error {
|
||||
func (c *connWriter) connect() error {
|
||||
if c.innerWriter != nil {
|
||||
c.innerWriter.Close()
|
||||
c.innerWriter = nil
|
||||
@@ -94,11 +95,11 @@ func (c *ConnWriter) connect() error {
|
||||
}
|
||||
|
||||
c.innerWriter = conn
|
||||
c.lg = log.New(conn, "", log.Ldate|log.Ltime)
|
||||
c.lg = newLogWriter(conn)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ConnWriter) neddedConnectOnMsg() bool {
|
||||
func (c *connWriter) needToConnectOnMsg() bool {
|
||||
if c.Reconnect {
|
||||
c.Reconnect = false
|
||||
return true
|
||||
101
vendor/github.com/astaxie/beego/logs/console.go
generated
vendored
Normal file
101
vendor/github.com/astaxie/beego/logs/console.go
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// 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 logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// brush is a color join function
|
||||
type brush func(string) string
|
||||
|
||||
// newBrush return a fix color Brush
|
||||
func newBrush(color string) brush {
|
||||
pre := "\033["
|
||||
reset := "\033[0m"
|
||||
return func(text string) string {
|
||||
return pre + color + "m" + text + reset
|
||||
}
|
||||
}
|
||||
|
||||
var colors = []brush{
|
||||
newBrush("1;37"), // Emergency white
|
||||
newBrush("1;36"), // Alert cyan
|
||||
newBrush("1;35"), // Critical magenta
|
||||
newBrush("1;31"), // Error red
|
||||
newBrush("1;33"), // Warning yellow
|
||||
newBrush("1;32"), // Notice green
|
||||
newBrush("1;34"), // Informational blue
|
||||
newBrush("1;34"), // Debug blue
|
||||
}
|
||||
|
||||
// consoleWriter implements LoggerInterface and writes messages to terminal.
|
||||
type consoleWriter struct {
|
||||
lg *logWriter
|
||||
Level int `json:"level"`
|
||||
Colorful bool `json:"color"` //this filed is useful only when system's terminal supports color
|
||||
}
|
||||
|
||||
// NewConsole create ConsoleWriter returning as LoggerInterface.
|
||||
func NewConsole() Logger {
|
||||
cw := &consoleWriter{
|
||||
lg: newLogWriter(os.Stdout),
|
||||
Level: LevelDebug,
|
||||
Colorful: runtime.GOOS != "windows",
|
||||
}
|
||||
return cw
|
||||
}
|
||||
|
||||
// Init init console logger.
|
||||
// jsonConfig like '{"level":LevelTrace}'.
|
||||
func (c *consoleWriter) Init(jsonConfig string) error {
|
||||
if len(jsonConfig) == 0 {
|
||||
return nil
|
||||
}
|
||||
err := json.Unmarshal([]byte(jsonConfig), c)
|
||||
if runtime.GOOS == "windows" {
|
||||
c.Colorful = false
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteMsg write message in console.
|
||||
func (c *consoleWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||
if level > c.Level {
|
||||
return nil
|
||||
}
|
||||
if c.Colorful {
|
||||
msg = colors[level](msg)
|
||||
}
|
||||
c.lg.println(when, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy implementing method. empty.
|
||||
func (c *consoleWriter) Destroy() {
|
||||
|
||||
}
|
||||
|
||||
// Flush implementing method. empty.
|
||||
func (c *consoleWriter) Flush() {
|
||||
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("console", NewConsole)
|
||||
}
|
||||
282
vendor/github.com/astaxie/beego/logs/file.go
generated
vendored
Normal file
282
vendor/github.com/astaxie/beego/logs/file.go
generated
vendored
Normal file
@@ -0,0 +1,282 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// 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 logs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// fileLogWriter implements LoggerInterface.
|
||||
// It writes messages by lines limit, file size limit, or time frequency.
|
||||
type fileLogWriter struct {
|
||||
sync.Mutex // write log order by order and atomic incr maxLinesCurLines and maxSizeCurSize
|
||||
// The opened file
|
||||
Filename string `json:"filename"`
|
||||
fileWriter *os.File
|
||||
|
||||
// Rotate at line
|
||||
MaxLines int `json:"maxlines"`
|
||||
maxLinesCurLines int
|
||||
|
||||
// Rotate at size
|
||||
MaxSize int `json:"maxsize"`
|
||||
maxSizeCurSize int
|
||||
|
||||
// Rotate daily
|
||||
Daily bool `json:"daily"`
|
||||
MaxDays int64 `json:"maxdays"`
|
||||
dailyOpenDate int
|
||||
|
||||
Rotate bool `json:"rotate"`
|
||||
|
||||
Level int `json:"level"`
|
||||
|
||||
Perm os.FileMode `json:"perm"`
|
||||
|
||||
fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
|
||||
}
|
||||
|
||||
// newFileWriter create a FileLogWriter returning as LoggerInterface.
|
||||
func newFileWriter() Logger {
|
||||
w := &fileLogWriter{
|
||||
Filename: "",
|
||||
MaxLines: 1000000,
|
||||
MaxSize: 1 << 28, //256 MB
|
||||
Daily: true,
|
||||
MaxDays: 7,
|
||||
Rotate: true,
|
||||
Level: LevelTrace,
|
||||
Perm: 0660,
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
// Init file logger with json config.
|
||||
// jsonConfig like:
|
||||
// {
|
||||
// "filename":"logs/beego.log",
|
||||
// "maxLines":10000,
|
||||
// "maxsize":1<<30,
|
||||
// "daily":true,
|
||||
// "maxDays":15,
|
||||
// "rotate":true,
|
||||
// "perm":0600
|
||||
// }
|
||||
func (w *fileLogWriter) Init(jsonConfig string) error {
|
||||
err := json.Unmarshal([]byte(jsonConfig), w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(w.Filename) == 0 {
|
||||
return errors.New("jsonconfig must have filename")
|
||||
}
|
||||
w.suffix = filepath.Ext(w.Filename)
|
||||
w.fileNameOnly = strings.TrimSuffix(w.Filename, w.suffix)
|
||||
if w.suffix == "" {
|
||||
w.suffix = ".log"
|
||||
}
|
||||
err = w.startLogger()
|
||||
return err
|
||||
}
|
||||
|
||||
// start file logger. create log file and set to locker-inside file writer.
|
||||
func (w *fileLogWriter) startLogger() error {
|
||||
file, err := w.createLogFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if w.fileWriter != nil {
|
||||
w.fileWriter.Close()
|
||||
}
|
||||
w.fileWriter = file
|
||||
return w.initFd()
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) needRotate(size int, day int) bool {
|
||||
return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
|
||||
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
|
||||
(w.Daily && day != w.dailyOpenDate)
|
||||
|
||||
}
|
||||
|
||||
// WriteMsg write logger message into file.
|
||||
func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||
if level > w.Level {
|
||||
return nil
|
||||
}
|
||||
h, d := formatTimeHeader(when)
|
||||
msg = string(h) + msg + "\n"
|
||||
if w.Rotate {
|
||||
if w.needRotate(len(msg), d) {
|
||||
w.Lock()
|
||||
if w.needRotate(len(msg), d) {
|
||||
if err := w.doRotate(when); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
}
|
||||
}
|
||||
w.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
w.Lock()
|
||||
_, err := w.fileWriter.Write([]byte(msg))
|
||||
if err == nil {
|
||||
w.maxLinesCurLines++
|
||||
w.maxSizeCurSize += len(msg)
|
||||
}
|
||||
w.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) createLogFile() (*os.File, error) {
|
||||
// Open the log file
|
||||
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, w.Perm)
|
||||
return fd, err
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) initFd() error {
|
||||
fd := w.fileWriter
|
||||
fInfo, err := fd.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get stat err: %s\n", err)
|
||||
}
|
||||
w.maxSizeCurSize = int(fInfo.Size())
|
||||
w.dailyOpenDate = time.Now().Day()
|
||||
w.maxLinesCurLines = 0
|
||||
if fInfo.Size() > 0 {
|
||||
count, err := w.lines()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.maxLinesCurLines = count
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) lines() (int, error) {
|
||||
fd, err := os.Open(w.Filename)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
buf := make([]byte, 32768) // 32k
|
||||
count := 0
|
||||
lineSep := []byte{'\n'}
|
||||
|
||||
for {
|
||||
c, err := fd.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
return count, err
|
||||
}
|
||||
|
||||
count += bytes.Count(buf[:c], lineSep)
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// DoRotate means it need to write file in new file.
|
||||
// new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size)
|
||||
func (w *fileLogWriter) doRotate(logTime time.Time) error {
|
||||
_, err := os.Lstat(w.Filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// file exists
|
||||
// Find the next available number
|
||||
num := 1
|
||||
fName := ""
|
||||
if w.MaxLines > 0 || w.MaxSize > 0 {
|
||||
for ; err == nil && num <= 999; num++ {
|
||||
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, w.suffix)
|
||||
_, err = os.Lstat(fName)
|
||||
}
|
||||
} else {
|
||||
fName = fmt.Sprintf("%s.%s%s", w.fileNameOnly, logTime.Format("2006-01-02"), w.suffix)
|
||||
_, err = os.Lstat(fName)
|
||||
}
|
||||
// return error if the last file checked still existed
|
||||
if err == nil {
|
||||
return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.Filename)
|
||||
}
|
||||
|
||||
// close fileWriter before rename
|
||||
w.fileWriter.Close()
|
||||
|
||||
// Rename the file to its new found name
|
||||
// even if occurs error,we MUST guarantee to restart new logger
|
||||
renameErr := os.Rename(w.Filename, fName)
|
||||
// re-start logger
|
||||
startLoggerErr := w.startLogger()
|
||||
go w.deleteOldLog()
|
||||
|
||||
if startLoggerErr != nil {
|
||||
return fmt.Errorf("Rotate StartLogger: %s\n", startLoggerErr)
|
||||
}
|
||||
if renameErr != nil {
|
||||
return fmt.Errorf("Rotate: %s\n", renameErr)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) deleteOldLog() {
|
||||
dir := filepath.Dir(w.Filename)
|
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to delete old log '%s', error: %v\n", path, r)
|
||||
}
|
||||
}()
|
||||
|
||||
if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.MaxDays) {
|
||||
if strings.HasPrefix(filepath.Base(path), w.fileNameOnly) &&
|
||||
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
||||
os.Remove(path)
|
||||
}
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// Destroy close the file description, close file writer.
|
||||
func (w *fileLogWriter) Destroy() {
|
||||
w.fileWriter.Close()
|
||||
}
|
||||
|
||||
// Flush flush file logger.
|
||||
// there are no buffering messages in file logger in memory.
|
||||
// flush file means sync file from disk.
|
||||
func (w *fileLogWriter) Flush() {
|
||||
w.fileWriter.Sync()
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("file", newFileWriter)
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package logs provide a general log interface
|
||||
// Usage:
|
||||
//
|
||||
// import "github.com/astaxie/beego/logs"
|
||||
@@ -34,9 +35,12 @@ package logs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RFC5424 log message levels.
|
||||
@@ -60,12 +64,12 @@ const (
|
||||
LevelWarn = LevelWarning
|
||||
)
|
||||
|
||||
type loggerType func() LoggerInterface
|
||||
type loggerType func() Logger
|
||||
|
||||
// LoggerInterface defines the behavior of a log provider.
|
||||
type LoggerInterface interface {
|
||||
// Logger defines the behavior of a log provider.
|
||||
type Logger interface {
|
||||
Init(config string) error
|
||||
WriteMsg(msg string, level int) error
|
||||
WriteMsg(when time.Time, msg string, level int) error
|
||||
Destroy()
|
||||
Flush()
|
||||
}
|
||||
@@ -93,68 +97,107 @@ type BeeLogger struct {
|
||||
enableFuncCallDepth bool
|
||||
loggerFuncCallDepth int
|
||||
asynchronous bool
|
||||
msg chan *logMsg
|
||||
outputs map[string]LoggerInterface
|
||||
msgChan chan *logMsg
|
||||
signalChan chan string
|
||||
wg sync.WaitGroup
|
||||
outputs []*nameLogger
|
||||
}
|
||||
|
||||
type nameLogger struct {
|
||||
Logger
|
||||
name string
|
||||
}
|
||||
|
||||
type logMsg struct {
|
||||
level int
|
||||
msg string
|
||||
when time.Time
|
||||
}
|
||||
|
||||
var logMsgPool *sync.Pool
|
||||
|
||||
// NewLogger returns a new BeeLogger.
|
||||
// channellen means the number of messages in chan.
|
||||
// channelLen means the number of messages in chan(used where asynchronous is true).
|
||||
// if the buffering chan is full, logger adapters write to file or other way.
|
||||
func NewLogger(channellen int64) *BeeLogger {
|
||||
func NewLogger(channelLen int64) *BeeLogger {
|
||||
bl := new(BeeLogger)
|
||||
bl.level = LevelDebug
|
||||
bl.loggerFuncCallDepth = 2
|
||||
bl.msg = make(chan *logMsg, channellen)
|
||||
bl.outputs = make(map[string]LoggerInterface)
|
||||
bl.msgChan = make(chan *logMsg, channelLen)
|
||||
bl.signalChan = make(chan string, 1)
|
||||
return bl
|
||||
}
|
||||
|
||||
// Async set the log to asynchronous and start the goroutine
|
||||
func (bl *BeeLogger) Async() *BeeLogger {
|
||||
bl.asynchronous = true
|
||||
logMsgPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &logMsg{}
|
||||
},
|
||||
}
|
||||
bl.wg.Add(1)
|
||||
go bl.startLogger()
|
||||
return bl
|
||||
}
|
||||
|
||||
// SetLogger provides a given logger adapter into BeeLogger with config string.
|
||||
// config need to be correct JSON as string: {"interval":360}.
|
||||
func (bl *BeeLogger) SetLogger(adaptername string, config string) error {
|
||||
func (bl *BeeLogger) SetLogger(adapterName string, config string) error {
|
||||
bl.lock.Lock()
|
||||
defer bl.lock.Unlock()
|
||||
if log, ok := adapters[adaptername]; ok {
|
||||
lg := log()
|
||||
err := lg.Init(config)
|
||||
bl.outputs[adaptername] = lg
|
||||
if err != nil {
|
||||
fmt.Println("logs.BeeLogger.SetLogger: " + err.Error())
|
||||
return err
|
||||
|
||||
for _, l := range bl.outputs {
|
||||
if l.name == adapterName {
|
||||
return fmt.Errorf("logs: duplicate adaptername %q (you have set this logger before)", adapterName)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adaptername)
|
||||
}
|
||||
|
||||
log, ok := adapters[adapterName]
|
||||
if !ok {
|
||||
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
|
||||
}
|
||||
|
||||
lg := log()
|
||||
err := lg.Init(config)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error())
|
||||
return err
|
||||
}
|
||||
bl.outputs = append(bl.outputs, &nameLogger{name: adapterName, Logger: lg})
|
||||
return nil
|
||||
}
|
||||
|
||||
// remove a logger adapter in BeeLogger.
|
||||
func (bl *BeeLogger) DelLogger(adaptername string) error {
|
||||
// DelLogger remove a logger adapter in BeeLogger.
|
||||
func (bl *BeeLogger) DelLogger(adapterName string) error {
|
||||
bl.lock.Lock()
|
||||
defer bl.lock.Unlock()
|
||||
if lg, ok := bl.outputs[adaptername]; ok {
|
||||
lg.Destroy()
|
||||
delete(bl.outputs, adaptername)
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adaptername)
|
||||
outputs := []*nameLogger{}
|
||||
for _, lg := range bl.outputs {
|
||||
if lg.name == adapterName {
|
||||
lg.Destroy()
|
||||
} else {
|
||||
outputs = append(outputs, lg)
|
||||
}
|
||||
}
|
||||
if len(outputs) == len(bl.outputs) {
|
||||
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
|
||||
}
|
||||
bl.outputs = outputs
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) writeToLoggers(when time.Time, msg string, level int) {
|
||||
for _, l := range bl.outputs {
|
||||
err := l.WriteMsg(when, msg, level)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) writerMsg(loglevel int, msg string) error {
|
||||
lm := new(logMsg)
|
||||
lm.level = loglevel
|
||||
func (bl *BeeLogger) writeMsg(logLevel int, msg string) error {
|
||||
when := time.Now()
|
||||
if bl.enableFuncCallDepth {
|
||||
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
|
||||
if !ok {
|
||||
@@ -162,43 +205,38 @@ func (bl *BeeLogger) writerMsg(loglevel int, msg string) error {
|
||||
line = 0
|
||||
}
|
||||
_, filename := path.Split(file)
|
||||
lm.msg = fmt.Sprintf("[%s:%d] %s", filename, line, msg)
|
||||
} else {
|
||||
lm.msg = msg
|
||||
msg = "[" + filename + ":" + strconv.FormatInt(int64(line), 10) + "]" + msg
|
||||
}
|
||||
if bl.asynchronous {
|
||||
bl.msg <- lm
|
||||
lm := logMsgPool.Get().(*logMsg)
|
||||
lm.level = logLevel
|
||||
lm.msg = msg
|
||||
lm.when = when
|
||||
bl.msgChan <- lm
|
||||
} else {
|
||||
for name, l := range bl.outputs {
|
||||
err := l.WriteMsg(lm.msg, lm.level)
|
||||
if err != nil {
|
||||
fmt.Println("unable to WriteMsg to adapter:", name, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
bl.writeToLoggers(when, msg, logLevel)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set log message level.
|
||||
//
|
||||
// SetLevel Set log message level.
|
||||
// If message level (such as LevelDebug) is higher than logger level (such as LevelWarning),
|
||||
// log providers will not even be sent the message.
|
||||
func (bl *BeeLogger) SetLevel(l int) {
|
||||
bl.level = l
|
||||
}
|
||||
|
||||
// set log funcCallDepth
|
||||
// SetLogFuncCallDepth set log funcCallDepth
|
||||
func (bl *BeeLogger) SetLogFuncCallDepth(d int) {
|
||||
bl.loggerFuncCallDepth = d
|
||||
}
|
||||
|
||||
// get log funcCallDepth for wrapper
|
||||
// GetLogFuncCallDepth return log funcCallDepth for wrapper
|
||||
func (bl *BeeLogger) GetLogFuncCallDepth() int {
|
||||
return bl.loggerFuncCallDepth
|
||||
}
|
||||
|
||||
// enable log funcCallDepth
|
||||
// EnableFuncCallDepth enable log funcCallDepth
|
||||
func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
|
||||
bl.enableFuncCallDepth = b
|
||||
}
|
||||
@@ -206,145 +244,179 @@ func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
|
||||
// start logger chan reading.
|
||||
// when chan is not empty, write logs.
|
||||
func (bl *BeeLogger) startLogger() {
|
||||
gameOver := false
|
||||
for {
|
||||
select {
|
||||
case bm := <-bl.msg:
|
||||
for _, l := range bl.outputs {
|
||||
err := l.WriteMsg(bm.msg, bm.level)
|
||||
if err != nil {
|
||||
fmt.Println("ERROR, unable to WriteMsg:", err)
|
||||
case bm := <-bl.msgChan:
|
||||
bl.writeToLoggers(bm.when, bm.msg, bm.level)
|
||||
logMsgPool.Put(bm)
|
||||
case sg := <-bl.signalChan:
|
||||
// Now should only send "flush" or "close" to bl.signalChan
|
||||
bl.flush()
|
||||
if sg == "close" {
|
||||
for _, l := range bl.outputs {
|
||||
l.Destroy()
|
||||
}
|
||||
bl.outputs = nil
|
||||
gameOver = true
|
||||
}
|
||||
bl.wg.Done()
|
||||
}
|
||||
if gameOver {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Log EMERGENCY level message.
|
||||
// Emergency Log EMERGENCY level message.
|
||||
func (bl *BeeLogger) Emergency(format string, v ...interface{}) {
|
||||
if LevelEmergency > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[M] "+format, v...)
|
||||
bl.writerMsg(LevelEmergency, msg)
|
||||
bl.writeMsg(LevelEmergency, msg)
|
||||
}
|
||||
|
||||
// Log ALERT level message.
|
||||
// Alert Log ALERT level message.
|
||||
func (bl *BeeLogger) Alert(format string, v ...interface{}) {
|
||||
if LevelAlert > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[A] "+format, v...)
|
||||
bl.writerMsg(LevelAlert, msg)
|
||||
bl.writeMsg(LevelAlert, msg)
|
||||
}
|
||||
|
||||
// Log CRITICAL level message.
|
||||
// Critical Log CRITICAL level message.
|
||||
func (bl *BeeLogger) Critical(format string, v ...interface{}) {
|
||||
if LevelCritical > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[C] "+format, v...)
|
||||
bl.writerMsg(LevelCritical, msg)
|
||||
bl.writeMsg(LevelCritical, msg)
|
||||
}
|
||||
|
||||
// Log ERROR level message.
|
||||
// Error Log ERROR level message.
|
||||
func (bl *BeeLogger) Error(format string, v ...interface{}) {
|
||||
if LevelError > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[E] "+format, v...)
|
||||
bl.writerMsg(LevelError, msg)
|
||||
bl.writeMsg(LevelError, msg)
|
||||
}
|
||||
|
||||
// Log WARNING level message.
|
||||
// Warning Log WARNING level message.
|
||||
func (bl *BeeLogger) Warning(format string, v ...interface{}) {
|
||||
if LevelWarning > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[W] "+format, v...)
|
||||
bl.writerMsg(LevelWarning, msg)
|
||||
bl.writeMsg(LevelWarning, msg)
|
||||
}
|
||||
|
||||
// Log NOTICE level message.
|
||||
// Notice Log NOTICE level message.
|
||||
func (bl *BeeLogger) Notice(format string, v ...interface{}) {
|
||||
if LevelNotice > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[N] "+format, v...)
|
||||
bl.writerMsg(LevelNotice, msg)
|
||||
bl.writeMsg(LevelNotice, msg)
|
||||
}
|
||||
|
||||
// Log INFORMATIONAL level message.
|
||||
// Informational Log INFORMATIONAL level message.
|
||||
func (bl *BeeLogger) Informational(format string, v ...interface{}) {
|
||||
if LevelInformational > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[I] "+format, v...)
|
||||
bl.writerMsg(LevelInformational, msg)
|
||||
bl.writeMsg(LevelInformational, msg)
|
||||
}
|
||||
|
||||
// Log DEBUG level message.
|
||||
// Debug Log DEBUG level message.
|
||||
func (bl *BeeLogger) Debug(format string, v ...interface{}) {
|
||||
if LevelDebug > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[D] "+format, v...)
|
||||
bl.writerMsg(LevelDebug, msg)
|
||||
bl.writeMsg(LevelDebug, msg)
|
||||
}
|
||||
|
||||
// Log WARN level message.
|
||||
// Warn Log WARN level message.
|
||||
// compatibility alias for Warning()
|
||||
func (bl *BeeLogger) Warn(format string, v ...interface{}) {
|
||||
if LevelWarning > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[W] "+format, v...)
|
||||
bl.writerMsg(LevelWarning, msg)
|
||||
bl.writeMsg(LevelWarning, msg)
|
||||
}
|
||||
|
||||
// Log INFO level message.
|
||||
// Info Log INFO level message.
|
||||
// compatibility alias for Informational()
|
||||
func (bl *BeeLogger) Info(format string, v ...interface{}) {
|
||||
if LevelInformational > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[I] "+format, v...)
|
||||
bl.writerMsg(LevelInformational, msg)
|
||||
bl.writeMsg(LevelInformational, msg)
|
||||
}
|
||||
|
||||
// Log TRACE level message.
|
||||
// Trace Log TRACE level message.
|
||||
// compatibility alias for Debug()
|
||||
func (bl *BeeLogger) Trace(format string, v ...interface{}) {
|
||||
if LevelDebug > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[D] "+format, v...)
|
||||
bl.writerMsg(LevelDebug, msg)
|
||||
bl.writeMsg(LevelDebug, msg)
|
||||
}
|
||||
|
||||
// flush all chan data.
|
||||
// Flush flush all chan data.
|
||||
func (bl *BeeLogger) Flush() {
|
||||
for _, l := range bl.outputs {
|
||||
l.Flush()
|
||||
if bl.asynchronous {
|
||||
bl.signalChan <- "flush"
|
||||
bl.wg.Wait()
|
||||
bl.wg.Add(1)
|
||||
return
|
||||
}
|
||||
bl.flush()
|
||||
}
|
||||
|
||||
// close logger, flush all chan data and destroy all adapters in BeeLogger.
|
||||
// Close close logger, flush all chan data and destroy all adapters in BeeLogger.
|
||||
func (bl *BeeLogger) Close() {
|
||||
if bl.asynchronous {
|
||||
bl.signalChan <- "close"
|
||||
bl.wg.Wait()
|
||||
} else {
|
||||
bl.flush()
|
||||
for _, l := range bl.outputs {
|
||||
l.Destroy()
|
||||
}
|
||||
bl.outputs = nil
|
||||
}
|
||||
close(bl.msgChan)
|
||||
close(bl.signalChan)
|
||||
}
|
||||
|
||||
// Reset close all outputs, and set bl.outputs to nil
|
||||
func (bl *BeeLogger) Reset() {
|
||||
bl.Flush()
|
||||
for _, l := range bl.outputs {
|
||||
l.Destroy()
|
||||
}
|
||||
bl.outputs = nil
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) flush() {
|
||||
for {
|
||||
if len(bl.msg) > 0 {
|
||||
bm := <-bl.msg
|
||||
for _, l := range bl.outputs {
|
||||
err := l.WriteMsg(bm.msg, bm.level)
|
||||
if err != nil {
|
||||
fmt.Println("ERROR, unable to WriteMsg (while closing logger):", err)
|
||||
}
|
||||
}
|
||||
if len(bl.msgChan) > 0 {
|
||||
bm := <-bl.msgChan
|
||||
bl.writeToLoggers(bm.when, bm.msg, bm.level)
|
||||
logMsgPool.Put(bm)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
for _, l := range bl.outputs {
|
||||
l.Flush()
|
||||
l.Destroy()
|
||||
}
|
||||
}
|
||||
79
vendor/github.com/astaxie/beego/logs/logger.go
generated
vendored
Normal file
79
vendor/github.com/astaxie/beego/logs/logger.go
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// 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 logs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type logWriter struct {
|
||||
sync.Mutex
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
func newLogWriter(wr io.Writer) *logWriter {
|
||||
return &logWriter{writer: wr}
|
||||
}
|
||||
|
||||
func (lg *logWriter) println(when time.Time, msg string) {
|
||||
lg.Lock()
|
||||
h, _ := formatTimeHeader(when)
|
||||
lg.writer.Write(append(append(h, msg...), '\n'))
|
||||
lg.Unlock()
|
||||
}
|
||||
|
||||
func formatTimeHeader(when time.Time) ([]byte, int) {
|
||||
y, mo, d := when.Date()
|
||||
h, mi, s := when.Clock()
|
||||
//len(2006/01/02 15:03:04)==19
|
||||
var buf [20]byte
|
||||
t := 3
|
||||
for y >= 10 {
|
||||
p := y / 10
|
||||
buf[t] = byte('0' + y - p*10)
|
||||
y = p
|
||||
t--
|
||||
}
|
||||
buf[0] = byte('0' + y)
|
||||
buf[4] = '/'
|
||||
if mo > 9 {
|
||||
buf[5] = '1'
|
||||
buf[6] = byte('0' + mo - 9)
|
||||
} else {
|
||||
buf[5] = '0'
|
||||
buf[6] = byte('0' + mo)
|
||||
}
|
||||
buf[7] = '/'
|
||||
t = d / 10
|
||||
buf[8] = byte('0' + t)
|
||||
buf[9] = byte('0' + d - t*10)
|
||||
buf[10] = ' '
|
||||
t = h / 10
|
||||
buf[11] = byte('0' + t)
|
||||
buf[12] = byte('0' + h - t*10)
|
||||
buf[13] = ':'
|
||||
t = mi / 10
|
||||
buf[14] = byte('0' + t)
|
||||
buf[15] = byte('0' + mi - t*10)
|
||||
buf[16] = ':'
|
||||
t = s / 10
|
||||
buf[17] = byte('0' + t)
|
||||
buf[18] = byte('0' + s - t*10)
|
||||
buf[19] = ' '
|
||||
|
||||
return buf[0:], d
|
||||
}
|
||||
116
vendor/github.com/astaxie/beego/logs/multifile.go
generated
vendored
Normal file
116
vendor/github.com/astaxie/beego/logs/multifile.go
generated
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// 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 logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A filesLogWriter manages several fileLogWriter
|
||||
// filesLogWriter will write logs to the file in json configuration and write the same level log to correspond file
|
||||
// means if the file name in configuration is project.log filesLogWriter will create project.error.log/project.debug.log
|
||||
// and write the error-level logs to project.error.log and write the debug-level logs to project.debug.log
|
||||
// the rotate attribute also acts like fileLogWriter
|
||||
type multiFileLogWriter struct {
|
||||
writers [LevelDebug + 1 + 1]*fileLogWriter // the last one for fullLogWriter
|
||||
fullLogWriter *fileLogWriter
|
||||
Separate []string `json:"separate"`
|
||||
}
|
||||
|
||||
var levelNames = [...]string{"emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"}
|
||||
|
||||
// Init file logger with json config.
|
||||
// jsonConfig like:
|
||||
// {
|
||||
// "filename":"logs/beego.log",
|
||||
// "maxLines":0,
|
||||
// "maxsize":0,
|
||||
// "daily":true,
|
||||
// "maxDays":15,
|
||||
// "rotate":true,
|
||||
// "perm":0600,
|
||||
// "separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"],
|
||||
// }
|
||||
|
||||
func (f *multiFileLogWriter) Init(config string) error {
|
||||
writer := newFileWriter().(*fileLogWriter)
|
||||
err := writer.Init(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.fullLogWriter = writer
|
||||
f.writers[LevelDebug+1] = writer
|
||||
|
||||
//unmarshal "separate" field to f.Separate
|
||||
json.Unmarshal([]byte(config), f)
|
||||
|
||||
jsonMap := map[string]interface{}{}
|
||||
json.Unmarshal([]byte(config), &jsonMap)
|
||||
|
||||
for i := LevelEmergency; i < LevelDebug+1; i++ {
|
||||
for _, v := range f.Separate {
|
||||
if v == levelNames[i] {
|
||||
jsonMap["filename"] = f.fullLogWriter.fileNameOnly + "." + levelNames[i] + f.fullLogWriter.suffix
|
||||
jsonMap["level"] = i
|
||||
bs, _ := json.Marshal(jsonMap)
|
||||
writer = newFileWriter().(*fileLogWriter)
|
||||
writer.Init(string(bs))
|
||||
f.writers[i] = writer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *multiFileLogWriter) Destroy() {
|
||||
for i := 0; i < len(f.writers); i++ {
|
||||
if f.writers[i] != nil {
|
||||
f.writers[i].Destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *multiFileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||
if f.fullLogWriter != nil {
|
||||
f.fullLogWriter.WriteMsg(when, msg, level)
|
||||
}
|
||||
for i := 0; i < len(f.writers)-1; i++ {
|
||||
if f.writers[i] != nil {
|
||||
if level == f.writers[i].Level {
|
||||
f.writers[i].WriteMsg(when, msg, level)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *multiFileLogWriter) Flush() {
|
||||
for i := 0; i < len(f.writers); i++ {
|
||||
if f.writers[i] != nil {
|
||||
f.writers[i].Flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// newFilesWriter create a FileLogWriter returning as LoggerInterface.
|
||||
func newFilesWriter() Logger {
|
||||
return &multiFileLogWriter{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("multifile", newFilesWriter)
|
||||
}
|
||||
@@ -24,31 +24,26 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// no usage
|
||||
// subjectPhrase = "Diagnostic message from server"
|
||||
)
|
||||
|
||||
// smtpWriter implements LoggerInterface and is used to send emails via given SMTP-server.
|
||||
type SmtpWriter struct {
|
||||
Username string `json:"Username"`
|
||||
// SMTPWriter implements LoggerInterface and is used to send emails via given SMTP-server.
|
||||
type SMTPWriter struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Host string `json:"Host"`
|
||||
Host string `json:"host"`
|
||||
Subject string `json:"subject"`
|
||||
FromAddress string `json:"fromAddress"`
|
||||
RecipientAddresses []string `json:"sendTos"`
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
// create smtp writer.
|
||||
func NewSmtpWriter() LoggerInterface {
|
||||
return &SmtpWriter{Level: LevelTrace}
|
||||
// NewSMTPWriter create smtp writer.
|
||||
func newSMTPWriter() Logger {
|
||||
return &SMTPWriter{Level: LevelTrace}
|
||||
}
|
||||
|
||||
// init smtp writer with json config.
|
||||
// Init smtp writer with json config.
|
||||
// config like:
|
||||
// {
|
||||
// "Username":"example@gmail.com",
|
||||
// "username":"example@gmail.com",
|
||||
// "password:"password",
|
||||
// "host":"smtp.gmail.com:465",
|
||||
// "subject":"email title",
|
||||
@@ -56,7 +51,7 @@ func NewSmtpWriter() LoggerInterface {
|
||||
// "sendTos":["email1","email2"],
|
||||
// "level":LevelError
|
||||
// }
|
||||
func (s *SmtpWriter) Init(jsonconfig string) error {
|
||||
func (s *SMTPWriter) Init(jsonconfig string) error {
|
||||
err := json.Unmarshal([]byte(jsonconfig), s)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -64,7 +59,7 @@ func (s *SmtpWriter) Init(jsonconfig string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SmtpWriter) GetSmtpAuth(host string) smtp.Auth {
|
||||
func (s *SMTPWriter) getSMTPAuth(host string) smtp.Auth {
|
||||
if len(strings.Trim(s.Username, " ")) == 0 && len(strings.Trim(s.Password, " ")) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -76,7 +71,7 @@ func (s *SmtpWriter) GetSmtpAuth(host string) smtp.Auth {
|
||||
)
|
||||
}
|
||||
|
||||
func (s *SmtpWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAddress string, recipients []string, msgContent []byte) error {
|
||||
func (s *SMTPWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAddress string, recipients []string, msgContent []byte) error {
|
||||
client, err := smtp.Dial(hostAddressWithPort)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -129,9 +124,9 @@ func (s *SmtpWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAd
|
||||
return nil
|
||||
}
|
||||
|
||||
// write message in smtp writer.
|
||||
// WriteMsg write message in smtp writer.
|
||||
// it will send an email with subject and only this message.
|
||||
func (s *SmtpWriter) WriteMsg(msg string, level int) error {
|
||||
func (s *SMTPWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||
if level > s.Level {
|
||||
return nil
|
||||
}
|
||||
@@ -139,27 +134,27 @@ func (s *SmtpWriter) WriteMsg(msg string, level int) error {
|
||||
hp := strings.Split(s.Host, ":")
|
||||
|
||||
// Set up authentication information.
|
||||
auth := s.GetSmtpAuth(hp[0])
|
||||
auth := s.getSMTPAuth(hp[0])
|
||||
|
||||
// Connect to the server, authenticate, set the sender and recipient,
|
||||
// and send the email all in one step.
|
||||
content_type := "Content-Type: text/plain" + "; charset=UTF-8"
|
||||
contentType := "Content-Type: text/plain" + "; charset=UTF-8"
|
||||
mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.FromAddress + "<" + s.FromAddress +
|
||||
">\r\nSubject: " + s.Subject + "\r\n" + content_type + "\r\n\r\n" + fmt.Sprintf(".%s", time.Now().Format("2006-01-02 15:04:05")) + msg)
|
||||
">\r\nSubject: " + s.Subject + "\r\n" + contentType + "\r\n\r\n" + fmt.Sprintf(".%s", when.Format("2006-01-02 15:04:05")) + msg)
|
||||
|
||||
return s.sendMail(s.Host, auth, s.FromAddress, s.RecipientAddresses, mailmsg)
|
||||
}
|
||||
|
||||
// implementing method. empty.
|
||||
func (s *SmtpWriter) Flush() {
|
||||
// Flush implementing method. empty.
|
||||
func (s *SMTPWriter) Flush() {
|
||||
return
|
||||
}
|
||||
|
||||
// implementing method. empty.
|
||||
func (s *SmtpWriter) Destroy() {
|
||||
// Destroy implementing method. empty.
|
||||
func (s *SMTPWriter) Destroy() {
|
||||
return
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("smtp", NewSmtpWriter)
|
||||
Register("smtp", newSMTPWriter)
|
||||
}
|
||||
202
vendor/github.com/rakyll/statik/LICENSE
generated
vendored
Normal file
202
vendor/github.com/rakyll/statik/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2014 Google Inc.
|
||||
|
||||
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.
|
||||
146
vendor/github.com/rakyll/statik/fs/fs.go
generated
vendored
Normal file
146
vendor/github.com/rakyll/statik/fs/fs.go
generated
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 contains an HTTP file system that works with zip contents.
|
||||
package fs
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var zipData string
|
||||
|
||||
type statikFS struct {
|
||||
files map[string]*zip.File
|
||||
}
|
||||
|
||||
// Registers zip contents data, later used to initialize
|
||||
// the statik file system.
|
||||
func Register(data string) {
|
||||
zipData = data
|
||||
}
|
||||
|
||||
// Creates a new file system with the registered zip contents data.
|
||||
func New() (http.FileSystem, error) {
|
||||
if zipData == "" {
|
||||
return nil, errors.New("statik/fs: No zip data registered.")
|
||||
}
|
||||
zipReader, err := zip.NewReader(strings.NewReader(zipData), int64(len(zipData)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files := make(map[string]*zip.File)
|
||||
for _, file := range zipReader.File {
|
||||
files["/"+file.Name] = file
|
||||
}
|
||||
return &statikFS{files: files}, nil
|
||||
}
|
||||
|
||||
// Opens a file, unzip the contents and initializes
|
||||
// readers. Returns os.ErrNotExists if file is not
|
||||
// found in the archive.
|
||||
func (fs *statikFS) Open(name string) (http.File, error) {
|
||||
name = strings.Replace(name, "//", "/", -1)
|
||||
f, ok := fs.files[name]
|
||||
|
||||
// The file doesn't match, but maybe it's a directory,
|
||||
// thus we should look for index.html
|
||||
if !ok {
|
||||
indexName := strings.Replace(name+"/index.html", "//", "/", -1)
|
||||
f, ok = fs.files[indexName]
|
||||
|
||||
if !ok {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
return newFile(f, true)
|
||||
}
|
||||
return newFile(f, false)
|
||||
}
|
||||
|
||||
var nopCloser = ioutil.NopCloser(nil)
|
||||
|
||||
func newFile(zf *zip.File, isDir bool) (*file, error) {
|
||||
rc, err := zf.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rc.Close()
|
||||
all, err := ioutil.ReadAll(rc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &file{
|
||||
FileInfo: zf.FileInfo(),
|
||||
data: all,
|
||||
readerAt: bytes.NewReader(all),
|
||||
Closer: nopCloser,
|
||||
isDir: isDir,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Represents an HTTP file, acts as a bridge between
|
||||
// zip.File and http.File.
|
||||
type file struct {
|
||||
os.FileInfo
|
||||
io.Closer
|
||||
|
||||
data []byte // non-nil if regular file
|
||||
reader *io.SectionReader
|
||||
readerAt io.ReaderAt // over data
|
||||
isDir bool
|
||||
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (f *file) newReader() {
|
||||
f.reader = io.NewSectionReader(f.readerAt, 0, f.FileInfo.Size())
|
||||
}
|
||||
|
||||
// Reads bytes into p, returns the number of read bytes.
|
||||
func (f *file) Read(p []byte) (n int, err error) {
|
||||
f.once.Do(f.newReader)
|
||||
return f.reader.Read(p)
|
||||
}
|
||||
|
||||
// Seeks to the offset.
|
||||
func (f *file) Seek(offset int64, whence int) (ret int64, err error) {
|
||||
f.once.Do(f.newReader)
|
||||
return f.reader.Seek(offset, whence)
|
||||
}
|
||||
|
||||
// Stats the file.
|
||||
func (f *file) Stat() (os.FileInfo, error) {
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// IsDir returns true if the file location represents a directory.
|
||||
func (f *file) IsDir() bool {
|
||||
return f.isDir
|
||||
}
|
||||
|
||||
// Returns an empty slice of files, directory
|
||||
// listing is disabled.
|
||||
func (f *file) Readdir(count int) ([]os.FileInfo, error) {
|
||||
// directory listing is disabled.
|
||||
return make([]os.FileInfo, 0), nil
|
||||
}
|
||||
Reference in New Issue
Block a user