<commit_before>package main

import (
    "bytes"
    "encoding/binary"
    "errors"
    "flag"
    "fmt"
    "github.com/cyfdecyf/leakybuf"
    ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
    "io"
    "log"
    "net"
    "os"
    "os/signal"
    "runtime"
    "strconv"
    "syscall"
    "bufio"
    "net/http"
    "strings"
)

var debug ss.DebugLog

var storage *Storage

const dnsGoroutineNum = 64

func getUser(conn net.Conn) (user User, err error) {
    const (
        idVersion = 0 // \x01
        version   = 1
        idUser    = 1 // \x02
    )
    buf := make([]byte, 2)
    if _, err = io.ReadFull(conn, buf); err != nil {
        return
    }
    switch buf[idVersion] {
    case version:
        break
    default:
        err = errors.New(fmt.Sprintf("version %s not supported", buf[idVersion]))
        return
    }
    userLen := buf[idUser]
    username := make([]byte, userLen)
    if _, err = io.ReadFull(conn, username); err != nil {
        return
    }
    user, err = storage.Get("user:" + string(username))
    return
}

func getRequest(conn *ss.Conn) (host string, extra []byte, err error) {
    const (
        idType  = 0 // address type index
        idIP0   = 1 // ip addres start index
        idDmLen = 1 // domain address length index
        idDm0   = 2 // domain address start index

        typeIPv4 = 1 // type is ipv4 address
        typeDm   = 3 // type is domain address
        typeIPv6 = 4 // type is ipv6 address

        lenIPv4   = 1 + net.IPv4len + 2 // 1addrType + ipv4 + 2port
        lenIPv6   = 1 + net.IPv6len + 2 // 1addrType + ipv6 + 2port
        lenDmBase = 1 + 1 + 2           // 1addrType + 1addrLen + 2port, plus addrLen
    )

    // buf size should at least have the same size with the largest possible
    // request size (when addrType is 3, domain name has at most 256 bytes)
    // 1(addrType) + 1(lenByte) + 256(max length address) + 2(port)
    buf := make([]byte, 260)
    var n int
    // read till we get possible domain length field
    ss.SetReadTimeout(conn)
    if n, err = io.ReadAtLeast(conn, buf, idDmLen+1); err != nil {
        return
    }

    reqLen := -1
    switch buf[idType] {
    case typeIPv4:
        reqLen = lenIPv4
    case typeIPv6:
        reqLen = lenIPv6
    case typeDm:
        reqLen = int(buf[idDmLen]) + lenDmBase
    default:
        err = errors.New(fmt.Sprintf("addr type %d not supported", buf[idType]))
        return
    }

    if n < reqLen { // rare case
        ss.SetReadTimeout(conn)
        if _, err = io.ReadFull(conn, buf[n:reqLen]); err != nil {
            return
        }
    } else if n > reqLen {
        // it's possible to read more than just the request head
        extra = buf[reqLen:n]
    }

    // Return string for typeIP is not most efficient, but browsers (Chrome,
    // Safari, Firefox) all seems using typeDm exclusively. So this is not a
    // big problem.
    switch buf[idType] {
    case typeIPv4:
        host = net.IP(buf[idIP0 : idIP0+net.IPv4len]).String()
    case typeIPv6:
        host = net.IP(buf[idIP0 : idIP0+net.IPv6len]).String()
    case typeDm:
        host = string(buf[idDm0 : idDm0+buf[idDmLen]])
    }
    // parse port
    port := binary.BigEndian.Uint16(buf[reqLen-2 : reqLen])
    host = net.JoinHostPort(host, strconv.Itoa(int(port)))
    return
}

const logCntDelta = 100

var connCnt int
var nextLogConnCnt int = logCntDelta

func handleConnection(user User, conn *ss.Conn) {
    var host string
    var size = 0
    var raw_req_header, raw_res_header []byte
    var is_http = false
    var res_size = 0
    var req_chan = make(chan []byte)

    connCnt++ // this maybe not accurate, but should be enough
    if connCnt-nextLogConnCnt >= 0 {
        // XXX There's no xadd in the atomic package, so it's difficult to log
        // the message only once with low cost. Also note nextLogConnCnt maybe
        // added twice for current peak connection number level.
        log.Printf("Number of client connections reaches %d\n", nextLogConnCnt)
        nextLogConnCnt += logCntDelta
    }

    // function arguments are always evaluated, so surround debug statement
    // with if statement
    if debug {
        debug.Printf("new client %s->%s\n", conn.RemoteAddr().String(), conn.LocalAddr())
    }
    closed := false
    defer func() {
        if debug {
            debug.Printf("closed pipe %s<->%s\n", conn.RemoteAddr(), host)
        }
        connCnt--
        if !closed {
            conn.Close()
        }
    }()

    host, extra, err := getRequest(conn)
    if err != nil {
        log.Println("error getting request", conn.RemoteAddr(), conn.LocalAddr(), err)
        return
    }
    debug.Println("connecting", host)
    remote, err := net.Dial("tcp", host)
    if err != nil {
        if ne, ok := err.(*net.OpError); ok && (ne.Err == syscall.EMFILE || ne.Err == syscall.ENFILE) {
            // log too many open file error
            // EMFILE is process reaches open file limits, ENFILE is system limit
            log.Println("dial error:", err)
        } else {
            log.Println("error connecting to:", host, err)
        }
        return
    }
    defer func() {
        if is_http{
            tmp_req_header := <-req_chan
            buffer := bytes.NewBuffer(raw_req_header)
            buffer.Write(tmp_req_header)
            raw_req_header = buffer.Bytes()
        }
        showConn(raw_req_header, raw_res_header, host, user, size, is_http)
        close(req_chan)
        if !closed {
            remote.Close()
        }
    }()
    // write extra bytes read from

    is_http, extra, _ = checkHttp(extra, conn)
    if strings.HasSuffix(host, ":80") {
        is_http = true
    }
    raw_req_header = extra
    res_size, err = remote.Write(extra)
    storage.IncrSize("flow:" + user.Name, res_size)
    storage.ZincrbySize("flow:" + user.Name, host, res_size)
    size += res_size
    if err != nil {
        debug.Println("write request extra error:", err)
        return
    }

    if debug {
        debug.Printf("piping %s<->%s", conn.RemoteAddr(), host)
    }

    go func() {
        _, raw_header := PipeThenClose(conn, remote, ss.SET_TIMEOUT, is_http, false, user)
        if is_http {
            req_chan<-raw_header
        }
    }()

    res_size, raw_res_header = PipeThenClose(remote, conn, ss.NO_TIMEOUT, is_http, true, user)
    size += res_size
    closed = true
    return
}

func showConn(raw_req_header, raw_res_header []byte, host string, user User, size int, is_http bool) {
    if size == 0 {
        log.Printf("Error: user %s request %s cancel", user.Name, host)
        return
    }
    if is_http {
        req, _ := http.ReadRequest(bufio.NewReader(bytes.NewReader(raw_req_header)))
        if req == nil {
            lines := bytes.SplitN(raw_req_header, []byte(" "), 2)
            log.Printf("%s http://%s/ \"Unknow\" HTTP/1.1 unknow %s %d\n", lines[0], host, user.Name, size)
            return
        }
        res, _ := http.ReadResponse(bufio.NewReader(bytes.NewReader(raw_res_header)), req)
        statusCode := 200
        if res != nil {
            statusCode = res.StatusCode
        }
        log.Printf("%s http://%s%s \"%s\" %s %d %s %d\n", req.Method, req.Host, req.URL.String(), req.Header.Get("user-agent"), req.Proto, statusCode, user.Name, size)
    } else {
        log.Printf("CONNECT %s \"NONE\" NONE NONE %s %d\n", host, user.Name, size)
    }
}

func checkHttp(extra []byte, conn *ss.Conn) (is_http bool, data []byte, err error) {
    var buf []byte
    var methods = []string{"GET", "HEAD", "POST", "PUT", "TRACE", "OPTIONS", "DELETE"}
    is_http = false
    if extra == nil || len(extra) < 10 {
        buf = make([]byte, 10)
        if _, err = io.ReadFull(conn, buf); err != nil {
            return
        }
    }

    if buf == nil {
        data = extra
    } else if extra == nil {
        data = buf
    }else {
        buffer := bytes.NewBuffer(extra)
        buffer.Write(buf)
        data = buffer.Bytes()
    }

    for _, method := range methods {
        if bytes.HasPrefix(data, []byte(method)) {
            is_http = true
            break
        }
    }
    return
}

const bufSize = 4096
const nBuf = 2048

func PipeThenClose(src, dst net.Conn, timeoutOpt int, is_http bool, is_res bool, user User) (total int, raw_header []byte) {
    var pipeBuf = leakybuf.NewLeakyBuf(nBuf, bufSize)
    defer dst.Close()
    buf := pipeBuf.Get()
    // defer pipeBuf.Put(buf)
    var buffer = bytes.NewBuffer(nil)
    var is_end = false
    var size int

    for {
        if timeoutOpt == ss.SET_TIMEOUT {
            ss.SetReadTimeout(src)
        }
        n, err := src.Read(buf)
        // read may return EOF with n > 0
        // should always process n > 0 bytes before handling error
        if n > 0 {
            if is_http && !is_end {
                buffer.Write(buf)
                raw_header = buffer.Bytes()
                lines := bytes.SplitN(raw_header, []byte("\r\n\r\n"), 2)
                if len(lines) == 2 {
                    is_end = true
                }
            }

            size, err = dst.Write(buf[0:n])
            if is_res {
                total_size, _ := storage.IncrSize("flow:" + user.Name, size)
                storage.ZincrbySize("flow:" + user.Name, host, res_size)
                if total_size > user.Limit {
                    return
                }
            }
            total += size
            if err != nil {
                ss.Debug.Println("write:", err)
                break
            }
        }
        if err != nil || n == 0 {
            // Always "use of closed network connection", but no easy way to
            // identify this specific error. So just leave the error along for now.
            // More info here: https://code.google.com/p/go/issues/detail?id=4373
            break
        }
    }
    return
}

func waitSignal() {
    var sigChan = make(chan os.Signal, 1)
    signal.Notify(sigChan, syscall.SIGHUP)
    for sig := range sigChan {
        if sig == syscall.SIGHUP {
        } else {
            // is this going to happen?
            log.Printf("caught signal %v, exit", sig)
            os.Exit(0)
        }
    }
}

func run(port string) {
    ln, err := net.Listen("tcp", ":"+port)
    if err != nil {
        log.Printf("error listening port %v: %v\n", port, err)
        os.Exit(0)
    }
    var cipher *ss.Cipher
    log.Printf("server listening port %v ...\n", port)
    for {
        conn, err := ln.Accept()
        if err != nil {
            log.Printf("accept error: %v\n", err)
            continue
        }

        user, err := getUser(conn)
        if err != nil {
            log.Printf("Error get passeoed: %s %v\n", port, err)
            conn.Close()
            continue
        }
        size, err := storage.GetSize("flow:" + user.Name)
        if user.Limit < size {
            log.Printf("Error user runover: %s\n", size)
            conn.Close()
            continue
        }

        // log.Println("creating cipher for user:", user.Name)
        cipher, err = ss.NewCipher(user.Method, user.Password)
        if err != nil {
            log.Printf("Error generating cipher for user: %s %v\n", user.Name, err)
            conn.Close()
            continue
        }
        go handleConnection(user, ss.NewConn(conn, cipher.Copy()))
    }
}

func main() {
    log.SetOutput(os.Stdout)

    var printVer bool
    var core int
    var serverPort string
    var redisServer string

    flag.BoolVar(&printVer, "version", false, "print version")
    flag.StringVar(&serverPort, "p", "8388", "server port")
    flag.StringVar(&redisServer, "redis", ":6379", "redis server")
    flag.IntVar(&core, "core", 0, "maximum number of CPU cores to use, default is determinied by Go runtime")
    flag.BoolVar((*bool)(&debug), "d", false, "print debug message")

    flag.Parse()

    if printVer {
        ss.PrintVersion()
        os.Exit(0)
    }

    ss.SetDebug(debug)

    storage = NewStorage(redisServer)
    if core > 0 {
        runtime.GOMAXPROCS(core)
    }

    go run(serverPort)

    waitSignal()
}
<commit_msg>Fix. pipe host for statics<commit_after>package main

import (
    "bytes"
    "encoding/binary"
    "errors"
    "flag"
    "fmt"
    "github.com/cyfdecyf/leakybuf"
    ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
    "io"
    "log"
    "net"
    "os"
    "os/signal"
    "runtime"
    "strconv"
    "syscall"
    "bufio"
    "net/http"
    "strings"
)

var debug ss.DebugLog

var storage *Storage

const dnsGoroutineNum = 64

func getUser(conn net.Conn) (user User, err error) {
    const (
        idVersion = 0 // \x01
        version   = 1
        idUser    = 1 // \x02
    )
    buf := make([]byte, 2)
    if _, err = io.ReadFull(conn, buf); err != nil {
        return
    }
    switch buf[idVersion] {
    case version:
        break
    default:
        err = errors.New(fmt.Sprintf("version %s not supported", buf[idVersion]))
        return
    }
    userLen := buf[idUser]
    username := make([]byte, userLen)
    if _, err = io.ReadFull(conn, username); err != nil {
        return
    }
    user, err = storage.Get("user:" + string(username))
    return
}

func getRequest(conn *ss.Conn) (host string, extra []byte, err error) {
    const (
        idType  = 0 // address type index
        idIP0   = 1 // ip addres start index
        idDmLen = 1 // domain address length index
        idDm0   = 2 // domain address start index

        typeIPv4 = 1 // type is ipv4 address
        typeDm   = 3 // type is domain address
        typeIPv6 = 4 // type is ipv6 address

        lenIPv4   = 1 + net.IPv4len + 2 // 1addrType + ipv4 + 2port
        lenIPv6   = 1 + net.IPv6len + 2 // 1addrType + ipv6 + 2port
        lenDmBase = 1 + 1 + 2           // 1addrType + 1addrLen + 2port, plus addrLen
    )

    // buf size should at least have the same size with the largest possible
    // request size (when addrType is 3, domain name has at most 256 bytes)
    // 1(addrType) + 1(lenByte) + 256(max length address) + 2(port)
    buf := make([]byte, 260)
    var n int
    // read till we get possible domain length field
    ss.SetReadTimeout(conn)
    if n, err = io.ReadAtLeast(conn, buf, idDmLen+1); err != nil {
        return
    }

    reqLen := -1
    switch buf[idType] {
    case typeIPv4:
        reqLen = lenIPv4
    case typeIPv6:
        reqLen = lenIPv6
    case typeDm:
        reqLen = int(buf[idDmLen]) + lenDmBase
    default:
        err = errors.New(fmt.Sprintf("addr type %d not supported", buf[idType]))
        return
    }

    if n < reqLen { // rare case
        ss.SetReadTimeout(conn)
        if _, err = io.ReadFull(conn, buf[n:reqLen]); err != nil {
            return
        }
    } else if n > reqLen {
        // it's possible to read more than just the request head
        extra = buf[reqLen:n]
    }

    // Return string for typeIP is not most efficient, but browsers (Chrome,
    // Safari, Firefox) all seems using typeDm exclusively. So this is not a
    // big problem.
    switch buf[idType] {
    case typeIPv4:
        host = net.IP(buf[idIP0 : idIP0+net.IPv4len]).String()
    case typeIPv6:
        host = net.IP(buf[idIP0 : idIP0+net.IPv6len]).String()
    case typeDm:
        host = string(buf[idDm0 : idDm0+buf[idDmLen]])
    }
    // parse port
    port := binary.BigEndian.Uint16(buf[reqLen-2 : reqLen])
    host = net.JoinHostPort(host, strconv.Itoa(int(port)))
    return
}

const logCntDelta = 100

var connCnt int
var nextLogConnCnt int = logCntDelta

func handleConnection(user User, conn *ss.Conn) {
    var host string
    var size = 0
    var raw_req_header, raw_res_header []byte
    var is_http = false
    var res_size = 0
    var req_chan = make(chan []byte)

    connCnt++ // this maybe not accurate, but should be enough
    if connCnt-nextLogConnCnt >= 0 {
        // XXX There's no xadd in the atomic package, so it's difficult to log
        // the message only once with low cost. Also note nextLogConnCnt maybe
        // added twice for current peak connection number level.
        log.Printf("Number of client connections reaches %d\n", nextLogConnCnt)
        nextLogConnCnt += logCntDelta
    }

    // function arguments are always evaluated, so surround debug statement
    // with if statement
    if debug {
        debug.Printf("new client %s->%s\n", conn.RemoteAddr().String(), conn.LocalAddr())
    }
    closed := false
    defer func() {
        if debug {
            debug.Printf("closed pipe %s<->%s\n", conn.RemoteAddr(), host)
        }
        connCnt--
        if !closed {
            conn.Close()
        }
    }()

    host, extra, err := getRequest(conn)
    if err != nil {
        log.Println("error getting request", conn.RemoteAddr(), conn.LocalAddr(), err)
        return
    }
    debug.Println("connecting", host)
    remote, err := net.Dial("tcp", host)
    if err != nil {
        if ne, ok := err.(*net.OpError); ok && (ne.Err == syscall.EMFILE || ne.Err == syscall.ENFILE) {
            // log too many open file error
            // EMFILE is process reaches open file limits, ENFILE is system limit
            log.Println("dial error:", err)
        } else {
            log.Println("error connecting to:", host, err)
        }
        return
    }
    defer func() {
        if is_http{
            tmp_req_header := <-req_chan
            buffer := bytes.NewBuffer(raw_req_header)
            buffer.Write(tmp_req_header)
            raw_req_header = buffer.Bytes()
        }
        showConn(raw_req_header, raw_res_header, host, user, size, is_http)
        close(req_chan)
        if !closed {
            remote.Close()
        }
    }()
    // write extra bytes read from

    is_http, extra, _ = checkHttp(extra, conn)
    if strings.HasSuffix(host, ":80") {
        is_http = true
    }
    raw_req_header = extra
    res_size, err = remote.Write(extra)
    storage.IncrSize("flow:" + user.Name, res_size)
    storage.ZincrbySize("flow:" + user.Name, host, res_size)
    size += res_size
    if err != nil {
        debug.Println("write request extra error:", err)
        return
    }

    if debug {
        debug.Printf("piping %s<->%s", conn.RemoteAddr(), host)
    }

    go func() {
        _, raw_header := PipeThenClose(conn, remote, ss.SET_TIMEOUT, is_http, false, host, user)
        if is_http {
            req_chan<-raw_header
        }
    }()

    res_size, raw_res_header = PipeThenClose(remote, conn, ss.NO_TIMEOUT, is_http, true, host, user)
    size += res_size
    closed = true
    return
}

func showConn(raw_req_header, raw_res_header []byte, host string, user User, size int, is_http bool) {
    if size == 0 {
        log.Printf("Error: user %s request %s cancel", user.Name, host)
        return
    }
    if is_http {
        req, _ := http.ReadRequest(bufio.NewReader(bytes.NewReader(raw_req_header)))
        if req == nil {
            lines := bytes.SplitN(raw_req_header, []byte(" "), 2)
            log.Printf("%s http://%s/ \"Unknow\" HTTP/1.1 unknow %s %d\n", lines[0], host, user.Name, size)
            return
        }
        res, _ := http.ReadResponse(bufio.NewReader(bytes.NewReader(raw_res_header)), req)
        statusCode := 200
        if res != nil {
            statusCode = res.StatusCode
        }
        log.Printf("%s http://%s%s \"%s\" %s %d %s %d\n", req.Method, req.Host, req.URL.String(), req.Header.Get("user-agent"), req.Proto, statusCode, user.Name, size)
    } else {
        log.Printf("CONNECT %s \"NONE\" NONE NONE %s %d\n", host, user.Name, size)
    }
}

func checkHttp(extra []byte, conn *ss.Conn) (is_http bool, data []byte, err error) {
    var buf []byte
    var methods = []string{"GET", "HEAD", "POST", "PUT", "TRACE", "OPTIONS", "DELETE"}
    is_http = false
    if extra == nil || len(extra) < 10 {
        buf = make([]byte, 10)
        if _, err = io.ReadFull(conn, buf); err != nil {
            return
        }
    }

    if buf == nil {
        data = extra
    } else if extra == nil {
        data = buf
    }else {
        buffer := bytes.NewBuffer(extra)
        buffer.Write(buf)
        data = buffer.Bytes()
    }

    for _, method := range methods {
        if bytes.HasPrefix(data, []byte(method)) {
            is_http = true
            break
        }
    }
    return
}

const bufSize = 4096
const nBuf = 2048

func PipeThenClose(src, dst net.Conn, timeoutOpt int, is_http bool, is_res bool, host string, user User) (total int, raw_header []byte) {
    var pipeBuf = leakybuf.NewLeakyBuf(nBuf, bufSize)
    defer dst.Close()
    buf := pipeBuf.Get()
    // defer pipeBuf.Put(buf)
    var buffer = bytes.NewBuffer(nil)
    var is_end = false
    var size int

    for {
        if timeoutOpt == ss.SET_TIMEOUT {
            ss.SetReadTimeout(src)
        }
        n, err := src.Read(buf)
        // read may return EOF with n > 0
        // should always process n > 0 bytes before handling error
        if n > 0 {
            if is_http && !is_end {
                buffer.Write(buf)
                raw_header = buffer.Bytes()
                lines := bytes.SplitN(raw_header, []byte("\r\n\r\n"), 2)
                if len(lines) == 2 {
                    is_end = true
                }
            }

            size, err = dst.Write(buf[0:n])
            if is_res {
                total_size, _ := storage.IncrSize("flow:" + user.Name, size)
                storage.ZincrbySize("flow:" + user.Name, host, size)
                if total_size > user.Limit {
                    return
                }
            }
            total += size
            if err != nil {
                ss.Debug.Println("write:", err)
                break
            }
        }
        if err != nil || n == 0 {
            // Always "use of closed network connection", but no easy way to
            // identify this specific error. So just leave the error along for now.
            // More info here: https://code.google.com/p/go/issues/detail?id=4373
            break
        }
    }
    return
}

func waitSignal() {
    var sigChan = make(chan os.Signal, 1)
    signal.Notify(sigChan, syscall.SIGHUP)
    for sig := range sigChan {
        if sig == syscall.SIGHUP {
        } else {
            // is this going to happen?
            log.Printf("caught signal %v, exit", sig)
            os.Exit(0)
        }
    }
}

func run(port string) {
    ln, err := net.Listen("tcp", ":"+port)
    if err != nil {
        log.Printf("error listening port %v: %v\n", port, err)
        os.Exit(0)
    }
    var cipher *ss.Cipher
    log.Printf("server listening port %v ...\n", port)
    for {
        conn, err := ln.Accept()
        if err != nil {
            log.Printf("accept error: %v\n", err)
            continue
        }

        user, err := getUser(conn)
        if err != nil {
            log.Printf("Error get passeoed: %s %v\n", port, err)
            conn.Close()
            continue
        }
        size, err := storage.GetSize("flow:" + user.Name)
        if user.Limit < size {
            log.Printf("Error user runover: %s\n", size)
            conn.Close()
            continue
        }

        // log.Println("creating cipher for user:", user.Name)
        cipher, err = ss.NewCipher(user.Method, user.Password)
        if err != nil {
            log.Printf("Error generating cipher for user: %s %v\n", user.Name, err)
            conn.Close()
            continue
        }
        go handleConnection(user, ss.NewConn(conn, cipher.Copy()))
    }
}

func main() {
    log.SetOutput(os.Stdout)

    var printVer bool
    var core int
    var serverPort string
    var redisServer string

    flag.BoolVar(&printVer, "version", false, "print version")
    flag.StringVar(&serverPort, "p", "8388", "server port")
    flag.StringVar(&redisServer, "redis", ":6379", "redis server")
    flag.IntVar(&core, "core", 0, "maximum number of CPU cores to use, default is determinied by Go runtime")
    flag.BoolVar((*bool)(&debug), "d", false, "print debug message")

    flag.Parse()

    if printVer {
        ss.PrintVersion()
        os.Exit(0)
    }

    ss.SetDebug(debug)

    storage = NewStorage(redisServer)
    if core > 0 {
        runtime.GOMAXPROCS(core)
    }

    go run(serverPort)

    waitSignal()
}
<|endoftext|>


<commit_before>package server

import (
	"net/http"

	"log"

	"github.com/MSC-XDU/playground/c"
	"github.com/MSC-XDU/playground/golang"
	"github.com/MSC-XDU/playground/python"
	"github.com/gorilla/mux"
)

func Start() {
	router := mux.NewRouter()
	router.HandleFunc("/go", wrapMiddleware(golang.ServerHandle)).Methods(http.MethodPost)
	router.HandleFunc("/python", wrapMiddleware(python.ServerHandle)).Methods(http.MethodPost)
	router.HandleFunc("/c", wrapMiddleware(c.ServerHandle)).Methods(http.MethodPost)
	router.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		http.ServeFile(w, req, "test.html")
	}).Methods(http.MethodGet)
	router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
	http.ListenAndServe(":9090", router)
}

type Handle func(http.ResponseWriter, *http.Request)
type Middleware func(Handle) Handle

func ParseMultipartForm(handle Handle) Handle {
	return func(w http.ResponseWriter, req *http.Request) {
		err := req.ParseMultipartForm(4096)
		if err != nil {
			w.WriteHeader(400)
			log.Println(err.Error())
			w.Write([]byte("异常请求"))
			return
		}
		handle(w, req)
	}
}

var middleware = []Middleware{
	ParseMultipartForm,
}

func wrapMiddleware(handle Handle) Handle {
	for _, m := range middleware {
		handle = m(handle)
	}
	return handle
}
<commit_msg>移除 Test handle<commit_after>package server

import (
	"net/http"

	"log"

	"github.com/MSC-XDU/playground/c"
	"github.com/MSC-XDU/playground/golang"
	"github.com/MSC-XDU/playground/python"
	"github.com/gorilla/mux"
)

func Start() {
	router := mux.NewRouter()
	router.HandleFunc("/go", wrapMiddleware(golang.ServerHandle)).Methods(http.MethodPost)
	router.HandleFunc("/python", wrapMiddleware(python.ServerHandle)).Methods(http.MethodPost)
	router.HandleFunc("/c", wrapMiddleware(c.ServerHandle)).Methods(http.MethodPost)
	//router.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
	//	http.ServeFile(w, req, "test.html")
	//}).Methods(http.MethodGet)
	router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
	http.ListenAndServe(":9090", router)
}

type Handle func(http.ResponseWriter, *http.Request)
type Middleware func(Handle) Handle

func ParseMultipartForm(handle Handle) Handle {
	return func(w http.ResponseWriter, req *http.Request) {
		err := req.ParseMultipartForm(4096)
		if err != nil {
			w.WriteHeader(400)
			log.Println(err.Error())
			w.Write([]byte("异常请求"))
			return
		}
		handle(w, req)
	}
}

var middleware = []Middleware{
	ParseMultipartForm,
}

func wrapMiddleware(handle Handle) Handle {
	for _, m := range middleware {
		handle = m(handle)
	}
	return handle
}
<|endoftext|>


<commit_before>package server

import "fmt"
import "errors"
import "io"
import "ldpserver/ldp"
import "ldpserver/util"
import "ldpserver/textstore"

const defaultSlug string = "node"

type Server struct {
	settings ldp.Settings
	minter   chan string
	// this should use an interface to it's not tied to "textStore"
	nextResource chan textstore.Store
}

func NewServer(rootUri string, dataPath string) Server {
	var server Server
	server.settings = ldp.SettingsNew(rootUri, dataPath)
	ldp.CreateRoot(server.settings)
	server.minter = CreateMinter(server.settings.IdFile())
	server.nextResource = make(chan textstore.Store)
	return server
}

func (server Server) GetNode(path string) (ldp.Node, error) {
	return ldp.GetNode(server.settings, path)
}

func (server Server) GetHead(path string) (ldp.Node, error) {
	return ldp.GetHead(server.settings, path)
}

func (server Server) CreateRdfSource(triples string, parentPath string, slug string) (ldp.Node, error) {
	container, err := server.getContainer(parentPath)
	if err != nil {
		return ldp.Node{}, err
	}

	newPath, err := server.getNewPath(slug)
	if err != nil {
		return ldp.Node{}, err
	}

	bag := server.createResource(parentPath, newPath)
	if bag.Error() != nil {
		return ldp.Node{}, bag.Error()
	}

	node, err := ldp.NewRdfNode(server.settings, triples, parentPath, newPath)
	if err != nil {
		return ldp.Node{}, err
	}

	if err := container.AddChild(node); err != nil {
		return ldp.Node{}, err
	}
	return node, nil
}

func (server Server) CreateNonRdfSource(reader io.ReadCloser, parentPath string, slug string) (ldp.Node, error) {
	container, err := server.getContainer(parentPath)
	if err != nil {
		return ldp.Node{}, err
	}

	newPath, err := server.getNewPath(slug)
	if err != nil {
		return ldp.Node{}, err
	}

	bag := server.createResource(parentPath, newPath)
	if bag.Error() != nil {
		return ldp.Node{}, bag.Error()
	}

	node, err := ldp.NewNonRdfNode(server.settings, reader, parentPath, newPath)
	if err != nil {
		return node, err
	}

	if err := container.AddChild(node); err != nil {
		return node, err
	}
	return node, nil
}

func (server Server) PatchNode(path string, triples string) error {
	node, err := ldp.GetNode(server.settings, path)
	if err != nil {
		return err
	}
	return node.Patch(triples)
}

func (server Server) getNewPath(slug string) (string, error) {
	if slug == "" {
		// Generate a new server URI (e.g. node34)
		return MintNextUri(defaultSlug, server.minter), nil
	}

	if !util.IsValidSlug(slug) {
		errorMsg := fmt.Sprintf("Invalid Slug received (%s). Slug must not include special characters.", slug)
		return "", errors.New(errorMsg)
	}
	return slug, nil
}

func (server Server) createResource(parentPath string, newPath string) textstore.Store {
	// Queue up the creation of a new resource
	path := util.UriConcat(parentPath, newPath)
	fullPath := util.PathConcat(server.settings.DataPath(), path)
	go func(fullPath string) {
		server.nextResource <- textstore.CreateStore(fullPath)
	}(fullPath)

	// Wait for the new resource to be available.
	bag := <-server.nextResource
	return bag
}

func (server Server) getContainer(path string) (ldp.Node, error) {
	if isRootPath(path) {
		// Shortcut. We know for sure this is a container
		return ldp.GetHead(server.settings, "/")
	}

	node, err := ldp.GetNode(server.settings, path)
	if err != nil {
		return node, err
	} else if !node.IsBasicContainer() {
		errorMsg := fmt.Sprintf("%s is not a container", path)
		return node, errors.New(errorMsg)
	}
	return node, nil
}

func (server Server) getContainerUri(parentPath string) (string, error) {
	if isRootPath(parentPath) {
		return server.settings.RootUri(), nil
	}

	// Make sure the parent node exists and it's a container
	parentNode, err := ldp.GetNode(server.settings, parentPath)
	if err != nil {
		return "", err
	} else if !parentNode.IsBasicContainer() {
		return "", errors.New("Parent is not a container")
	}
	return parentNode.Uri(), nil
}

func isRootPath(path string) bool {
	return path == "" || path == "/"
}
<commit_msg>Removed outdated reference to bags<commit_after>package server

import "fmt"
import "errors"
import "io"
import "ldpserver/ldp"
import "ldpserver/util"
import "ldpserver/textstore"

const defaultSlug string = "node"

type Server struct {
	settings ldp.Settings
	minter   chan string
	// this should use an interface to it's not tied to "textStore"
	nextResource chan textstore.Store
}

func NewServer(rootUri string, dataPath string) Server {
	var server Server
	server.settings = ldp.SettingsNew(rootUri, dataPath)
	ldp.CreateRoot(server.settings)
	server.minter = CreateMinter(server.settings.IdFile())
	server.nextResource = make(chan textstore.Store)
	return server
}

func (server Server) GetNode(path string) (ldp.Node, error) {
	return ldp.GetNode(server.settings, path)
}

func (server Server) GetHead(path string) (ldp.Node, error) {
	return ldp.GetHead(server.settings, path)
}

func (server Server) CreateRdfSource(triples string, parentPath string, slug string) (ldp.Node, error) {
	container, err := server.getContainer(parentPath)
	if err != nil {
		return ldp.Node{}, err
	}

	newPath, err := server.getNewPath(slug)
	if err != nil {
		return ldp.Node{}, err
	}

	bag := server.createResource(parentPath, newPath)
	if bag.Error() != nil {
		return ldp.Node{}, bag.Error()
	}

	node, err := ldp.NewRdfNode(server.settings, triples, parentPath, newPath)
	if err != nil {
		return ldp.Node{}, err
	}

	if err := container.AddChild(node); err != nil {
		return ldp.Node{}, err
	}
	return node, nil
}

func (server Server) CreateNonRdfSource(reader io.ReadCloser, parentPath string, slug string) (ldp.Node, error) {
	container, err := server.getContainer(parentPath)
	if err != nil {
		return ldp.Node{}, err
	}

	newPath, err := server.getNewPath(slug)
	if err != nil {
		return ldp.Node{}, err
	}

	bag := server.createResource(parentPath, newPath)
	if bag.Error() != nil {
		return ldp.Node{}, bag.Error()
	}

	node, err := ldp.NewNonRdfNode(server.settings, reader, parentPath, newPath)
	if err != nil {
		return node, err
	}

	if err := container.AddChild(node); err != nil {
		return node, err
	}
	return node, nil
}

func (server Server) PatchNode(path string, triples string) error {
	node, err := ldp.GetNode(server.settings, path)
	if err != nil {
		return err
	}
	return node.Patch(triples)
}

func (server Server) getNewPath(slug string) (string, error) {
	if slug == "" {
		// Generate a new server URI (e.g. node34)
		return MintNextUri(defaultSlug, server.minter), nil
	}

	if !util.IsValidSlug(slug) {
		errorMsg := fmt.Sprintf("Invalid Slug received (%s). Slug must not include special characters.", slug)
		return "", errors.New(errorMsg)
	}
	return slug, nil
}

func (server Server) createResource(parentPath string, newPath string) textstore.Store {
	// Queue up the creation of a new resource
	path := util.UriConcat(parentPath, newPath)
	fullPath := util.PathConcat(server.settings.DataPath(), path)
	go func(fullPath string) {
		server.nextResource <- textstore.CreateStore(fullPath)
	}(fullPath)

	// Wait for the new resource to be available.
	resource := <-server.nextResource
	return resource
}

func (server Server) getContainer(path string) (ldp.Node, error) {
	if isRootPath(path) {
		// Shortcut. We know for sure this is a container
		return ldp.GetHead(server.settings, "/")
	}

	node, err := ldp.GetNode(server.settings, path)
	if err != nil {
		return node, err
	} else if !node.IsBasicContainer() {
		errorMsg := fmt.Sprintf("%s is not a container", path)
		return node, errors.New(errorMsg)
	}
	return node, nil
}

func (server Server) getContainerUri(parentPath string) (string, error) {
	if isRootPath(parentPath) {
		return server.settings.RootUri(), nil
	}

	// Make sure the parent node exists and it's a container
	parentNode, err := ldp.GetNode(server.settings, parentPath)
	if err != nil {
		return "", err
	} else if !parentNode.IsBasicContainer() {
		return "", errors.New("Parent is not a container")
	}
	return parentNode.Uri(), nil
}

func isRootPath(path string) bool {
	return path == "" || path == "/"
}
<|endoftext|>


<commit_before>package server

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"

	"github.com/jpillora/cloud-torrent/engine"
	"github.com/jpillora/cloud-torrent/static"
	"github.com/jpillora/go-realtime"
	"github.com/jpillora/requestlog"
	"github.com/jpillora/scraper/scraper"
	"github.com/skratchdot/open-golang/open"
)

//Server is the "State" portion of the diagram
type Server struct {
	//config
	Title      string `help:"Title of this instance" env:"TITLE"`
	Port       int    `help:"Listening port" env:"PORT"`
	Host       string `help:"Listening interface (default all)"`
	Auth       string `help:"Optional basic auth in form 'user:password'" env:"AUTH"`
	ConfigPath string `help:"Configuration file path"`
	KeyPath    string `help:"TLS Key file path"`
	CertPath   string `help:"TLS Certicate file path" short:"r"`
	Log        bool   `help:"Enable request logging"`
	Open       bool   `help:"Open now with your default browser"`
	//http handlers
	files, static http.Handler
	scraper       *scraper.Handler
	scraperh      http.Handler
	//torrent engine
	engine *engine.Engine
	//realtime state (sync'd with browser immediately)
	rt    *realtime.Handler
	state struct {
		realtime.Object
		sync.Mutex
		Config          engine.Config
		SearchProviders scraper.Config
		Downloads       *fsNode
		Torrents        map[string]*engine.Torrent
		Users           map[string]*realtime.User
		Stats           struct {
			Title   string
			Version string
			Runtime string
			Uptime  time.Time
		}
	}
}

func (s *Server) Run(version string) error {

	tls := s.CertPath != "" || s.KeyPath != "" //poor man's XOR
	if tls && (s.CertPath == "" || s.KeyPath == "") {
		return fmt.Errorf("You must provide both key and cert paths")
	}

	s.state.Stats.Title = s.Title
	s.state.Stats.Version = version
	s.state.Stats.Runtime = strings.TrimPrefix(runtime.Version(), "go")
	s.state.Stats.Uptime = time.Now()

	//init maps
	s.state.Users = map[string]*realtime.User{}
	//will use a the local embed/ dir if it exists, otherwise will use the hardcoded embedded binaries
	s.files = http.HandlerFunc(s.serveFiles)
	s.static = ctstatic.FileSystemHandler()
	s.scraper = &scraper.Handler{Log: false}
	if err := s.scraper.LoadConfig(defaultSearchConfig); err != nil {
		log.Fatal(err)
	}
	s.state.SearchProviders = s.scraper.Config //share scraper config
	s.scraperh = http.StripPrefix("/search", s.scraper)

	s.engine = engine.New()

	//realtime
	s.rt = realtime.NewHandler()
	if err := s.rt.Add("state", &s.state); err != nil {
		log.Fatalf("State object not syncable: %s", err)
	}
	//realtime user events
	go func() {
		for user := range s.rt.UserEvents() {
			if user.Connected {
				s.state.Users[user.ID] = user
			} else {
				delete(s.state.Users, user.ID)
			}
			s.state.Update()
		}
	}()

	//configure engine
	c := engine.Config{
		DownloadDirectory: "./downloads",
		EnableUpload:      true,
		AutoStart:         true,
	}
	if _, err := os.Stat(s.ConfigPath); err == nil {
		if b, err := ioutil.ReadFile(s.ConfigPath); err != nil {
			return fmt.Errorf("Read configuration error: %s", err)
		} else if len(b) == 0 {
			//ignore empty file
		} else if err := json.Unmarshal(b, &c); err != nil {
			return fmt.Errorf("Malformed configuration: %s", err)
		}
	}
	if c.IncomingPort <= 0 || c.IncomingPort >= 65535 {
		c.IncomingPort = 50007
	}
	if err := s.reconfigure(c); err != nil {
		return fmt.Errorf("initial configure failed: %s", err)
	}

	//poll torrents and files
	go func() {
		for {
			s.state.Lock()
			s.state.Torrents = s.engine.GetTorrents()
			s.state.Downloads = s.listFiles()
			// log.Printf("torrents #%d files #%d", len(s.state.Torrents), len(s.state.Downloads.Children))
			s.state.Unlock()
			s.state.Update()
			time.Sleep(1 * time.Second)
		}
	}()

	host := s.Host
	if host == "" {
		host = "0.0.0.0"
	}
	addr := fmt.Sprintf("%s:%d", host, s.Port)
	proto := "http"
	if tls {
		proto += "s"
	}
	log.Printf("Listening at %s://%s", proto, addr)

	if s.Open {
		openhost := host
		if openhost == "0.0.0.0" {
			openhost = "localhost"
		}
		go func() {
			time.Sleep(1 * time.Second)
			open.Run(fmt.Sprintf("%s://%s:%d", proto, openhost, s.Port))
		}()
	}

	h := http.Handler(http.HandlerFunc(s.handle))
	if s.Log {
		h = requestlog.Wrap(h)
	}

	if tls {
		return http.ListenAndServeTLS(addr, s.CertPath, s.KeyPath, h)
	} else {
		return http.ListenAndServe(addr, h)
	}
}

func (s *Server) reconfigure(c engine.Config) error {
	dldir, err := filepath.Abs(c.DownloadDirectory)
	if err != nil {
		return fmt.Errorf("Invalid path")
	}
	c.DownloadDirectory = dldir
	if err := s.engine.Configure(c); err != nil {
		return err
	}
	b, _ := json.MarshalIndent(&c, "", "  ")
	ioutil.WriteFile(s.ConfigPath, b, 0755)
	s.state.Config = c
	s.state.Update()
	return nil
}

func (s *Server) handle(w http.ResponseWriter, r *http.Request) {

	//handle realtime client connections
	if r.URL.Path == "/realtime.js" {
		realtime.JS.ServeHTTP(w, r)
		return
	} else if r.URL.Path == "/realtime" {
		s.rt.ServeHTTP(w, r)
		return
	}

	//basic auth
	if s.Auth != "" {
		u, p, _ := r.BasicAuth()
		if s.Auth != u+":"+p {
			w.Header().Set("WWW-Authenticate", "Basic")
			w.WriteHeader(http.StatusUnauthorized)
			w.Write([]byte("Access Denied"))
			return
		}
	}
	//search
	if strings.HasPrefix(r.URL.Path, "/search") {
		s.scraperh.ServeHTTP(w, r)
		return
	}
	//api call
	if strings.HasPrefix(r.URL.Path, "/api/") {
		//only pass request in, expect error out
		if err := s.api(r); err == nil {
			w.WriteHeader(http.StatusOK)
			w.Write([]byte("OK"))
		} else {
			w.WriteHeader(http.StatusBadRequest)
			w.Write([]byte(err.Error()))
		}
		return
	}
	//no match, assume static file
	s.files.ServeHTTP(w, r)
}
<commit_msg>Update server.go<commit_after>package server

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"

	"github.com/anmolmonga2009/toru/engine"
	"github.com/anmolmonga2009/toru/static"
	"github.com/jpillora/go-realtime"
	"github.com/jpillora/requestlog"
	"github.com/jpillora/scraper/scraper"
	"github.com/skratchdot/open-golang/open"
)

//Server is the "State" portion of the diagram
type Server struct {
	//config
	Title      string `help:"Title of this instance" env:"TITLE"`
	Port       int    `help:"Listening port" env:"PORT"`
	Host       string `help:"Listening interface (default all)"`
	Auth       string `help:"Optional basic auth in form 'user:password'" env:"AUTH"`
	ConfigPath string `help:"Configuration file path"`
	KeyPath    string `help:"TLS Key file path"`
	CertPath   string `help:"TLS Certicate file path" short:"r"`
	Log        bool   `help:"Enable request logging"`
	Open       bool   `help:"Open now with your default browser"`
	//http handlers
	files, static http.Handler
	scraper       *scraper.Handler
	scraperh      http.Handler
	//torrent engine
	engine *engine.Engine
	//realtime state (sync'd with browser immediately)
	rt    *realtime.Handler
	state struct {
		realtime.Object
		sync.Mutex
		Config          engine.Config
		SearchProviders scraper.Config
		Downloads       *fsNode
		Torrents        map[string]*engine.Torrent
		Users           map[string]*realtime.User
		Stats           struct {
			Title   string
			Version string
			Runtime string
			Uptime  time.Time
		}
	}
}

func (s *Server) Run(version string) error {

	tls := s.CertPath != "" || s.KeyPath != "" //poor man's XOR
	if tls && (s.CertPath == "" || s.KeyPath == "") {
		return fmt.Errorf("You must provide both key and cert paths")
	}

	s.state.Stats.Title = s.Title
	s.state.Stats.Version = version
	s.state.Stats.Runtime = strings.TrimPrefix(runtime.Version(), "go")
	s.state.Stats.Uptime = time.Now()

	//init maps
	s.state.Users = map[string]*realtime.User{}
	//will use a the local embed/ dir if it exists, otherwise will use the hardcoded embedded binaries
	s.files = http.HandlerFunc(s.serveFiles)
	s.static = ctstatic.FileSystemHandler()
	s.scraper = &scraper.Handler{Log: false}
	if err := s.scraper.LoadConfig(defaultSearchConfig); err != nil {
		log.Fatal(err)
	}
	s.state.SearchProviders = s.scraper.Config //share scraper config
	s.scraperh = http.StripPrefix("/search", s.scraper)

	s.engine = engine.New()

	//realtime
	s.rt = realtime.NewHandler()
	if err := s.rt.Add("state", &s.state); err != nil {
		log.Fatalf("State object not syncable: %s", err)
	}
	//realtime user events
	go func() {
		for user := range s.rt.UserEvents() {
			if user.Connected {
				s.state.Users[user.ID] = user
			} else {
				delete(s.state.Users, user.ID)
			}
			s.state.Update()
		}
	}()

	//configure engine
	c := engine.Config{
		DownloadDirectory: "./downloads",
		EnableUpload:      true,
		AutoStart:         true,
	}
	if _, err := os.Stat(s.ConfigPath); err == nil {
		if b, err := ioutil.ReadFile(s.ConfigPath); err != nil {
			return fmt.Errorf("Read configuration error: %s", err)
		} else if len(b) == 0 {
			//ignore empty file
		} else if err := json.Unmarshal(b, &c); err != nil {
			return fmt.Errorf("Malformed configuration: %s", err)
		}
	}
	if c.IncomingPort <= 0 || c.IncomingPort >= 65535 {
		c.IncomingPort = 50009
	}
	if err := s.reconfigure(c); err != nil {
		return fmt.Errorf("initial configure failed: %s", err)
	}

	//poll torrents and files
	go func() {
		for {
			s.state.Lock()
			s.state.Torrents = s.engine.GetTorrents()
			s.state.Downloads = s.listFiles()
			// log.Printf("torrents #%d files #%d", len(s.state.Torrents), len(s.state.Downloads.Children))
			s.state.Unlock()
			s.state.Update()
			time.Sleep(1 * time.Second)
		}
	}()

	host := s.Host
	if host == "" {
		host = "0.0.0.0"
	}
	addr := fmt.Sprintf("%s:%d", host, s.Port)
	proto := "http"
	if tls {
		proto += "s"
	}
	log.Printf("Listening at %s://%s", proto, addr)

	if s.Open {
		openhost := host
		if openhost == "0.0.0.0" {
			openhost = "localhost"
		}
		go func() {
			time.Sleep(1 * time.Second)
			open.Run(fmt.Sprintf("%s://%s:%d", proto, openhost, s.Port))
		}()
	}

	h := http.Handler(http.HandlerFunc(s.handle))
	if s.Log {
		h = requestlog.Wrap(h)
	}

	if tls {
		return http.ListenAndServeTLS(addr, s.CertPath, s.KeyPath, h)
	} else {
		return http.ListenAndServe(addr, h)
	}
}

func (s *Server) reconfigure(c engine.Config) error {
	dldir, err := filepath.Abs(c.DownloadDirectory)
	if err != nil {
		return fmt.Errorf("Invalid path")
	}
	c.DownloadDirectory = dldir
	if err := s.engine.Configure(c); err != nil {
		return err
	}
	b, _ := json.MarshalIndent(&c, "", "  ")
	ioutil.WriteFile(s.ConfigPath, b, 0755)
	s.state.Config = c
	s.state.Update()
	return nil
}

func (s *Server) handle(w http.ResponseWriter, r *http.Request) {

	//handle realtime client connections
	if r.URL.Path == "/realtime.js" {
		realtime.JS.ServeHTTP(w, r)
		return
	} else if r.URL.Path == "/realtime" {
		s.rt.ServeHTTP(w, r)
		return
	}

	//basic auth
	if s.Auth != "" {
		u, p, _ := r.BasicAuth()
		if s.Auth != u+":"+p {
			w.Header().Set("WWW-Authenticate", "Basic")
			w.WriteHeader(http.StatusUnauthorized)
			w.Write([]byte("Access Denied"))
			return
		}
	}
	//search
	if strings.HasPrefix(r.URL.Path, "/search") {
		s.scraperh.ServeHTTP(w, r)
		return
	}
	//api call
	if strings.HasPrefix(r.URL.Path, "/api/") {
		//only pass request in, expect error out
		if err := s.api(r); err == nil {
			w.WriteHeader(http.StatusOK)
			w.Write([]byte("OK"))
		} else {
			w.WriteHeader(http.StatusBadRequest)
			w.Write([]byte(err.Error()))
		}
		return
	}
	//no match, assume static file
	s.files.ServeHTTP(w, r)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"log"
	"net/http"
	"strconv"

	"github.com/campoy/todo/todo"
	"github.com/gorilla/mux"
)

func main() {
	r := mux.NewRouter()
	r.HandleFunc("/task", listTasks).Methods("GET")
	r.HandleFunc("/task", newTask).Methods("POST")
	r.HandleFunc("/task/{id}", getTask).Methods("GET")
	r.HandleFunc("/task/{id}", updateTask).Methods("PUT")
	http.ListenAndServe(":8080", r)
}

var tasks = todo.NewTaskManager()

func listTasks(w http.ResponseWriter, r *http.Request) {
	var res struct{ Tasks []*todo.Task }
	res.Tasks = tasks.All()
	err := json.NewEncoder(w).Encode(res)
	if err != nil {
		http.Error(w, "oops", http.StatusInternalServerError)
		log.Println(err)
	}
}

func newTask(w http.ResponseWriter, r *http.Request) {
	req := struct{ Title string }{}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	t, err := todo.NewTask(req.Title)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	tasks.Save(t)
}

func getTask(w http.ResponseWriter, r *http.Request) {
	txt := mux.Vars(r)["id"]
	id, err := strconv.ParseInt(txt, 10, 0)
	if err != nil {
		http.Error(w, "task ID is not a number", http.StatusBadRequest)
		return
	}
	t, ok := tasks.Find(id)
	if !ok {
		http.NotFound(w, r)
		return
	}

	if err := json.NewEncoder(w).Encode(t); err != nil {
		http.Error(w, "oops", http.StatusInternalServerError)
		log.Println(err)
	}
}

func updateTask(w http.ResponseWriter, r *http.Request) {
	txt := mux.Vars(r)["id"]
	id, err := strconv.ParseInt(txt, 10, 0)
	if err != nil {
		http.Error(w, "task ID is not a number", http.StatusBadRequest)
		return
	}
	var t todo.Task
	if err := json.NewDecoder(r.Body).Decode(&t); err != nil {
		http.Error(w, "decode failed", http.StatusBadRequest)
		return
	}
	if t.ID != id {
		http.Error(w, "inconsistent task ID", http.StatusBadRequest)
		return
	}
	if _, ok := tasks.Find(t.ID); !ok {
		http.NotFound(w, r)
		return
	}
	tasks.Save(&t)
}
<commit_msg>added docs<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"

	"github.com/campoy/todo/todo"
	"github.com/gorilla/mux"
)

func main() {
	r := mux.NewRouter()
	r.HandleFunc("/task", errorHandler(ListTasks)).Methods("GET")
	r.HandleFunc("/task", errorHandler(NewTask)).Methods("POST")
	r.HandleFunc("/task/{id}", errorHandler(GetTask)).Methods("GET")
	r.HandleFunc("/task/{id}", errorHandler(UpdateTask)).Methods("PUT")
	http.ListenAndServe(":8080", r)
}

var tasks = todo.NewTaskManager()

type badRequest struct{ error }
type notFound struct{ error }

func errorHandler(f func(w http.ResponseWriter, r *http.Request) error) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		err := f(w, r)
		if err == nil {
			return
		}
		switch err.(type) {
		case badRequest:
			http.Error(w, err.Error(), http.StatusBadRequest)
		case notFound:
			http.Error(w, "task not found", http.StatusNotFound)
		default:
			log.Println(err)
			http.Error(w, "oops", http.StatusInternalServerError)
		}
	}
}

// ListTask handles GET requests on /task.
// There's no parameters and it returns an object with a Tasks field containing a list of tasks.
//
// Example:
//
//   req: GET /task
//   res: 200 {"Tasks": [
//          {"ID": 1, "Title": "Learn Go", "Done": false},
//          {"ID": 2, "Title": "Buy bread", "Done": true}
//        ]}
func ListTasks(w http.ResponseWriter, r *http.Request) error {
	res := struct{ Tasks []*todo.Task }{tasks.All()}
	return json.NewEncoder(w).Encode(res)
}

// NewTask handles POST requests on /task.
// The request body must contain a JSON object with a Title field.
// The status code of the response is used to indicate any error.
//
// Examples:
//
//   req: POST /task {"Title": ""}
//   res: 400 empty title
//
//   req: POST /task {"Title": "Buy bread"}
//   res: 200
func NewTask(w http.ResponseWriter, r *http.Request) error {
	req := struct{ Title string }{}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		return badRequest{err}
	}
	t, err := todo.NewTask(req.Title)
	if err != nil {
		return badRequest{err}
	}
	return tasks.Save(t)
}

// parseID obtains the id variable from the given request url,
// parses the obtained text and returns the result.
func parseID(r *http.Request) (int64, error) {
	txt, ok := mux.Vars(r)["id"]
	if !ok {
		return 0, fmt.Errorf("task id not found")
	}
	return strconv.ParseInt(txt, 10, 0)
}

// GetTask handles GET requsts to /task/{taskID}.
// There's no parameters and it returns a JSON encoded task.
//
// Examples:
//
//   req: GET /task/1
//   res: 200 {"ID": 1, "Title": "Buy bread", "Done": true}
//
//   req: GET /task/42
//   res: 404 task not found
func GetTask(w http.ResponseWriter, r *http.Request) error {
	id, err := parseID(r)
	if err != nil {
		return badRequest{err}
	}
	t, ok := tasks.Find(id)
	if !ok {
		return notFound{}
	}
	return json.NewEncoder(w).Encode(t)
}

// UpdateTask handles PUT requests to /task/{taskID}.
// The request body must contain a JSON encoded task.
//
// Example:
//
//   req: PUT /task/1 {"ID": 1, "Title": "Learn Go", "Done": true}
//   res: 200
//
//   req: PUT /task/2 {"ID": 2, "Title": "Learn Go", "Done": true}
//   res: 400 inconsistent task IDs
func UpdateTask(w http.ResponseWriter, r *http.Request) error {
	id, err := parseID(r)
	if err != nil {
		return badRequest{err}
	}
	var t todo.Task
	if err := json.NewDecoder(r.Body).Decode(&t); err != nil {
		return badRequest{err}
	}
	if t.ID != id {
		return badRequest{fmt.Errorf("inconsistent task IDs")}
	}
	if _, ok := tasks.Find(t.ID); !ok {
		return notFound{}
	}
	return tasks.Save(&t)
}
<|endoftext|>


<commit_before>package server

import (
	"axon-docker/bar"
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/jaschaephraim/lrserver"
	"gopkg.in/fsnotify.v1"
)

func init() {
	sizestr.ToggleCase()
}

//Server is custom file server
type Server struct {
	c        *Config
	addr     string
	port     string
	root     string
	hasIndex bool
	fallback *httputil.ReverseProxy
	watcher  *fsnotify.Watcher
	watching map[string]bool
	lr       *lrserver.Server
}

//NewServer creates a new Server
func New(c *Config) (*Server, error) {

	port := strconv.Itoa(c.Port)
	s := &Server{
		c:    c,
		port: port,
		addr: c.Host + ":" + port,
	}

	_, err := os.Stat(c.Dir)
	if c.Dir == "" || err != nil {
		return nil, fmt.Errorf("Missing directory: %s", c.Dir)
	}

	if c.PushState {
		s.root = filepath.Join(c.Dir, "index.html")
		if _, err := os.Stat(s.root); err != nil {
			return nil, fmt.Errorf("'%s' is required for pushstate", s.root)

		}
		s.hasIndex = true
	}

	if c.Fallback != "" {
		u, err := url.Parse(c.Fallback)

		if !strings.HasPrefix(u.Scheme, "http") {
			return nil, fmt.Errorf("Invalid fallback protocol scheme")
		}

		if err != nil {
			return nil, err
		}
		s.fallback = httputil.NewSingleHostReverseProxy(u)
	}

	if c.LiveReload {
		s.lr, _ = lrserver.New("serve-lr", lrserver.DefaultPort)
		discard := log.New(ioutil.Discard, "", 0)
		s.lr.SetErrorLog(discard)
		s.lr.SetStatusLog(discard)
		s.watching = map[string]bool{}
		s.watcher, err = fsnotify.NewWatcher()
		if err != nil {
			return nil, err
		}
	}

	return s, nil
}

func (s *Server) Start() error {

	if s.c.Open {
		go func() {
			time.Sleep(500 * time.Millisecond)
			cmd := exec.Command("open", "http://localhost:"+s.port)
			cmd.Run()
		}()
	}

	if s.c.LiveReload {
		go func() {
			if err := s.lr.ListenAndServe(); err != nil {
				fmt.Printf("LiveReload server closed: %s", err)
			}
		}()
		go func() {
			for {
				event := <-s.watcher.Events
				s.lr.Reload(event.Name)
			}
		}()
	}

	if !s.c.NoLogging {
		fmt.Println(c("serving ", "grey") +
			c(ShortenPath(s.c.Dir), "cyan") +
			c(" on port ", "grey") +
			c(s.port, "cyan"))
	}
	return http.ListenAndServe(s.addr, http.HandlerFunc(s.handle))
}

func (s *Server) handle(w http.ResponseWriter, r *http.Request) {

	path := r.URL.Path

	//reporting
	var sf *fakeFile
	var code int
	reply := func(c int, msg string) {
		w.WriteHeader(c)
		code = c
		if msg != "" {
			w.Write([]byte(msg))
		}
	}
	t0 := time.Now()
	defer func() {
		t := time.Now().Sub(t0)
		size := ""
		if sf != nil {
			size = " " + sizestr.ToString(sf.read)
		}
		if !s.c.NoLogging {
			fmt.Println(c(r.Method+" "+r.URL.Path, "grey") +
				" " + fmtcode(code) + " " +
				c(fmtduration(t)+size, "grey"))
		}
	}()

	//requested file
	p := filepath.Join(s.c.Dir, path)

	//check file or dir
	isdir := false
	if info, err := os.Stat(p); err == nil {
		//found! -> is file or dir?
		isdir = info.IsDir()
	} else if err != nil {
		//missing! -> no-pushstate or has extension?
		if !s.c.PushState || filepath.Ext(p) != "" {
			//not found! handle with proxy?
			if s.fallback != nil {
				s.fallback.ServeHTTP(w, r)
				return
			}
			//not found!!
			reply(404, "Not found")
			return
		}
		p = s.root //known to exist
		isdir = false
	}

	//optionally use index instead of directory list
	if isdir && !s.c.NoIndex {
		dirindex := filepath.Join(p, "index.html")
		if _, err := os.Stat(dirindex); err == nil {
			p = dirindex
			isdir = false
		}
	}

	//directory list
	if isdir {
		if s.c.NoList {
			reply(403, "Listing not allowed")
			return
		}
		files, err := ioutil.ReadDir(p)
		if err != nil {
			reply(500, err.Error())
			return
		}
		buff := &bytes.Buffer{}
		for _, f := range files {
			n := f.Name()
			if f.IsDir() {
				n += "/"
			}
			s := fmt.Sprintf("\n\t%s\n
\n", filepath.Join(path, n), n)
			buff.WriteString(s)
		}
		w.Header().Set("Content-Type", "text/html")
		reply(200, buff.String())
		return
	}

	//check file again
	info, err := os.Stat(p)
	if err != nil {
		reply(404, "Not found")
		return
	}

	//stream file
	f, err := os.Open(p)
	if err != nil {
		reply(500, err.Error())
		return
	}

	//add all served directories to the watcher
	if s.c.LiveReload {
		dir, _ := filepath.Split(p)
		if _, ok := s.watching[dir]; !ok {
			s.watcher.Add(dir)
			s.watching[dir] = true
		}
	}

	//http.ServeContent handles caching and range requests
	code = 200
	sf = &fakeFile{File: f}
	http.ServeContent(w, r, info.Name(), info.ModTime(), sf)
}
<commit_msg>fixed imports<commit_after>package server

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/jaschaephraim/lrserver"
	"github.com/jpillora/sizestr"
	"gopkg.in/fsnotify.v1"
)

func init() {
	sizestr.ToggleCase()
}

//Server is custom file server
type Server struct {
	c        *Config
	addr     string
	port     string
	root     string
	hasIndex bool
	fallback *httputil.ReverseProxy
	watcher  *fsnotify.Watcher
	watching map[string]bool
	lr       *lrserver.Server
}

//NewServer creates a new Server
func New(c *Config) (*Server, error) {

	port := strconv.Itoa(c.Port)
	s := &Server{
		c:    c,
		port: port,
		addr: c.Host + ":" + port,
	}

	_, err := os.Stat(c.Dir)
	if c.Dir == "" || err != nil {
		return nil, fmt.Errorf("Missing directory: %s", c.Dir)
	}

	if c.PushState {
		s.root = filepath.Join(c.Dir, "index.html")
		if _, err := os.Stat(s.root); err != nil {
			return nil, fmt.Errorf("'%s' is required for pushstate", s.root)

		}
		s.hasIndex = true
	}

	if c.Fallback != "" {
		u, err := url.Parse(c.Fallback)

		if !strings.HasPrefix(u.Scheme, "http") {
			return nil, fmt.Errorf("Invalid fallback protocol scheme")
		}

		if err != nil {
			return nil, err
		}
		s.fallback = httputil.NewSingleHostReverseProxy(u)
	}

	if c.LiveReload {
		s.lr, _ = lrserver.New("serve-lr", lrserver.DefaultPort)
		discard := log.New(ioutil.Discard, "", 0)
		s.lr.SetErrorLog(discard)
		s.lr.SetStatusLog(discard)
		s.watching = map[string]bool{}
		s.watcher, err = fsnotify.NewWatcher()
		if err != nil {
			return nil, err
		}
	}

	return s, nil
}

func (s *Server) Start() error {

	if s.c.Open {
		go func() {
			time.Sleep(500 * time.Millisecond)
			cmd := exec.Command("open", "http://localhost:"+s.port)
			cmd.Run()
		}()
	}

	if s.c.LiveReload {
		go func() {
			if err := s.lr.ListenAndServe(); err != nil {
				fmt.Printf("LiveReload server closed: %s", err)
			}
		}()
		go func() {
			for {
				event := <-s.watcher.Events
				s.lr.Reload(event.Name)
			}
		}()
	}

	if !s.c.NoLogging {
		fmt.Println(c("serving ", "grey") +
			c(ShortenPath(s.c.Dir), "cyan") +
			c(" on port ", "grey") +
			c(s.port, "cyan"))
	}
	return http.ListenAndServe(s.addr, http.HandlerFunc(s.handle))
}

func (s *Server) handle(w http.ResponseWriter, r *http.Request) {

	path := r.URL.Path

	//reporting
	var sf *fakeFile
	var code int
	reply := func(c int, msg string) {
		w.WriteHeader(c)
		code = c
		if msg != "" {
			w.Write([]byte(msg))
		}
	}
	t0 := time.Now()
	defer func() {
		t := time.Now().Sub(t0)
		size := ""
		if sf != nil {
			size = " " + sizestr.ToString(sf.read)
		}
		if !s.c.NoLogging {
			fmt.Println(c(r.Method+" "+r.URL.Path, "grey") +
				" " + fmtcode(code) + " " +
				c(fmtduration(t)+size, "grey"))
		}
	}()

	//requested file
	p := filepath.Join(s.c.Dir, path)

	//check file or dir
	isdir := false
	if info, err := os.Stat(p); err == nil {
		//found! -> is file or dir?
		isdir = info.IsDir()
	} else if err != nil {
		//missing! -> no-pushstate or has extension?
		if !s.c.PushState || filepath.Ext(p) != "" {
			//not found! handle with proxy?
			if s.fallback != nil {
				s.fallback.ServeHTTP(w, r)
				return
			}
			//not found!!
			reply(404, "Not found")
			return
		}
		p = s.root //known to exist
		isdir = false
	}

	//optionally use index instead of directory list
	if isdir && !s.c.NoIndex {
		dirindex := filepath.Join(p, "index.html")
		if _, err := os.Stat(dirindex); err == nil {
			p = dirindex
			isdir = false
		}
	}

	//directory list
	if isdir {
		if s.c.NoList {
			reply(403, "Listing not allowed")
			return
		}
		files, err := ioutil.ReadDir(p)
		if err != nil {
			reply(500, err.Error())
			return
		}
		buff := &bytes.Buffer{}
		for _, f := range files {
			n := f.Name()
			if f.IsDir() {
				n += "/"
			}
			s := fmt.Sprintf("\n\t%s\n
\n", filepath.Join(path, n), n)
			buff.WriteString(s)
		}
		w.Header().Set("Content-Type", "text/html")
		reply(200, buff.String())
		return
	}

	//check file again
	info, err := os.Stat(p)
	if err != nil {
		reply(404, "Not found")
		return
	}

	//stream file
	f, err := os.Open(p)
	if err != nil {
		reply(500, err.Error())
		return
	}

	//add all served directories to the watcher
	if s.c.LiveReload {
		dir, _ := filepath.Split(p)
		if _, ok := s.watching[dir]; !ok {
			s.watcher.Add(dir)
			s.watching[dir] = true
		}
	}

	//http.ServeContent handles caching and range requests
	code = 200
	sf = &fakeFile{File: f}
	http.ServeContent(w, r, info.Name(), info.ModTime(), sf)
}
<|endoftext|>


<commit_before>package server

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/binary"
	"errors"
	"fmt"
	"github.com/WhiteHatCP/seclab-listener/backend"
	"io"
	"log"
	"net"
	"time"
)

const (
	ReqOpen     = 0xff
	ReqClose    = 0x00
	ReqKeygen   = 0xaa
	RespAllGood = 0xff
)

type Server interface {
	CheckMessage([]byte) error
	DispatchRequest(byte) ([]byte, error)
	Serve(net.Listener)
}

type server struct {
	key     []byte
	maxAge  int
	backend backend.Backend
}

func New(key []byte, maxAge int, backend backend.Backend) Server {
	return &server{
		key:     key,
		maxAge:  maxAge,
		backend: backend,
	}
}

func checkHash(key []byte, payload []byte, hash []byte) bool {
	mac := hmac.New(sha256.New, key)
	mac.Write(payload)
	expected := mac.Sum(nil)
	return hmac.Equal(hash, expected)
}

// Read the status byte, validate HMAC and timestamp
func (s *server) CheckMessage(data []byte) error {
	if !checkHash(s.key, data[:9], data[9:]) {
		return errors.New("Incorrect HMAC signature")
	}
	ts := int64(binary.BigEndian.Uint64(data[1:9]))
	if time.Now().Unix()-ts > int64(s.maxAge) {
		return errors.New("Request expired")
	}
	return nil
}

func (s *server) DispatchRequest(status byte) ([]byte, error) {
	if status == ReqOpen {
		log.Print("Received request: open")
		return []byte{RespAllGood}, s.backend.Open()
	} else if status == ReqClose {
		log.Print("Received request: close")
		return []byte{RespAllGood}, s.backend.Close()
	} else if status == ReqKeygen {
		return nil, fmt.Errorf("Keygen not implemented")
	} else {
		return nil, fmt.Errorf("Unrecognized status byte: 0x%02x", status)
	}
}

func (s *server) handleConnection(conn net.Conn) {
	defer conn.Close()
	data := make([]byte, 41)
	for {
		if _, err := io.ReadFull(conn, data); err != nil {
			if err != io.ErrUnexpectedEOF {
				log.Print(err)
			}
			return
		}
		err := s.CheckMessage(data)
		if err != nil {
			log.Print(err)
			return
		}
		resp, err := s.DispatchRequest(data[0])
		if err != nil {
			log.Print(err)
			return
		}
		conn.Write(resp)
	}
}

func (s *server) Serve(ln net.Listener) {
	for {
		conn, err := ln.Accept()
		if err != nil {
			log.Fatal(err)
		}
		go s.handleConnection(conn)
	}
}
<commit_msg>Properly don't print EOF<commit_after>package server

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/binary"
	"errors"
	"fmt"
	"github.com/WhiteHatCP/seclab-listener/backend"
	"io"
	"log"
	"net"
	"time"
)

const (
	ReqOpen     = 0xff
	ReqClose    = 0x00
	ReqKeygen   = 0xaa
	RespAllGood = 0xff
)

type Server interface {
	CheckMessage([]byte) error
	DispatchRequest(byte) ([]byte, error)
	Serve(net.Listener)
}

type server struct {
	key     []byte
	maxAge  int
	backend backend.Backend
}

func New(key []byte, maxAge int, backend backend.Backend) Server {
	return &server{
		key:     key,
		maxAge:  maxAge,
		backend: backend,
	}
}

func checkHash(key []byte, payload []byte, hash []byte) bool {
	mac := hmac.New(sha256.New, key)
	mac.Write(payload)
	expected := mac.Sum(nil)
	return hmac.Equal(hash, expected)
}

// Read the status byte, validate HMAC and timestamp
func (s *server) CheckMessage(data []byte) error {
	if !checkHash(s.key, data[:9], data[9:]) {
		return errors.New("Incorrect HMAC signature")
	}
	ts := int64(binary.BigEndian.Uint64(data[1:9]))
	if time.Now().Unix()-ts > int64(s.maxAge) {
		return errors.New("Request expired")
	}
	return nil
}

func (s *server) DispatchRequest(status byte) ([]byte, error) {
	if status == ReqOpen {
		log.Print("Received request: open")
		return []byte{RespAllGood}, s.backend.Open()
	} else if status == ReqClose {
		log.Print("Received request: close")
		return []byte{RespAllGood}, s.backend.Close()
	} else if status == ReqKeygen {
		return nil, fmt.Errorf("Keygen not implemented")
	} else {
		return nil, fmt.Errorf("Unrecognized status byte: 0x%02x", status)
	}
}

func (s *server) handleConnection(conn net.Conn) {
	defer conn.Close()
	data := make([]byte, 41)
	for {
		if _, err := io.ReadFull(conn, data); err != nil {
			if err != io.EOF {
				log.Print(err)
			}
			return
		}
		err := s.CheckMessage(data)
		if err != nil {
			log.Print(err)
			return
		}
		resp, err := s.DispatchRequest(data[0])
		if err != nil {
			log.Print(err)
			return
		}
		conn.Write(resp)
	}
}

func (s *server) Serve(ln net.Listener) {
	for {
		conn, err := ln.Accept()
		if err != nil {
			log.Fatal(err)
		}
		go s.handleConnection(conn)
	}
}
<|endoftext|>


<commit_before>package util

import (
	"errors"
	"os"
	"os/user"
	"path/filepath"
	"strings"
	"time"

	"github.com/chrislusf/seaweedfs/weed/glog"
)

func TestFolderWritable(folder string) (err error) {
	fileInfo, err := os.Stat(folder)
	if err != nil {
		return err
	}
	if !fileInfo.IsDir() {
		return errors.New("Not a valid folder!")
	}
	perm := fileInfo.Mode().Perm()
	glog.V(0).Infoln("Folder", folder, "Permission:", perm)
	if 0200&perm != 0 {
		return nil
	}
	return errors.New("Not writable!")
}

func GetFileSize(file *os.File) (size int64, err error) {
	var fi os.FileInfo
	if fi, err = file.Stat(); err == nil {
		size = fi.Size()
	}
	return
}

func FileExists(filename string) bool {

	_, err := os.Stat(filename)
	if os.IsNotExist(err) {
		return false
	}
	return true

}

func CheckFile(filename string) (exists, canRead, canWrite bool, modTime time.Time, fileSize int64) {
	exists = true
	fi, err := os.Stat(filename)
	if os.IsNotExist(err) {
		exists = false
		return
	}
	if err != nil {
		glog.Errorf("check %s: %v", filename, err)
		return
	}
	if fi.Mode()&0400 != 0 {
		canRead = true
	}
	if fi.Mode()&0200 != 0 {
		canWrite = true
	}
	modTime = fi.ModTime()
	fileSize = fi.Size()
	return
}

func ResolvePath(path string) string {

	usr, _ := user.Current()
	dir := usr.HomeDir

	if path == "~" {
		// In case of "~", which won't be caught by the "else if"
		path = dir
	} else if strings.HasPrefix(path, "~/") {
		// Use strings.HasPrefix so we don't match paths like
		// "/something/~/something/"
		path = filepath.Join(dir, path[2:])
	}

	return path
}
<commit_msg>avoid unnecessary user home checking<commit_after>package util

import (
	"errors"
	"os"
	"os/user"
	"path/filepath"
	"strings"
	"time"

	"github.com/chrislusf/seaweedfs/weed/glog"
)

func TestFolderWritable(folder string) (err error) {
	fileInfo, err := os.Stat(folder)
	if err != nil {
		return err
	}
	if !fileInfo.IsDir() {
		return errors.New("Not a valid folder!")
	}
	perm := fileInfo.Mode().Perm()
	glog.V(0).Infoln("Folder", folder, "Permission:", perm)
	if 0200&perm != 0 {
		return nil
	}
	return errors.New("Not writable!")
}

func GetFileSize(file *os.File) (size int64, err error) {
	var fi os.FileInfo
	if fi, err = file.Stat(); err == nil {
		size = fi.Size()
	}
	return
}

func FileExists(filename string) bool {

	_, err := os.Stat(filename)
	if os.IsNotExist(err) {
		return false
	}
	return true

}

func CheckFile(filename string) (exists, canRead, canWrite bool, modTime time.Time, fileSize int64) {
	exists = true
	fi, err := os.Stat(filename)
	if os.IsNotExist(err) {
		exists = false
		return
	}
	if err != nil {
		glog.Errorf("check %s: %v", filename, err)
		return
	}
	if fi.Mode()&0400 != 0 {
		canRead = true
	}
	if fi.Mode()&0200 != 0 {
		canWrite = true
	}
	modTime = fi.ModTime()
	fileSize = fi.Size()
	return
}

func ResolvePath(path string) string {

	if !strings.Contains(path, "~") {
		return path
	}

	usr, _ := user.Current()
	dir := usr.HomeDir

	if path == "~" {
		// In case of "~", which won't be caught by the "else if"
		path = dir
	} else if strings.HasPrefix(path, "~/") {
		// Use strings.HasPrefix so we don't match paths like
		// "/something/~/something/"
		path = filepath.Join(dir, path[2:])
	}

	return path
}
<|endoftext|>


<commit_before>package utils

import (
	"fmt"
	"os"
)

// GetEnvWithDefault

func ExampleGetEnvWithDefault_notSet() {
	os.Clearenv()
	fmt.Println(GetEnvWithDefault("SHH_TEST_ENV", "bar"))
	// Output: bar
}

func ExampleGetEnvWithDefault_empty() {
	os.Setenv("SHH_TEST_ENV", "")
	fmt.Println(GetEnvWithDefault("SHH_TEST_ENV", "bar"))
	// Output:
}

func ExampleGetEnvWithDefault_notDefault() {
	os.Setenv("SHH_TEST_ENV", "foo")
	fmt.Println(GetEnvWithDefault("SHH_TEST_ENV", "bar"))
	// Output: foo
}

func ExampleGetEnvWithDefault_default() {
	os.Setenv("SHH_TEST_ENV", "bar")
	fmt.Println(GetEnvWithDefault("SHH_TEST_ENV", "bar"))
	// Output: bar
}

// GetEnvWithDefaultInt

func ExampleGetEnvWithDefaultInt_notSet() {
	os.Clearenv()
	fmt.Println(GetEnvWithDefaultInt("SHH_TEST_ENV", 42))
	// Output: 42
}

func ExampleGetEnvWithDefaultInt_empty() {
	os.Setenv("SHH_TEST_ENV", "")
	fmt.Println(GetEnvWithDefaultInt("SHH_TEST_ENV", 42))
	// Output: 42
}

func ExampleGetEnvWithDefaultInt_notDefault() {
	os.Setenv("SHH_TEST_ENV", "7")
	fmt.Println(GetEnvWithDefaultInt("SHH_TEST_ENV", 42))
	// Output: 7
}

func ExampleGetEnvWithDefaultInt_default() {
	os.Setenv("SHH_TEST_ENV", "42")
	fmt.Println(GetEnvWithDefaultInt("SHH_TEST_ENV", 42))
	// Output: 42
}

// GetEnvWithDefaultDuration

func ExampleGetEnvWithDefaultDuration_notSet() {
	os.Clearenv()
	fmt.Println(GetEnvWithDefaultDuration("SHH_TEST_ENV", "42s"))
	// Output: 42s
}

func ExampleGetEnvWithDefaultDuration_empty() {
	os.Setenv("SHH_TEST_ENV", "")
	fmt.Println(GetEnvWithDefaultDuration("SHH_TEST_ENV", "42s"))
	// Output: 42s
}

func ExampleGetEnvWithDefaultDuration_notDefault() {
	os.Setenv("SHH_TEST_ENV", "7s")
	fmt.Println(GetEnvWithDefaultDuration("SHH_TEST_ENV", "42s"))
	// Output: 7s
}

func ExampleGetEnvWithDefaultDuration_default() {
	os.Setenv("SHH_TEST_ENV", "42s")
	fmt.Println(GetEnvWithDefaultDuration("SHH_TEST_ENV", "42s"))
	// Output: 42s
}

// GetEnvWithDefaultStrings

func ExampleGetEnvWithDefaultStrings_empty() {
	os.Setenv("SHH_TEST_ENV", "")
	fmt.Println(len(GetEnvWithDefaultStrings("SHH_TEST_ENV", "")))
	// Output: 0
}

func ExampleGetEnvWithDefaultStrings_single() {
	os.Setenv("SHH_TEST_ENV", "foo")
	v := GetEnvWithDefaultStrings("SHH_TEST_ENV", "")
	fmt.Println(len(v))
	fmt.Println(v[0])
	// Output: 1
	// foo
}

func ExampleGetEnvWithDefaultStrings_multiple() {
	os.Setenv("SHH_TEST_ENV", "foo,bar")
	v := GetEnvWithDefaultStrings("SHH_TEST_ENV", "")
	fmt.Println(len(v))
	fmt.Println(v[0])
	fmt.Println(v[1])
	// Output: 2
	// bar
	// foo
}
<commit_msg>This test was actually wrong<commit_after>package utils

import (
	"fmt"
	"os"
)

// GetEnvWithDefault

func ExampleGetEnvWithDefault_notSet() {
	os.Clearenv()
	fmt.Println(GetEnvWithDefault("SHH_TEST_ENV", "bar"))
	// Output: bar
}

func ExampleGetEnvWithDefault_empty() {
	os.Setenv("SHH_TEST_ENV", "")
	fmt.Println(GetEnvWithDefault("SHH_TEST_ENV", "bar"))
	// Output:
	// bar
}

func ExampleGetEnvWithDefault_notDefault() {
	os.Setenv("SHH_TEST_ENV", "foo")
	fmt.Println(GetEnvWithDefault("SHH_TEST_ENV", "bar"))
	// Output: foo
}

func ExampleGetEnvWithDefault_default() {
	os.Setenv("SHH_TEST_ENV", "bar")
	fmt.Println(GetEnvWithDefault("SHH_TEST_ENV", "bar"))
	// Output: bar
}

// GetEnvWithDefaultInt

func ExampleGetEnvWithDefaultInt_notSet() {
	os.Clearenv()
	fmt.Println(GetEnvWithDefaultInt("SHH_TEST_ENV", 42))
	// Output: 42
}

func ExampleGetEnvWithDefaultInt_empty() {
	os.Setenv("SHH_TEST_ENV", "")
	fmt.Println(GetEnvWithDefaultInt("SHH_TEST_ENV", 42))
	// Output: 42
}

func ExampleGetEnvWithDefaultInt_notDefault() {
	os.Setenv("SHH_TEST_ENV", "7")
	fmt.Println(GetEnvWithDefaultInt("SHH_TEST_ENV", 42))
	// Output: 7
}

func ExampleGetEnvWithDefaultInt_default() {
	os.Setenv("SHH_TEST_ENV", "42")
	fmt.Println(GetEnvWithDefaultInt("SHH_TEST_ENV", 42))
	// Output: 42
}

// GetEnvWithDefaultDuration

func ExampleGetEnvWithDefaultDuration_notSet() {
	os.Clearenv()
	fmt.Println(GetEnvWithDefaultDuration("SHH_TEST_ENV", "42s"))
	// Output: 42s
}

func ExampleGetEnvWithDefaultDuration_empty() {
	os.Setenv("SHH_TEST_ENV", "")
	fmt.Println(GetEnvWithDefaultDuration("SHH_TEST_ENV", "42s"))
	// Output: 42s
}

func ExampleGetEnvWithDefaultDuration_notDefault() {
	os.Setenv("SHH_TEST_ENV", "7s")
	fmt.Println(GetEnvWithDefaultDuration("SHH_TEST_ENV", "42s"))
	// Output: 7s
}

func ExampleGetEnvWithDefaultDuration_default() {
	os.Setenv("SHH_TEST_ENV", "42s")
	fmt.Println(GetEnvWithDefaultDuration("SHH_TEST_ENV", "42s"))
	// Output: 42s
}

// GetEnvWithDefaultStrings

func ExampleGetEnvWithDefaultStrings_empty() {
	os.Setenv("SHH_TEST_ENV", "")
	fmt.Println(len(GetEnvWithDefaultStrings("SHH_TEST_ENV", "")))
	// Output: 0
}

func ExampleGetEnvWithDefaultStrings_single() {
	os.Setenv("SHH_TEST_ENV", "foo")
	v := GetEnvWithDefaultStrings("SHH_TEST_ENV", "")
	fmt.Println(len(v))
	fmt.Println(v[0])
	// Output: 1
	// foo
}

func ExampleGetEnvWithDefaultStrings_multiple() {
	os.Setenv("SHH_TEST_ENV", "foo,bar")
	v := GetEnvWithDefaultStrings("SHH_TEST_ENV", "")
	fmt.Println(len(v))
	fmt.Println(v[0])
	fmt.Println(v[1])
	// Output: 2
	// bar
	// foo
}
<|endoftext|>


<commit_before>// Based on https://github.com/martini-contrib/staticbin
package staticbin

import (
	"bytes"
	"log"
	"net/http"
	"path"
	"path/filepath"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
)

type Options struct {
	// SkipLogging will disable [Static] log messages when a static file is served.
	SkipLogging bool
	// IndexFile defines which file to serve as index if it exists.
	IndexFile string
	// Path prefix
	Dir string
}

func (o *Options) init() {
	if o.IndexFile == "" {
		o.IndexFile = "index.html"
	}
}

// Static returns a middleware handler that serves static files in the given directory.
func Static(asset func(string) ([]byte, error), options ...Options) gin.HandlerFunc {
	if asset == nil {
		panic("asset is nil")
	}

	opt := Options{}
	for _, o := range options {
		opt = o
		break
	}
	opt.init()

	modtime := time.Now()

	return func(c *gin.Context) {
		if c.Request.Method != "GET" && c.Request.Method != "HEAD" {
			// Request is not correct. Go farther.
			return
		}

		url := c.Request.URL.Path
		file := strings.TrimPrefix(opt.Dir+url, string(filepath.Separator))
		b, err := asset(file)

		if err != nil {
			// Try to serve the index file.
			b, err = asset(path.Join(file, opt.IndexFile))

			if err != nil {
				// Go farther if the asset could not be found.
				return
			}
		}

		if !opt.SkipLogging {
			log.Println("[Static] Serving " + url)
		}

		http.ServeContent(c.Writer, c.Request, url, modtime, bytes.NewReader(b))
		c.Abort()
	}
}
<commit_msg>restore c.Abort signature<commit_after>// Based on https://github.com/martini-contrib/staticbin
package staticbin

import (
	"bytes"
	"log"
	"net/http"
	"path"
	"path/filepath"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
)

type Options struct {
	// SkipLogging will disable [Static] log messages when a static file is served.
	SkipLogging bool
	// IndexFile defines which file to serve as index if it exists.
	IndexFile string
	// Path prefix
	Dir string
}

func (o *Options) init() {
	if o.IndexFile == "" {
		o.IndexFile = "index.html"
	}
}

// Static returns a middleware handler that serves static files in the given directory.
func Static(asset func(string) ([]byte, error), options ...Options) gin.HandlerFunc {
	if asset == nil {
		panic("asset is nil")
	}

	opt := Options{}
	for _, o := range options {
		opt = o
		break
	}
	opt.init()

	modtime := time.Now()

	return func(c *gin.Context) {
		if c.Request.Method != "GET" && c.Request.Method != "HEAD" {
			// Request is not correct. Go farther.
			return
		}

		url := c.Request.URL.Path
		file := strings.TrimPrefix(opt.Dir+url, string(filepath.Separator))
		b, err := asset(file)

		if err != nil {
			// Try to serve the index file.
			b, err = asset(path.Join(file, opt.IndexFile))

			if err != nil {
				// Go farther if the asset could not be found.
				return
			}
		}

		if !opt.SkipLogging {
			log.Println("[Static] Serving " + url)
		}

		http.ServeContent(c.Writer, c.Request, url, modtime, bytes.NewReader(b))
		// TODO: fix me
		c.Abort(-1)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"github.com/abaril/GoLights/api"
	"net/http"
	"time"
	"math"
	"log"
)

type status struct {
	Version   string           `json:"version"`
	IsAlive   bool             `json:"is_alive"`
	IsHome    bool             `json:"is_home"`
	NextAlarm *time.Time       `json:"next_alarm,omitempty"`
	Weather   *WeatherForecast `json:"forecast,omitempty"`
	DeviceStatus *map[string]DeviceStatusReport `json:"device_status,omitempty"`
}

var db api.MemDB

func InitStatusAPI(dbVal api.MemDB) http.HandlerFunc {
	db = dbVal
	db.Set("IsHome", false)
	return serveStatus
}

func serveStatus(w http.ResponseWriter, r *http.Request) {

	switch r.Method {
	case "GET":
		s := status{
			Version: VERSION_STRING,
			IsAlive: true,
		}
		raw, err := db.Get("IsHome")
		if err != nil {
			http.Error(w, http.StatusText(500), 500)
			return
		}
		s.IsHome = raw.(bool)

		raw, err = db.Get("NextAlarm")
		if err == nil {
			time := raw.(time.Time)
			s.NextAlarm = &time
		}

		raw, err = db.Get("Weather")
		if err == nil {
			weather := raw.(WeatherForecast)
			s.Weather = &weather
		}

		raw, err = db.Get("DeviceStatus")
		if err == nil {
			deviceStatus := raw.(map[string]DeviceStatusReport)
			for _, status := range deviceStatus {
				delta := math.Abs(time.Since(status.Time.Time).Minutes())
				log.Println("Time since = ", delta, " ", time.Since(status.Time.Time))
				status.Alive = delta <= 5;
			}
			s.DeviceStatus = &deviceStatus
		}

		json.NewEncoder(w).Encode(s)
		return
	}

	http.Error(w, http.StatusText(404), 404)
}
<commit_msg>@abaril: Copying status to augment. Probably a better way to do this.<commit_after>package main

import (
	"encoding/json"
	"github.com/abaril/GoLights/api"
	"net/http"
	"time"
	"math"
	"log"
)

type status struct {
	Version   string           `json:"version"`
	IsAlive   bool             `json:"is_alive"`
	IsHome    bool             `json:"is_home"`
	NextAlarm *time.Time       `json:"next_alarm,omitempty"`
	Weather   *WeatherForecast `json:"forecast,omitempty"`
	DeviceStatus *map[string]DeviceStatusReport `json:"device_status,omitempty"`
}

var db api.MemDB

func InitStatusAPI(dbVal api.MemDB) http.HandlerFunc {
	db = dbVal
	db.Set("IsHome", false)
	return serveStatus
}

func serveStatus(w http.ResponseWriter, r *http.Request) {

	switch r.Method {
	case "GET":
		s := status{
			Version: VERSION_STRING,
			IsAlive: true,
		}
		raw, err := db.Get("IsHome")
		if err != nil {
			http.Error(w, http.StatusText(500), 500)
			return
		}
		s.IsHome = raw.(bool)

		raw, err = db.Get("NextAlarm")
		if err == nil {
			time := raw.(time.Time)
			s.NextAlarm = &time
		}

		raw, err = db.Get("Weather")
		if err == nil {
			weather := raw.(WeatherForecast)
			s.Weather = &weather
		}

		raw, err = db.Get("DeviceStatus")
		if err == nil {
			originalStatus := raw.(map[string]DeviceStatusReport)
			newStatus := make(map[string]DeviceStatusReport)
			for name, status := range originalStatus {
				delta := math.Abs(time.Since(status.Time.Time).Minutes())
				status.Alive = delta <= 5
				newStatus[name] = status
				log.Println("Time since = ", delta, " ", time.Since(status.Time.Time), " ", status.Alive)
			}
			s.DeviceStatus = &newStatus
		}

		json.NewEncoder(w).Encode(s)
		return
	}

	http.Error(w, http.StatusText(404), 404)
}
<|endoftext|>


<commit_before>package mesh

import (
	"fmt"
	"net"
)

// Status is our current state as a peer, as taken from a router.
type Status struct {
	Name string

	protocol           string
	protocolMinVersion int
	protocolMaxVersion int
	encryption         bool
	peerDiscovery      bool
	nickName           string
	port               int
	peers              []PeerStatus
	unicastRoutes      []unicastRouteStatus
	broadcastRoutes    []broadcastRouteStatus
	connections        []LocalConnectionStatus
	targets            []string
	overlayDiagnostics interface{}
	trustedSubnets     []string
}

// NewStatus returns a Status object, taken as a snapshot from the router.
func NewStatus(router *Router) *Status {
	return &Status{
		Name:               router.Ourself.Name.String(),
		protocol:           Protocol,
		protocolMinVersion: ProtocolMinVersion,
		protocolMaxVersion: ProtocolMaxVersion,
		encryption:         router.usingPassword(),
		peerDiscovery:      router.PeerDiscovery,
		nickName:           router.Ourself.NickName,
		port:               router.Port,
		peers:              makePeerStatusSlice(router.Peers),
		unicastRoutes:      makeUnicastRouteStatusSlice(router.Routes),
		broadcastRoutes:    makeBroadcastRouteStatusSlice(router.Routes),
		connections:        makeLocalConnectionStatusSlice(router.ConnectionMaker),
		targets:            makeTargetSlice(router.ConnectionMaker),
		overlayDiagnostics: router.Overlay.Diagnostics(),
		trustedSubnets:     makeTrustedSubnetsSlice(router.TrustedSubnets),
	}
}

// PeerStatus is the current state of a peer in the mesh.
type PeerStatus struct {
	Name        string
	NickName    string
	UID         PeerUID
	ShortID     PeerShortID
	Version     uint64
	Connections []connectionStatus
}

// makePeerStatusSlice takes a snapshot of the state of peers.
func makePeerStatusSlice(peers *Peers) []PeerStatus {
	var slice []PeerStatus

	peers.forEach(func(peer *Peer) {
		var connections []connectionStatus
		if peer == peers.ourself.Peer {
			for conn := range peers.ourself.getConnections() {
				connections = append(connections, makeConnectionStatus(conn))
			}
		} else {
			// Modifying peer.connections requires a write lock on
			// Peers, and since we are holding a read lock (due to the
			// ForEach), access without locking the peer is safe.
			for _, conn := range peer.connections {
				connections = append(connections, makeConnectionStatus(conn))
			}
		}
		slice = append(slice, PeerStatus{
			peer.Name.String(),
			peer.NickName,
			peer.UID,
			peer.ShortID,
			peer.Version,
			connections,
		})
	})

	return slice
}

type connectionStatus struct {
	Name        string
	NickName    string
	Address     string
	Outbound    bool
	Established bool
}

func makeConnectionStatus(c Connection) connectionStatus {
	return connectionStatus{
		Name:        c.Remote().Name.String(),
		NickName:    c.Remote().NickName,
		Address:     c.remoteTCPAddress(),
		Outbound:    c.isOutbound(),
		Established: c.isEstablished(),
	}
}

// unicastRouteStatus is the current state of an established unicast route.
type unicastRouteStatus struct {
	Dest, Via string
}

// makeUnicastRouteStatusSlice takes a snapshot of the unicast routes in routes.
func makeUnicastRouteStatusSlice(r *routes) []unicastRouteStatus {
	r.RLock()
	defer r.RUnlock()

	var slice []unicastRouteStatus
	for dest, via := range r.unicast {
		slice = append(slice, unicastRouteStatus{dest.String(), via.String()})
	}
	return slice
}

// BroadcastRouteStatus is the current state of an established broadcast route.
type broadcastRouteStatus struct {
	Source string
	Via    []string
}

// makeBroadcastRouteStatusSlice takes a snapshot of the broadcast routes in routes.
func makeBroadcastRouteStatusSlice(r *routes) []broadcastRouteStatus {
	r.RLock()
	defer r.RUnlock()

	var slice []broadcastRouteStatus
	for source, via := range r.broadcast {
		var hops []string
		for _, hop := range via {
			hops = append(hops, hop.String())
		}
		slice = append(slice, broadcastRouteStatus{source.String(), hops})
	}
	return slice
}

// LocalConnectionStatus is the current state of a physical connection to a peer.
type LocalConnectionStatus struct {
	Address  string
	Outbound bool
	State    string
	Info     string
}

// makeLocalConnectionStatusSlice takes a snapshot of the active local
// connections in the ConnectionMaker.
func makeLocalConnectionStatusSlice(cm *connectionMaker) []LocalConnectionStatus {
	resultChan := make(chan []LocalConnectionStatus, 0)
	cm.actionChan <- func() bool {
		var slice []LocalConnectionStatus
		for conn := range cm.connections {
			state := "pending"
			if conn.isEstablished() {
				state = "established"
			}
			lc, _ := conn.(*LocalConnection)
			info := fmt.Sprintf("%-6v %v", lc.OverlayConn.DisplayName(), conn.Remote())
			if lc.router.usingPassword() {
				if lc.untrusted() {
					info = fmt.Sprintf("%-11v %v", "encrypted", info)
				} else {
					info = fmt.Sprintf("%-11v %v", "unencrypted", info)
				}
			}
			slice = append(slice, LocalConnectionStatus{conn.remoteTCPAddress(), conn.isOutbound(), state, info})
		}
		for address, target := range cm.targets {
			add := func(state, info string) {
				slice = append(slice, LocalConnectionStatus{address, true, state, info})
			}
			switch target.state {
			case targetWaiting:
				until := "never"
				if !target.tryAfter.IsZero() {
					until = target.tryAfter.String()
				}
				if target.lastError == nil { // shouldn't happen
					add("waiting", "until: "+until)
				} else {
					add("failed", target.lastError.Error()+", retry: "+until)
				}
			case targetAttempting:
				if target.lastError == nil {
					add("connecting", "")
				} else {
					add("retrying", target.lastError.Error())
				}
			case targetConnected:
			}
		}
		resultChan <- slice
		return false
	}
	return <-resultChan
}

// makeTargetSlice takes a snapshot of the active targets (direct peers) in the ConnectionMaker.
func makeTargetSlice(cm *connectionMaker) []string {
	resultChan := make(chan []string, 0)
	cm.actionChan <- func() bool {
		var slice []string
		for peer := range cm.directPeers {
			slice = append(slice, peer)
		}
		resultChan <- slice
		return false
	}
	return <-resultChan
}

// makeTrustedSubnetsSlice makes a human-readable copy of the trustedSubnets.
func makeTrustedSubnetsSlice(trustedSubnets []*net.IPNet) []string {
	trustedSubnetStrs := []string{}
	for _, trustedSubnet := range trustedSubnets {
		trustedSubnetStrs = append(trustedSubnetStrs, trustedSubnet.String())
	}
	return trustedSubnetStrs
}
<commit_msg>Status needs its fields exported<commit_after>package mesh

import (
	"fmt"
	"net"
)

// Status is our current state as a peer, as taken from a router.
// This is designed to be used as diagnostic information.
type Status struct {
	Protocol           string
	ProtocolMinVersion int
	ProtocolMaxVersion int
	Encryption         bool
	PeerDiscovery      bool
	Name               string
	NickName           string
	Port               int
	Peers              []PeerStatus
	UnicastRoutes      []unicastRouteStatus
	BroadcastRoutes    []broadcastRouteStatus
	Connections        []LocalConnectionStatus
	Targets            []string
	OverlayDiagnostics interface{}
	TrustedSubnets     []string
}

// NewStatus returns a Status object, taken as a snapshot from the router.
func NewStatus(router *Router) *Status {
	return &Status{
		Protocol:           Protocol,
		ProtocolMinVersion: ProtocolMinVersion,
		ProtocolMaxVersion: ProtocolMaxVersion,
		Encryption:         router.usingPassword(),
		PeerDiscovery:      router.PeerDiscovery,
		Name:               router.Ourself.Name.String(),
		NickName:           router.Ourself.NickName,
		Port:               router.Port,
		Peers:              makePeerStatusSlice(router.Peers),
		UnicastRoutes:      makeUnicastRouteStatusSlice(router.Routes),
		BroadcastRoutes:    makeBroadcastRouteStatusSlice(router.Routes),
		Connections:        makeLocalConnectionStatusSlice(router.ConnectionMaker),
		Targets:            makeTargetSlice(router.ConnectionMaker),
		OverlayDiagnostics: router.Overlay.Diagnostics(),
		TrustedSubnets:     makeTrustedSubnetsSlice(router.TrustedSubnets),
	}
}

// PeerStatus is the current state of a peer in the mesh.
type PeerStatus struct {
	Name        string
	NickName    string
	UID         PeerUID
	ShortID     PeerShortID
	Version     uint64
	Connections []connectionStatus
}

// makePeerStatusSlice takes a snapshot of the state of peers.
func makePeerStatusSlice(peers *Peers) []PeerStatus {
	var slice []PeerStatus

	peers.forEach(func(peer *Peer) {
		var connections []connectionStatus
		if peer == peers.ourself.Peer {
			for conn := range peers.ourself.getConnections() {
				connections = append(connections, makeConnectionStatus(conn))
			}
		} else {
			// Modifying peer.connections requires a write lock on
			// Peers, and since we are holding a read lock (due to the
			// ForEach), access without locking the peer is safe.
			for _, conn := range peer.connections {
				connections = append(connections, makeConnectionStatus(conn))
			}
		}
		slice = append(slice, PeerStatus{
			peer.Name.String(),
			peer.NickName,
			peer.UID,
			peer.ShortID,
			peer.Version,
			connections,
		})
	})

	return slice
}

type connectionStatus struct {
	Name        string
	NickName    string
	Address     string
	Outbound    bool
	Established bool
}

func makeConnectionStatus(c Connection) connectionStatus {
	return connectionStatus{
		Name:        c.Remote().Name.String(),
		NickName:    c.Remote().NickName,
		Address:     c.remoteTCPAddress(),
		Outbound:    c.isOutbound(),
		Established: c.isEstablished(),
	}
}

// unicastRouteStatus is the current state of an established unicast route.
type unicastRouteStatus struct {
	Dest, Via string
}

// makeUnicastRouteStatusSlice takes a snapshot of the unicast routes in routes.
func makeUnicastRouteStatusSlice(r *routes) []unicastRouteStatus {
	r.RLock()
	defer r.RUnlock()

	var slice []unicastRouteStatus
	for dest, via := range r.unicast {
		slice = append(slice, unicastRouteStatus{dest.String(), via.String()})
	}
	return slice
}

// BroadcastRouteStatus is the current state of an established broadcast route.
type broadcastRouteStatus struct {
	Source string
	Via    []string
}

// makeBroadcastRouteStatusSlice takes a snapshot of the broadcast routes in routes.
func makeBroadcastRouteStatusSlice(r *routes) []broadcastRouteStatus {
	r.RLock()
	defer r.RUnlock()

	var slice []broadcastRouteStatus
	for source, via := range r.broadcast {
		var hops []string
		for _, hop := range via {
			hops = append(hops, hop.String())
		}
		slice = append(slice, broadcastRouteStatus{source.String(), hops})
	}
	return slice
}

// LocalConnectionStatus is the current state of a physical connection to a peer.
type LocalConnectionStatus struct {
	Address  string
	Outbound bool
	State    string
	Info     string
}

// makeLocalConnectionStatusSlice takes a snapshot of the active local
// connections in the ConnectionMaker.
func makeLocalConnectionStatusSlice(cm *connectionMaker) []LocalConnectionStatus {
	resultChan := make(chan []LocalConnectionStatus, 0)
	cm.actionChan <- func() bool {
		var slice []LocalConnectionStatus
		for conn := range cm.connections {
			state := "pending"
			if conn.isEstablished() {
				state = "established"
			}
			lc, _ := conn.(*LocalConnection)
			info := fmt.Sprintf("%-6v %v", lc.OverlayConn.DisplayName(), conn.Remote())
			if lc.router.usingPassword() {
				if lc.untrusted() {
					info = fmt.Sprintf("%-11v %v", "encrypted", info)
				} else {
					info = fmt.Sprintf("%-11v %v", "unencrypted", info)
				}
			}
			slice = append(slice, LocalConnectionStatus{conn.remoteTCPAddress(), conn.isOutbound(), state, info})
		}
		for address, target := range cm.targets {
			add := func(state, info string) {
				slice = append(slice, LocalConnectionStatus{address, true, state, info})
			}
			switch target.state {
			case targetWaiting:
				until := "never"
				if !target.tryAfter.IsZero() {
					until = target.tryAfter.String()
				}
				if target.lastError == nil { // shouldn't happen
					add("waiting", "until: "+until)
				} else {
					add("failed", target.lastError.Error()+", retry: "+until)
				}
			case targetAttempting:
				if target.lastError == nil {
					add("connecting", "")
				} else {
					add("retrying", target.lastError.Error())
				}
			case targetConnected:
			}
		}
		resultChan <- slice
		return false
	}
	return <-resultChan
}

// makeTargetSlice takes a snapshot of the active targets (direct peers) in the ConnectionMaker.
func makeTargetSlice(cm *connectionMaker) []string {
	resultChan := make(chan []string, 0)
	cm.actionChan <- func() bool {
		var slice []string
		for peer := range cm.directPeers {
			slice = append(slice, peer)
		}
		resultChan <- slice
		return false
	}
	return <-resultChan
}

// makeTrustedSubnetsSlice makes a human-readable copy of the trustedSubnets.
func makeTrustedSubnetsSlice(trustedSubnets []*net.IPNet) []string {
	trustedSubnetStrs := []string{}
	for _, trustedSubnet := range trustedSubnets {
		trustedSubnetStrs = append(trustedSubnetStrs, trustedSubnet.String())
	}
	return trustedSubnetStrs
}
<|endoftext|>