<commit_before>// Copyright 2010 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package net

import (
	"errors"
	"time"
)

// A Dialer contains options for connecting to an address.
//
// The zero value for each field is equivalent to dialing
// without that option. Dialing with the zero value of Dialer
// is therefore equivalent to just calling the Dial function.
type Dialer struct {
	// Timeout is the maximum amount of time a dial will wait for
	// a connect to complete. If Deadline is also set, it may fail
	// earlier.
	//
	// The default is no timeout.
	//
	// With or without a timeout, the operating system may impose
	// its own earlier timeout. For instance, TCP timeouts are
	// often around 3 minutes.
	Timeout time.Duration

	// Deadline is the absolute point in time after which dials
	// will fail. If Timeout is set, it may fail earlier.
	// Zero means no deadline, or dependent on the operating system
	// as with the Timeout option.
	Deadline time.Time

	// LocalAddr is the local address to use when dialing an
	// address. The address must be of a compatible type for the
	// network being dialed.
	// If nil, a local address is automatically chosen.
	LocalAddr Addr

	// DualStack allows a single dial to attempt to establish
	// multiple IPv4 and IPv6 connections and to return the first
	// established connection when the network is "tcp" and the
	// destination is a host name that has multiple address family
	// DNS records.
	DualStack bool
}

// Return either now+Timeout or Deadline, whichever comes first.
// Or zero, if neither is set.
func (d *Dialer) deadline() time.Time {
	if d.Timeout == 0 {
		return d.Deadline
	}
	timeoutDeadline := time.Now().Add(d.Timeout)
	if d.Deadline.IsZero() || timeoutDeadline.Before(d.Deadline) {
		return timeoutDeadline
	} else {
		return d.Deadline
	}
}

func parseNetwork(net string) (afnet string, proto int, err error) {
	i := last(net, ':')
	if i < 0 { // no colon
		switch net {
		case "tcp", "tcp4", "tcp6":
		case "udp", "udp4", "udp6":
		case "ip", "ip4", "ip6":
		case "unix", "unixgram", "unixpacket":
		default:
			return "", 0, UnknownNetworkError(net)
		}
		return net, 0, nil
	}
	afnet = net[:i]
	switch afnet {
	case "ip", "ip4", "ip6":
		protostr := net[i+1:]
		proto, i, ok := dtoi(protostr, 0)
		if !ok || i != len(protostr) {
			proto, err = lookupProtocol(protostr)
			if err != nil {
				return "", 0, err
			}
		}
		return afnet, proto, nil
	}
	return "", 0, UnknownNetworkError(net)
}

func resolveAddr(op, net, addr string, deadline time.Time) (netaddr, error) {
	afnet, _, err := parseNetwork(net)
	if err != nil {
		return nil, err
	}
	if op == "dial" && addr == "" {
		return nil, errMissingAddress
	}
	switch afnet {
	case "unix", "unixgram", "unixpacket":
		return ResolveUnixAddr(afnet, addr)
	}
	return resolveInternetAddr(afnet, addr, deadline)
}

// Dial connects to the address on the named network.
//
// Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only),
// "udp", "udp4" (IPv4-only), "udp6" (IPv6-only), "ip", "ip4"
// (IPv4-only), "ip6" (IPv6-only), "unix", "unixgram" and
// "unixpacket".
//
// For TCP and UDP networks, addresses have the form host:port.
// If host is a literal IPv6 address or host name, it must be enclosed
// in square brackets as in "[::1]:80", "[ipv6-host]:http" or
// "[ipv6-host%zone]:80".
// The functions JoinHostPort and SplitHostPort manipulate addresses
// in this form.
//
// Examples:
//	Dial("tcp", "12.34.56.78:80")
//	Dial("tcp", "google.com:http")
//	Dial("tcp", "[2001:db8::1]:http")
//	Dial("tcp", "[fe80::1%lo0]:80")
//
// For IP networks, the network must be "ip", "ip4" or "ip6" followed
// by a colon and a protocol number or name and the addr must be a
// literal IP address.
//
// Examples:
//	Dial("ip4:1", "127.0.0.1")
//	Dial("ip6:ospf", "::1")
//
// For Unix networks, the address must be a file system path.
func Dial(network, address string) (Conn, error) {
	var d Dialer
	return d.Dial(network, address)
}

// DialTimeout acts like Dial but takes a timeout.
// The timeout includes name resolution, if required.
func DialTimeout(network, address string, timeout time.Duration) (Conn, error) {
	d := Dialer{Timeout: timeout}
	return d.Dial(network, address)
}

// Dial connects to the address on the named network.
//
// See func Dial for a description of the network and address
// parameters.
func (d *Dialer) Dial(network, address string) (Conn, error) {
	ra, err := resolveAddr("dial", network, address, d.deadline())
	if err != nil {
		return nil, &OpError{Op: "dial", Net: network, Addr: nil, Err: err}
	}
	dialer := func(deadline time.Time) (Conn, error) {
		return dialSingle(network, address, d.LocalAddr, ra.toAddr(), deadline)
	}
	if ras, ok := ra.(addrList); ok && d.DualStack && network == "tcp" {
		dialer = func(deadline time.Time) (Conn, error) {
			return dialMulti(network, address, d.LocalAddr, ras, deadline)
		}
	}
	return dial(network, ra.toAddr(), dialer, d.deadline())
}

// dialMulti attempts to establish connections to each destination of
// the list of addresses. It will return the first established
// connection and close the other connections. Otherwise it returns
// error on the last attempt.
func dialMulti(net, addr string, la Addr, ras addrList, deadline time.Time) (Conn, error) {
	type racer struct {
		Conn
		Addr
		error
	}
	// Sig controls the flow of dial results on lane. It passes a
	// token to the next racer and also indicates the end of flow
	// by using closed channel.
	sig := make(chan bool, 1)
	lane := make(chan racer, 1)
	for _, ra := range ras {
		go func(ra Addr) {
			c, err := dialSingle(net, addr, la, ra, deadline)
			if _, ok := <-sig; ok {
				lane <- racer{c, ra, err}
			} else if err == nil {
				// We have to return the resources
				// that belong to the other
				// connections here for avoiding
				// unnecessary resource starvation.
				c.Close()
			}
		}(ra.toAddr())
	}
	defer close(sig)
	var failAddr Addr
	lastErr := errTimeout
	nracers := len(ras)
	for nracers > 0 {
		sig <- true
		select {
		case racer := <-lane:
			if racer.error == nil {
				return racer.Conn, nil
			}
			failAddr = racer.Addr
			lastErr = racer.error
			nracers--
		}
	}
	return nil, &OpError{Op: "dial", Net: net, Addr: failAddr, Err: lastErr}
}

// dialSingle attempts to establish and returns a single connection to
// the destination address.
func dialSingle(net, addr string, la, ra Addr, deadline time.Time) (c Conn, err error) {
	if la != nil && la.Network() != ra.Network() {
		return nil, &OpError{Op: "dial", Net: net, Addr: ra, Err: errors.New("mismatched local address type " + la.Network())}
	}
	switch ra := ra.(type) {
	case *TCPAddr:
		la, _ := la.(*TCPAddr)
		c, err = dialTCP(net, la, ra, deadline)
	case *UDPAddr:
		la, _ := la.(*UDPAddr)
		c, err = dialUDP(net, la, ra, deadline)
	case *IPAddr:
		la, _ := la.(*IPAddr)
		c, err = dialIP(net, la, ra, deadline)
	case *UnixAddr:
		la, _ := la.(*UnixAddr)
		c, err = dialUnix(net, la, ra, deadline)
	default:
		return nil, &OpError{Op: "dial", Net: net, Addr: ra, Err: &AddrError{Err: "unexpected address type", Addr: addr}}
	}
	if err != nil {
		return nil, err // c is non-nil interface containing nil pointer
	}
	return c, nil
}

// Listen announces on the local network address laddr.
// The network net must be a stream-oriented network: "tcp", "tcp4",
// "tcp6", "unix" or "unixpacket".
// See Dial for the syntax of laddr.
func Listen(net, laddr string) (Listener, error) {
	la, err := resolveAddr("listen", net, laddr, noDeadline)
	if err != nil {
		return nil, &OpError{Op: "listen", Net: net, Addr: nil, Err: err}
	}
	var l Listener
	switch la := la.toAddr().(type) {
	case *TCPAddr:
		l, err = ListenTCP(net, la)
	case *UnixAddr:
		l, err = ListenUnix(net, la)
	default:
		return nil, &OpError{Op: "listen", Net: net, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: laddr}}
	}
	if err != nil {
		return nil, err // l is non-nil interface containing nil pointer
	}
	return l, nil
}

// ListenPacket announces on the local network address laddr.
// The network net must be a packet-oriented network: "udp", "udp4",
// "udp6", "ip", "ip4", "ip6" or "unixgram".
// See Dial for the syntax of laddr.
func ListenPacket(net, laddr string) (PacketConn, error) {
	la, err := resolveAddr("listen", net, laddr, noDeadline)
	if err != nil {
		return nil, &OpError{Op: "listen", Net: net, Addr: nil, Err: err}
	}
	var l PacketConn
	switch la := la.toAddr().(type) {
	case *UDPAddr:
		l, err = ListenUDP(net, la)
	case *IPAddr:
		l, err = ListenIP(net, la)
	case *UnixAddr:
		l, err = ListenUnixgram(net, la)
	default:
		return nil, &OpError{Op: "listen", Net: net, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: laddr}}
	}
	if err != nil {
		return nil, err // l is non-nil interface containing nil pointer
	}
	return l, nil
}
<commit_msg>net: don't return a nested error when happy eyeballs dialing<commit_after>// Copyright 2010 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package net

import (
	"errors"
	"time"
)

// A Dialer contains options for connecting to an address.
//
// The zero value for each field is equivalent to dialing
// without that option. Dialing with the zero value of Dialer
// is therefore equivalent to just calling the Dial function.
type Dialer struct {
	// Timeout is the maximum amount of time a dial will wait for
	// a connect to complete. If Deadline is also set, it may fail
	// earlier.
	//
	// The default is no timeout.
	//
	// With or without a timeout, the operating system may impose
	// its own earlier timeout. For instance, TCP timeouts are
	// often around 3 minutes.
	Timeout time.Duration

	// Deadline is the absolute point in time after which dials
	// will fail. If Timeout is set, it may fail earlier.
	// Zero means no deadline, or dependent on the operating system
	// as with the Timeout option.
	Deadline time.Time

	// LocalAddr is the local address to use when dialing an
	// address. The address must be of a compatible type for the
	// network being dialed.
	// If nil, a local address is automatically chosen.
	LocalAddr Addr

	// DualStack allows a single dial to attempt to establish
	// multiple IPv4 and IPv6 connections and to return the first
	// established connection when the network is "tcp" and the
	// destination is a host name that has multiple address family
	// DNS records.
	DualStack bool
}

// Return either now+Timeout or Deadline, whichever comes first.
// Or zero, if neither is set.
func (d *Dialer) deadline() time.Time {
	if d.Timeout == 0 {
		return d.Deadline
	}
	timeoutDeadline := time.Now().Add(d.Timeout)
	if d.Deadline.IsZero() || timeoutDeadline.Before(d.Deadline) {
		return timeoutDeadline
	} else {
		return d.Deadline
	}
}

func parseNetwork(net string) (afnet string, proto int, err error) {
	i := last(net, ':')
	if i < 0 { // no colon
		switch net {
		case "tcp", "tcp4", "tcp6":
		case "udp", "udp4", "udp6":
		case "ip", "ip4", "ip6":
		case "unix", "unixgram", "unixpacket":
		default:
			return "", 0, UnknownNetworkError(net)
		}
		return net, 0, nil
	}
	afnet = net[:i]
	switch afnet {
	case "ip", "ip4", "ip6":
		protostr := net[i+1:]
		proto, i, ok := dtoi(protostr, 0)
		if !ok || i != len(protostr) {
			proto, err = lookupProtocol(protostr)
			if err != nil {
				return "", 0, err
			}
		}
		return afnet, proto, nil
	}
	return "", 0, UnknownNetworkError(net)
}

func resolveAddr(op, net, addr string, deadline time.Time) (netaddr, error) {
	afnet, _, err := parseNetwork(net)
	if err != nil {
		return nil, err
	}
	if op == "dial" && addr == "" {
		return nil, errMissingAddress
	}
	switch afnet {
	case "unix", "unixgram", "unixpacket":
		return ResolveUnixAddr(afnet, addr)
	}
	return resolveInternetAddr(afnet, addr, deadline)
}

// Dial connects to the address on the named network.
//
// Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only),
// "udp", "udp4" (IPv4-only), "udp6" (IPv6-only), "ip", "ip4"
// (IPv4-only), "ip6" (IPv6-only), "unix", "unixgram" and
// "unixpacket".
//
// For TCP and UDP networks, addresses have the form host:port.
// If host is a literal IPv6 address or host name, it must be enclosed
// in square brackets as in "[::1]:80", "[ipv6-host]:http" or
// "[ipv6-host%zone]:80".
// The functions JoinHostPort and SplitHostPort manipulate addresses
// in this form.
//
// Examples:
//	Dial("tcp", "12.34.56.78:80")
//	Dial("tcp", "google.com:http")
//	Dial("tcp", "[2001:db8::1]:http")
//	Dial("tcp", "[fe80::1%lo0]:80")
//
// For IP networks, the network must be "ip", "ip4" or "ip6" followed
// by a colon and a protocol number or name and the addr must be a
// literal IP address.
//
// Examples:
//	Dial("ip4:1", "127.0.0.1")
//	Dial("ip6:ospf", "::1")
//
// For Unix networks, the address must be a file system path.
func Dial(network, address string) (Conn, error) {
	var d Dialer
	return d.Dial(network, address)
}

// DialTimeout acts like Dial but takes a timeout.
// The timeout includes name resolution, if required.
func DialTimeout(network, address string, timeout time.Duration) (Conn, error) {
	d := Dialer{Timeout: timeout}
	return d.Dial(network, address)
}

// Dial connects to the address on the named network.
//
// See func Dial for a description of the network and address
// parameters.
func (d *Dialer) Dial(network, address string) (Conn, error) {
	ra, err := resolveAddr("dial", network, address, d.deadline())
	if err != nil {
		return nil, &OpError{Op: "dial", Net: network, Addr: nil, Err: err}
	}
	dialer := func(deadline time.Time) (Conn, error) {
		return dialSingle(network, address, d.LocalAddr, ra.toAddr(), deadline)
	}
	if ras, ok := ra.(addrList); ok && d.DualStack && network == "tcp" {
		dialer = func(deadline time.Time) (Conn, error) {
			return dialMulti(network, address, d.LocalAddr, ras, deadline)
		}
	}
	return dial(network, ra.toAddr(), dialer, d.deadline())
}

// dialMulti attempts to establish connections to each destination of
// the list of addresses. It will return the first established
// connection and close the other connections. Otherwise it returns
// error on the last attempt.
func dialMulti(net, addr string, la Addr, ras addrList, deadline time.Time) (Conn, error) {
	type racer struct {
		Conn
		error
	}
	// Sig controls the flow of dial results on lane. It passes a
	// token to the next racer and also indicates the end of flow
	// by using closed channel.
	sig := make(chan bool, 1)
	lane := make(chan racer, 1)
	for _, ra := range ras {
		go func(ra Addr) {
			c, err := dialSingle(net, addr, la, ra, deadline)
			if _, ok := <-sig; ok {
				lane <- racer{c, err}
			} else if err == nil {
				// We have to return the resources
				// that belong to the other
				// connections here for avoiding
				// unnecessary resource starvation.
				c.Close()
			}
		}(ra.toAddr())
	}
	defer close(sig)
	lastErr := errTimeout
	nracers := len(ras)
	for nracers > 0 {
		sig <- true
		select {
		case racer := <-lane:
			if racer.error == nil {
				return racer.Conn, nil
			}
			lastErr = racer.error
			nracers--
		}
	}
	return nil, lastErr
}

// dialSingle attempts to establish and returns a single connection to
// the destination address.
func dialSingle(net, addr string, la, ra Addr, deadline time.Time) (c Conn, err error) {
	if la != nil && la.Network() != ra.Network() {
		return nil, &OpError{Op: "dial", Net: net, Addr: ra, Err: errors.New("mismatched local address type " + la.Network())}
	}
	switch ra := ra.(type) {
	case *TCPAddr:
		la, _ := la.(*TCPAddr)
		c, err = dialTCP(net, la, ra, deadline)
	case *UDPAddr:
		la, _ := la.(*UDPAddr)
		c, err = dialUDP(net, la, ra, deadline)
	case *IPAddr:
		la, _ := la.(*IPAddr)
		c, err = dialIP(net, la, ra, deadline)
	case *UnixAddr:
		la, _ := la.(*UnixAddr)
		c, err = dialUnix(net, la, ra, deadline)
	default:
		return nil, &OpError{Op: "dial", Net: net, Addr: ra, Err: &AddrError{Err: "unexpected address type", Addr: addr}}
	}
	if err != nil {
		return nil, err // c is non-nil interface containing nil pointer
	}
	return c, nil
}

// Listen announces on the local network address laddr.
// The network net must be a stream-oriented network: "tcp", "tcp4",
// "tcp6", "unix" or "unixpacket".
// See Dial for the syntax of laddr.
func Listen(net, laddr string) (Listener, error) {
	la, err := resolveAddr("listen", net, laddr, noDeadline)
	if err != nil {
		return nil, &OpError{Op: "listen", Net: net, Addr: nil, Err: err}
	}
	var l Listener
	switch la := la.toAddr().(type) {
	case *TCPAddr:
		l, err = ListenTCP(net, la)
	case *UnixAddr:
		l, err = ListenUnix(net, la)
	default:
		return nil, &OpError{Op: "listen", Net: net, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: laddr}}
	}
	if err != nil {
		return nil, err // l is non-nil interface containing nil pointer
	}
	return l, nil
}

// ListenPacket announces on the local network address laddr.
// The network net must be a packet-oriented network: "udp", "udp4",
// "udp6", "ip", "ip4", "ip6" or "unixgram".
// See Dial for the syntax of laddr.
func ListenPacket(net, laddr string) (PacketConn, error) {
	la, err := resolveAddr("listen", net, laddr, noDeadline)
	if err != nil {
		return nil, &OpError{Op: "listen", Net: net, Addr: nil, Err: err}
	}
	var l PacketConn
	switch la := la.toAddr().(type) {
	case *UDPAddr:
		l, err = ListenUDP(net, la)
	case *IPAddr:
		l, err = ListenIP(net, la)
	case *UnixAddr:
		l, err = ListenUnixgram(net, la)
	default:
		return nil, &OpError{Op: "listen", Net: net, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: laddr}}
	}
	if err != nil {
		return nil, err // l is non-nil interface containing nil pointer
	}
	return l, nil
}
<|endoftext|>


<commit_before>package driver

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
)

const (
	bufSize = 32 * 1024 // Max number of bytes read from a buffer
)

// LogRotator ingests data and writes out to a rotated set of files
type LogRotator struct {
	maxFiles int    // maximum number of rotated files retained by the log rotator
	fileSize int64  // maximum file size of a rotated file
	path     string // path where the rotated files are created
	fileName string // base file name of the rotated files

	logFileIdx int // index to the current file

	logger *log.Logger
}

// NewLogRotator configures and returns a new LogRotator
func NewLogRotator(path string, fileName string, maxFiles int, fileSize int64, logger *log.Logger) (*LogRotator, error) {
	files, err := ioutil.ReadDir(path)
	if err != nil {
		return nil, err
	}

	// finding out the log file with the largest index
	logFileIdx := 0
	for _, f := range files {
		if strings.HasPrefix(f.Name(), fileName) {
			fileIdx := strings.TrimPrefix(f.Name(), fmt.Sprintf("%s.", fileName))
			n, err := strconv.Atoi(fileIdx)
			if err != nil {
				continue
			}
			if n > logFileIdx {
				logFileIdx = n
			}
		}
	}

	return &LogRotator{
		maxFiles:   maxFiles,
		fileSize:   fileSize,
		path:       path,
		fileName:   fileName,
		logFileIdx: logFileIdx,
		logger:     logger,
	}, nil
}

// Start reads from a Reader and writes them to files and rotates them when the
// size of the file becomes equal to the max size configured
func (l *LogRotator) Start(r io.Reader) error {
	buf := make([]byte, bufSize)
	for {
		logFileName := filepath.Join(l.path, fmt.Sprintf("%s.%d", l.fileName, l.logFileIdx))
		remainingSize := l.fileSize
		if f, err := os.Stat(logFileName); err == nil {
			// skipping the current file if it happens to be a directory
			if f.IsDir() {
				l.logFileIdx += 1
				continue
			}
			// calculating the remaining capacity of the log file
			remainingSize = l.fileSize - f.Size()
		}
		f, err := os.OpenFile(logFileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			return err
		}
		l.logger.Println("[INFO] client.logrotator: opened a new file: %s", logFileName)
		// closing the current log file if it doesn't have any more capacity
		if remainingSize < 1 {
			l.logFileIdx = l.logFileIdx + 1
			f.Close()
			continue
		}

		// reading from the reader and writing into the current log file as long
		// as it has capacity or the reader closes
		for {
			var nr int
			var err error
			if remainingSize < bufSize {
				nr, err = r.Read(buf[0:remainingSize])
			} else {
				nr, err = r.Read(buf)
			}
			if err != nil {
				f.Close()
				return err
			}
			nw, err := f.Write(buf[:nr])
			if err != nil {
				f.Close()
				return err
			}
			if nr != nw {
				f.Close()
				return fmt.Errorf("failed to write data read from the reader into file, R: %d W: %d", nr, nw)
			}
			remainingSize -= int64(nr)
			if remainingSize < 1 {
				f.Close()
				break
			}
		}
		l.logFileIdx = l.logFileIdx + 1
	}
	return nil
}

// PurgeOldFiles removes older files and keeps only the last N files rotated for
// a file
func (l *LogRotator) PurgeOldFiles() {
	fIndexes := make([]int, l.maxFiles)
	files, err := ioutil.ReadDir(l.path)
	if err != nil {
		return
	}
	count := 0
	for _, f := range files {
		if strings.HasPrefix(f.Name(), l.fileName) {
			fileIdx := strings.TrimPrefix(f.Name(), fmt.Sprintf("%s.", l.fileName))
			n, err := strconv.Atoi(fileIdx)
			if err != nil {
				continue
			}
			if count == l.maxFiles {
				sort.Sort(sort.Reverse(sort.IntSlice(fIndexes)))
				fname := filepath.Join(l.path, fmt.Sprintf("%s.%d", l.fileName, fIndexes[count-1]))
				l.logger.Printf("[DEBUG] client.logrator: removing file: %v", fname)
				os.RemoveAll(fname)
				count -= 1
			}
			fIndexes[count] = n
			count += 1
		}
	}
}
<commit_msg>Changing the logic of purging old rotated files<commit_after>package driver

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
)

const (
	bufSize = 32 * 1024 // Max number of bytes read from a buffer
)

// LogRotator ingests data and writes out to a rotated set of files
type LogRotator struct {
	maxFiles int    // maximum number of rotated files retained by the log rotator
	fileSize int64  // maximum file size of a rotated file
	path     string // path where the rotated files are created
	fileName string // base file name of the rotated files

	logFileIdx int // index to the current file

	logger *log.Logger
}

// NewLogRotator configures and returns a new LogRotator
func NewLogRotator(path string, fileName string, maxFiles int, fileSize int64, logger *log.Logger) (*LogRotator, error) {
	files, err := ioutil.ReadDir(path)
	if err != nil {
		return nil, err
	}

	// finding out the log file with the largest index
	logFileIdx := 0
	for _, f := range files {
		if strings.HasPrefix(f.Name(), fileName) {
			fileIdx := strings.TrimPrefix(f.Name(), fmt.Sprintf("%s.", fileName))
			n, err := strconv.Atoi(fileIdx)
			if err != nil {
				continue
			}
			if n > logFileIdx {
				logFileIdx = n
			}
		}
	}

	return &LogRotator{
		maxFiles:   maxFiles,
		fileSize:   fileSize,
		path:       path,
		fileName:   fileName,
		logFileIdx: logFileIdx,
		logger:     logger,
	}, nil
}

// Start reads from a Reader and writes them to files and rotates them when the
// size of the file becomes equal to the max size configured
func (l *LogRotator) Start(r io.Reader) error {
	buf := make([]byte, bufSize)
	for {
		logFileName := filepath.Join(l.path, fmt.Sprintf("%s.%d", l.fileName, l.logFileIdx))
		remainingSize := l.fileSize
		if f, err := os.Stat(logFileName); err == nil {
			// skipping the current file if it happens to be a directory
			if f.IsDir() {
				l.logFileIdx += 1
				continue
			}
			// calculating the remaining capacity of the log file
			remainingSize = l.fileSize - f.Size()
		}
		f, err := os.OpenFile(logFileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			return err
		}
		l.logger.Printf("[DEBUG] client.logrotator: opened a new file: %s", logFileName)
		// closing the current log file if it doesn't have any more capacity
		if remainingSize < 1 {
			l.logFileIdx = l.logFileIdx + 1
			f.Close()
			continue
		}

		// reading from the reader and writing into the current log file as long
		// as it has capacity or the reader closes
		for {
			var nr int
			var err error
			if remainingSize < bufSize {
				nr, err = r.Read(buf[0:remainingSize])
			} else {
				nr, err = r.Read(buf)
			}
			if err != nil {
				f.Close()
				return err
			}
			nw, err := f.Write(buf[:nr])
			if err != nil {
				f.Close()
				return err
			}
			if nr != nw {
				f.Close()
				return fmt.Errorf("failed to write data read from the reader into file, R: %d W: %d", nr, nw)
			}
			remainingSize -= int64(nr)
			if remainingSize < 1 {
				f.Close()
				break
			}
		}
		l.logFileIdx = l.logFileIdx + 1
	}
	return nil
}

// PurgeOldFiles removes older files and keeps only the last N files rotated for
// a file
func (l *LogRotator) PurgeOldFiles() {
	var fIndexes []int
	files, err := ioutil.ReadDir(l.path)
	if err != nil {
		return
	}
	// Inserting all the rotated files in a slice
	for _, f := range files {
		if strings.HasPrefix(f.Name(), l.fileName) {
			fileIdx := strings.TrimPrefix(f.Name(), fmt.Sprintf("%s.", l.fileName))
			n, err := strconv.Atoi(fileIdx)
			if err != nil {
				continue
			}
			fIndexes = append(fIndexes, n)
		}
	}

	// sorting the file indexes so that we can purge the older files and keep
	// only the number of files as configured by the user
	sort.Sort(sort.IntSlice(fIndexes))
	toDelete := fIndexes[l.maxFiles-1 : len(fIndexes)-1]
	for _, fIndex := range toDelete {
		fname := filepath.Join(l.path, fmt.Sprintf("%s.%d", l.fileName, fIndex))
		os.RemoveAll(fname)
	}
}
<|endoftext|>


<commit_before>package http

import (
	"encoding/json"
	"fmt"
	"github.com/RangelReale/osin"
	"github.com/go-errors/errors"
	"github.com/ory-am/common/pkg"
	. "github.com/ory-am/hydra/client"
	"github.com/ory-am/hydra/middleware"
	"github.com/parnurzeal/gorequest"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/clientcredentials"
	"net/http"
	"net/url"
)

var isAllowed struct {
	Allowed bool `json:"allowed"`
}

type HTTPClient struct {
	ep           string
	clientToken  *oauth2.Token
	clientConfig *clientcredentials.Config
}

func New(endpoint, client, secret string) *HTTPClient {
	return &HTTPClient{
		ep: endpoint,
		clientConfig: &clientcredentials.Config{
			ClientSecret: secret,
			ClientID:     client,
			TokenURL:     pkg.JoinURL(endpoint, "oauth2/token"),
		},
		clientToken: &oauth2.Token{},
	}
}

func (c *HTTPClient) SetClientToken(token *oauth2.Token) {
	c.clientToken = token
}

func (c *HTTPClient) IsRequestAllowed(req *http.Request, resource, permission, owner string) (bool, error) {
	var token *osin.BearerAuth
	if token = osin.CheckBearerAuth(req); token == nil {
		return false, errors.New("No token given.")
	} else if token.Code == "" {
		return false, errors.New("No token given.")
	}
	env := middleware.NewEnv(req)
	env.Owner(owner)
	return c.IsAllowed(&AuthorizeRequest{Token: token.Code, Resource: resource, Permission: permission, Context: env.Ctx()})
}

func (c *HTTPClient) IsAllowed(ar *AuthorizeRequest) (bool, error) {
	return isValidAuthorizeRequest(c, ar, true)
}

func (c *HTTPClient) IsAuthenticated(token string) (bool, error) {
	return isValidAuthenticationRequest(c, token, true)
}

func isValidAuthenticationRequest(c *HTTPClient, token string, retry bool) (bool, error) {
	data := url.Values{}
	data.Set("token", token)
	request := gorequest.New()
	resp, body, errs := request.Post(pkg.JoinURL(c.ep, "/oauth2/introspect")).Type("form").SetBasicAuth(c.clientConfig.ClientID, c.clientConfig.ClientSecret).SendString(data.Encode()).End()
	if len(errs) > 0 {
		return false, errors.Errorf("Got errors: %v", errs)
	} else if resp.StatusCode != http.StatusOK {
		return false, errors.Errorf("Status code %d is not 200: %s", resp.StatusCode, body)
	}

	if retry && resp.StatusCode == http.StatusUnauthorized {
		var err error
		if c.clientToken, err = c.clientConfig.Token(oauth2.NoContext); err != nil {
			return false, errors.New(err)
		} else if c.clientToken == nil {
			return false, errors.New("Access token could not be retrieved")
		}
		return isValidAuthenticationRequest(c, token, false)
	} else if resp.StatusCode != http.StatusOK {
		return false, fmt.Errorf("Status code %d is not 200", resp.StatusCode)
	}

	var introspect struct {
		Active bool `json:"active"`
	}

	if err := json.Unmarshal([]byte(body), &introspect); err != nil {
		return false, err
	} else if !introspect.Active {
		return false, errors.New("Authentication denied")
	}
	return introspect.Active, nil
}

func isValidAuthorizeRequest(c *HTTPClient, ar *AuthorizeRequest, retry bool) (bool, error) {
	request := gorequest.New()
	resp, body, errs := request.Post(pkg.JoinURL(c.ep, "/guard/allowed")).SetBasicAuth(c.clientConfig.ClientID, c.clientConfig.ClientSecret).Set("Content-Type", "application/json").Send(*ar).End()
	if len(errs) > 0 {
		return false, errors.Errorf("Got errors: %v", errs)
	} else if retry && resp.StatusCode == http.StatusUnauthorized {
		var err error
		if c.clientToken, err = c.clientConfig.Token(oauth2.NoContext); err != nil {
			return false, errors.New(err)
		} else if c.clientToken == nil {
			return false, errors.New("Access token could not be retrieved")
		}
		return isValidAuthorizeRequest(c, ar, false)
	} else if resp.StatusCode != http.StatusOK {
		return false, errors.Errorf("Status code %d is not 200: %s", resp.StatusCode, body)
	}

	if err := json.Unmarshal([]byte(body), &isAllowed); err != nil {
		return false, errors.Errorf("Could not unmarshall body because %s", err.Error())
	}

	if !isAllowed.Allowed {
		return false, errors.New("Authroization denied")
	}
	return isAllowed.Allowed, nil
}
<commit_msg>client: resolve too many open files issue<commit_after>package http

import (
	"encoding/json"
	"fmt"
	"github.com/RangelReale/osin"
	"github.com/go-errors/errors"
	"github.com/ory-am/common/pkg"
	. "github.com/ory-am/hydra/client"
	"github.com/ory-am/hydra/middleware"
	"github.com/parnurzeal/gorequest"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/clientcredentials"
	"net/http"
	"net/url"
)

var isAllowed struct {
	Allowed bool `json:"allowed"`
}

type HTTPClient struct {
	ep           string
	clientToken  *oauth2.Token
	clientConfig *clientcredentials.Config
}

func New(endpoint, client, secret string) *HTTPClient {
	return &HTTPClient{
		ep: endpoint,
		clientConfig: &clientcredentials.Config{
			ClientSecret: secret,
			ClientID:     client,
			TokenURL:     pkg.JoinURL(endpoint, "oauth2/token"),
		},
		clientToken: &oauth2.Token{},
	}
}

func (c *HTTPClient) SetClientToken(token *oauth2.Token) {
	c.clientToken = token
}

func (c *HTTPClient) IsRequestAllowed(req *http.Request, resource, permission, owner string) (bool, error) {
	var token *osin.BearerAuth
	if token = osin.CheckBearerAuth(req); token == nil {
		return false, errors.New("No token given.")
	} else if token.Code == "" {
		return false, errors.New("No token given.")
	}
	env := middleware.NewEnv(req)
	env.Owner(owner)
	return c.IsAllowed(&AuthorizeRequest{Token: token.Code, Resource: resource, Permission: permission, Context: env.Ctx()})
}

func (c *HTTPClient) IsAllowed(ar *AuthorizeRequest) (bool, error) {
	return isValidAuthorizeRequest(c, ar, true)
}

func (c *HTTPClient) IsAuthenticated(token string) (bool, error) {
	return isValidAuthenticationRequest(c, token, true)
}

func isValidAuthenticationRequest(c *HTTPClient, token string, retry bool) (bool, error) {
	data := url.Values{}
	data.Set("token", token)
	request := gorequest.New()
	resp, body, errs := request.Post(pkg.JoinURL(c.ep, "/oauth2/introspect")).Type("form").SetBasicAuth(c.clientConfig.ClientID, c.clientConfig.ClientSecret).Set("Connection", "close").SendString(data.Encode()).End()
	if len(errs) > 0 {
		return false, errors.Errorf("Got errors: %v", errs)
	} else if resp.StatusCode != http.StatusOK {
		return false, errors.Errorf("Status code %d is not 200: %s", resp.StatusCode, body)
	}

	if retry && resp.StatusCode == http.StatusUnauthorized {
		var err error
		if c.clientToken, err = c.clientConfig.Token(oauth2.NoContext); err != nil {
			return false, errors.New(err)
		} else if c.clientToken == nil {
			return false, errors.New("Access token could not be retrieved")
		}
		return isValidAuthenticationRequest(c, token, false)
	} else if resp.StatusCode != http.StatusOK {
		return false, fmt.Errorf("Status code %d is not 200", resp.StatusCode)
	}

	var introspect struct {
		Active bool `json:"active"`
	}

	if err := json.Unmarshal([]byte(body), &introspect); err != nil {
		return false, err
	} else if !introspect.Active {
		return false, errors.New("Authentication denied")
	}
	return introspect.Active, nil
}

func isValidAuthorizeRequest(c *HTTPClient, ar *AuthorizeRequest, retry bool) (bool, error) {
	request := gorequest.New()
	resp, body, errs := request.Post(pkg.JoinURL(c.ep, "/guard/allowed")).SetBasicAuth(c.clientConfig.ClientID, c.clientConfig.ClientSecret).Set("Content-Type", "application/json").Set("Connection", "close").Send(*ar).End()
	if len(errs) > 0 {
		return false, errors.Errorf("Got errors: %v", errs)
	} else if retry && resp.StatusCode == http.StatusUnauthorized {
		var err error
		if c.clientToken, err = c.clientConfig.Token(oauth2.NoContext); err != nil {
			return false, errors.New(err)
		} else if c.clientToken == nil {
			return false, errors.New("Access token could not be retrieved")
		}
		return isValidAuthorizeRequest(c, ar, false)
	} else if resp.StatusCode != http.StatusOK {
		return false, errors.Errorf("Status code %d is not 200: %s", resp.StatusCode, body)
	}

	if err := json.Unmarshal([]byte(body), &isAllowed); err != nil {
		return false, errors.Errorf("Could not unmarshall body because %s", err.Error())
	}

	if !isAllowed.Allowed {
		return false, errors.New("Authroization denied")
	}
	return isAllowed.Allowed, nil
}
<|endoftext|>


<commit_before>/*
The MIT License (MIT)

Copyright (c) 2016 winlin

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

/*
 This the main entrance of https-proxy, proxy to api or other http server.
*/
package main

import (
	"crypto/tls"
	"flag"
	"fmt"
	"github.com/ossrs/go-oryx-lib/https"
	"net/http"
	"os"
	"path"
	"strings"
	"sync"
)

const server = "Oryx/0.0.1"

func main() {
	var httpPort, httpsPort int
	var httpsDomains, html, cacheFile string
	var useLetsEncrypt bool
	var ssCert, ssKey string
	flag.IntVar(&httpPort, "http", 80, "http listen at. 0 to disable http.")
	flag.IntVar(&httpsPort, "https", 443, "https listen at. 0 to disable https. 443 to serve. ")
	flag.StringVar(&httpsDomains, "domains", "", "the allow domains, empty to allow all. for example: ossrs.net,www.ossrs.net")
	flag.StringVar(&html, "root", "./html", "the www web root. support relative dir to argv[0].")
	flag.StringVar(&cacheFile, "cache", "./letsencrypt.cache", "the cache for https. support relative dir to argv[0].")
	flag.BoolVar(&useLetsEncrypt, "lets", true, "whether use letsencrypt CA. self sign if not.")
	flag.StringVar(&ssKey, "ssk", "server.key", "self-sign key, user can build it: openssl genrsa -out server.key 2048")
	flag.StringVar(&ssCert, "ssc", "server.crt", "self-sign cert, user can build it: openssl req -new -x509 -key server.key -out server.crt -days 365")
	flag.Parse()

	if httpsPort != 0 && httpsPort != 443 {
		fmt.Println("https must be 0(disabled) or 443(enabled)")
		os.Exit(-1)
	}
	if httpPort == 0 && httpsPort == 0 {
		fmt.Println("http or https are disabled")
		os.Exit(-1)
	}

	if !path.IsAbs(cacheFile) && path.IsAbs(os.Args[0]) {
		cacheFile = path.Join(path.Dir(os.Args[0]), cacheFile)
	}
	if !path.IsAbs(html) && path.IsAbs(os.Args[0]) {
		html = path.Join(path.Dir(os.Args[0]), html)
	}

	fh := http.FileServer(http.Dir(html))
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Server", server)

		if o := r.Header.Get("Origin"); len(o) > 0 {
			w.Header().Set("Access-Control-Allow-Origin", "*")
			w.Header().Set("Access-Control-Allow-Methods", "GET, POST, HEAD, PUT, DELETE, OPTIONS")
			w.Header().Set("Access-Control-Expose-Headers", "Server,range,Content-Length,Content-Range")
			w.Header().Set("Access-Control-Allow-Headers", "origin,range,accept-encoding,referer,Cache-Control,X-Proxy-Authorization,X-Requested-With,Content-Type")
		}

		fh.ServeHTTP(w, r)
	})

	var protos []string
	if httpPort != 0 {
		protos = append(protos, fmt.Sprintf("http(:%v)", httpPort))
	}
	if httpsPort != 0 {
		s := httpsDomains
		if httpsDomains == "" {
			s = "all domains"
		}
		protos = append(protos, fmt.Sprintf("https(:%v, %v, %v)", httpsPort, s, cacheFile))

		if useLetsEncrypt {
			protos = append(protos, "letsencrypt")
		} else {
			protos = append(protos, fmt.Sprintf("self-sign(%v, %v)", ssKey, ssCert))
		}
	}
	fmt.Println(fmt.Sprintf("%v html root at %v", strings.Join(protos, ", "), string(html)))

	wg := sync.WaitGroup{}
	go func() {
		defer wg.Done()

		if httpPort == 0 {
			return
		}

		if err := http.ListenAndServe(fmt.Sprintf(":%v", httpPort), nil); err != nil {
			panic(err)
		}
		fmt.Println("http server ok.")
	}()
	wg.Add(1)

	go func() {
		defer wg.Done()

		if httpsPort == 0 {
			return
		}

		var err error
		var m https.Manager

		if useLetsEncrypt {
			var domains []string
			if httpsDomains != "" {
				domains = strings.Split(httpsDomains, ",")
			}

			if m, err = https.NewLetsencryptManager("", domains, cacheFile); err != nil {
				panic(err)
			}
		} else {
			if m, err = https.NewSelfSignManager(ssCert, ssKey); err != nil {
				panic(err)
			}
		}

		svr := &http.Server{
			Addr: fmt.Sprintf(":%v", httpsPort),
			TLSConfig: &tls.Config{
				GetCertificate: m.GetCertificate,
			},
		}

		if err := svr.ListenAndServeTLS("", ""); err != nil {
			panic(err)
		}
		fmt.Println("https server ok.")
	}()
	wg.Add(1)

	wg.Wait()
}
<commit_msg>Support proxy to backend api<commit_after>/*
The MIT License (MIT)

Copyright (c) 2016 winlin

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

/*
 This the main entrance of https-proxy, proxy to api or other http server.
*/
package main

import (
	"crypto/tls"
	"flag"
	"fmt"
	"github.com/ossrs/go-oryx-lib/https"
	"net/http"
	"os"
	"path"
	"strings"
	"sync"
	"net/url"
	"net/http/httputil"
	"net"
)

const server = "Oryx/0.0.1"

func main() {
	var err error
	var httpPort, httpsPort int
	var httpsDomains, html, cacheFile string
	var useLetsEncrypt bool
	var ssCert, ssKey string
	var oproxy string
	flag.IntVar(&httpPort, "http", 80, "http listen at. 0 to disable http.")
	flag.IntVar(&httpsPort, "https", 443, "https listen at. 0 to disable https. 443 to serve. ")
	flag.StringVar(&httpsDomains, "domains", "", "the allow domains, empty to allow all. for example: ossrs.net,www.ossrs.net")
	flag.StringVar(&html, "root", "./html", "the www web root. support relative dir to argv[0].")
	flag.StringVar(&cacheFile, "cache", "./letsencrypt.cache", "the cache for https. support relative dir to argv[0].")
	flag.BoolVar(&useLetsEncrypt, "lets", true, "whether use letsencrypt CA. self sign if not.")
	flag.StringVar(&ssKey, "ssk", "server.key", "self-sign key, user can build it: openssl genrsa -out server.key 2048")
	flag.StringVar(&ssCert, "ssc", "server.crt", "self-sign cert, user can build it: openssl req -new -x509 -key server.key -out server.crt -days 365")
	flag.StringVar(&oproxy, "proxy", "", "proxy to backend api, for example, -proxy http://127.0.0.1:8888/api/webrtc")
	flag.Parse()

	if httpsPort != 0 && httpsPort != 443 {
		fmt.Println("https must be 0(disabled) or 443(enabled)")
		os.Exit(-1)
	}
	if httpPort == 0 && httpsPort == 0 {
		fmt.Println("http or https are disabled")
		os.Exit(-1)
	}

	var proxyUrl *url.URL
	var proxy *httputil.ReverseProxy
	if oproxy != "" {
		if proxyUrl,err = url.Parse(oproxy); err != nil {
			fmt.Println("proxy is not legal url, proxy is", oproxy)
			os.Exit(-1)
		}

		proxy = &httputil.ReverseProxy{
			Director: func(r *http.Request) {
				r.URL.Scheme = proxyUrl.Scheme
				r.URL.Host = proxyUrl.Host
				if ip, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
					r.Header.Set("X-Real-IP", ip)
				}
				//fmt.Println(fmt.Sprintf("proxy http %v to %v", r.RemoteAddr, r.URL.String()))
			},
		}
	}

	if !path.IsAbs(cacheFile) && path.IsAbs(os.Args[0]) {
		cacheFile = path.Join(path.Dir(os.Args[0]), cacheFile)
	}
	if !path.IsAbs(html) && path.IsAbs(os.Args[0]) {
		html = path.Join(path.Dir(os.Args[0]), html)
	}

	fh := http.FileServer(http.Dir(html))
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Server", server)

		if o := r.Header.Get("Origin"); len(o) > 0 {
			w.Header().Set("Access-Control-Allow-Origin", "*")
			w.Header().Set("Access-Control-Allow-Methods", "GET, POST, HEAD, PUT, DELETE, OPTIONS")
			w.Header().Set("Access-Control-Expose-Headers", "Server,range,Content-Length,Content-Range")
			w.Header().Set("Access-Control-Allow-Headers", "origin,range,accept-encoding,referer,Cache-Control,X-Proxy-Authorization,X-Requested-With,Content-Type")
		}

		if proxyUrl != nil && strings.HasPrefix(r.URL.Path, proxyUrl.Path) {
			// For matched OPTIONS, directly return without response.
			if r.Method == "OPTIONS" {
				return
			}

			proxy.ServeHTTP(w, r)
			return
		}

		fh.ServeHTTP(w, r)
	})

	var protos []string
	if httpPort != 0 {
		protos = append(protos, fmt.Sprintf("http(:%v)", httpPort))
	}
	if httpsPort != 0 {
		s := httpsDomains
		if httpsDomains == "" {
			s = "all domains"
		}
		protos = append(protos, fmt.Sprintf("https(:%v, %v, %v)", httpsPort, s, cacheFile))

		if useLetsEncrypt {
			protos = append(protos, "letsencrypt")
		} else {
			protos = append(protos, fmt.Sprintf("self-sign(%v, %v)", ssKey, ssCert))
		}
	}
	fmt.Println(fmt.Sprintf("%v html root at %v", strings.Join(protos, ", "), string(html)))

	wg := sync.WaitGroup{}
	go func() {
		defer wg.Done()

		if httpPort == 0 {
			return
		}

		if err := http.ListenAndServe(fmt.Sprintf(":%v", httpPort), nil); err != nil {
			panic(err)
		}
		fmt.Println("http server ok.")
	}()
	wg.Add(1)

	go func() {
		defer wg.Done()

		if httpsPort == 0 {
			return
		}

		var m https.Manager

		if useLetsEncrypt {
			var domains []string
			if httpsDomains != "" {
				domains = strings.Split(httpsDomains, ",")
			}

			if m, err = https.NewLetsencryptManager("", domains, cacheFile); err != nil {
				panic(err)
			}
		} else {
			if m, err = https.NewSelfSignManager(ssCert, ssKey); err != nil {
				panic(err)
			}
		}

		svr := &http.Server{
			Addr: fmt.Sprintf(":%v", httpsPort),
			TLSConfig: &tls.Config{
				GetCertificate: m.GetCertificate,
			},
		}

		if err := svr.ListenAndServeTLS("", ""); err != nil {
			panic(err)
		}
		fmt.Println("https server ok.")
	}()
	wg.Add(1)

	wg.Wait()
}
<|endoftext|>


<commit_before>// Copyright (c) 2021 Orange Forum authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package views

import (
	"context"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/go-chi/chi"
	"github.com/go-chi/jwtauth"
	"github.com/golang/glog"
	"github.com/gorilla/csrf"
	"github.com/lestrrat-go/jwx/jwt"
	"github.com/s-gv/orangeforum/models"
	"github.com/s-gv/orangeforum/templates"
)

const CtxUserKey = contextKey("user")

var userNameReg *regexp.Regexp
var nextURLReg *regexp.Regexp
var emailReg *regexp.Regexp

func cleanNextURL(next string, basePath string) string {
	if next == "" || next[0] != '/' {
		return basePath
	}
	return next
}

func authenticate(id int, basePath string, w http.ResponseWriter) error {
	_, tokenString, err := tokenAuth.Encode(map[string]interface{}{
		"user_id": strconv.Itoa(id),
		"iat":     time.Now(),
		"exp":     time.Now().Add(365 * 24 * time.Hour),
	})
	if err == nil {
		cookie := http.Cookie{
			Name:     "jwt",
			Value:    tokenString,
			Path:     basePath + "/",
			Expires:  time.Now().Add(365 * 24 * time.Hour),
			HttpOnly: true,
		}
		http.SetCookie(w, &cookie)
	}
	return err
}

func mustAuth(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		domainID := r.Context().Value(ctxDomain).(*models.Domain).DomainID
		token, claims, err := jwtauth.FromContext(r.Context())
		basePath, _ := r.Context().Value(ctxBasePath).(string)

		if err == nil && token != nil && jwt.Validate(token) == nil {
			if uid, ok := claims["user_id"].(string); ok {
				if iat, ok := claims["iat"].(time.Time); ok {
					userID, _ := strconv.Atoi(uid)
					user := models.GetUserByID(userID)
					if user != nil && user.LogoutAt.Time.Before(iat) && user.DomainID == domainID && !user.BannedAt.Valid {
						ctx := context.WithValue(r.Context(), CtxUserKey, user)
						// Token is authenticated, pass it through
						next.ServeHTTP(w, r.WithContext(ctx))
						return
					}
				}
			}
		}

		http.SetCookie(w, &http.Cookie{
			Name:     "jwt",
			Value:    "",
			Path:     "/",
			Expires:  time.Now().Add(-300 * time.Hour),
			HttpOnly: true,
		})
		if r.Method == "GET" {
			http.Redirect(w, r, basePath+"/auth/signin?next="+r.URL.Path, http.StatusSeeOther)
		} else {
			http.Redirect(w, r, basePath+"/", http.StatusSeeOther)
		}
	})
}

func canAuth(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		domainID := r.Context().Value(ctxDomain).(*models.Domain).DomainID
		token, claims, err := jwtauth.FromContext(r.Context())

		if err == nil && token != nil && jwt.Validate(token) == nil {
			if uid, ok := claims["user_id"].(string); ok {
				if iat, ok := claims["iat"].(time.Time); ok {
					userID, _ := strconv.Atoi(uid)
					user := models.GetUserByID(userID)
					if user != nil && user.LogoutAt.Time.Before(iat) && user.DomainID == domainID {
						ctx := context.WithValue(r.Context(), CtxUserKey, user)
						// Token is authenticated, pass it through
						next.ServeHTTP(w, r.WithContext(ctx))
						return
					}
				}
			}
		}

		next.ServeHTTP(w, r)
	})
}

func getAuthSignIn(w http.ResponseWriter, r *http.Request) {
	basePath := r.Context().Value(ctxBasePath).(string)
	next := cleanNextURL(r.FormValue("next"), basePath)
	templates.Signin.Execute(w, map[string]interface{}{
		csrf.TemplateTag: csrf.TemplateField(r),
		BasePathField:    basePath,
		"Next":           next,
	})
}

func postAuthSignIn(w http.ResponseWriter, r *http.Request) {
	domainID := r.Context().Value(ctxDomain).(*models.Domain).DomainID
	basePath := r.Context().Value(ctxBasePath).(string)
	next := cleanNextURL(r.FormValue("next"), basePath)
	email := r.PostFormValue("email")
	passwd := r.PostFormValue("password")
	user := models.GetUserByPasswd(domainID, email, passwd)

	if user != nil && !user.BannedAt.Valid {
		err := authenticate(user.UserID, basePath, w)
		if err != nil {
			glog.Errorf("Error authenticating: %s", err.Error())
		}
		http.Redirect(w, r, next, http.StatusSeeOther)
	}
	templates.Signin.Execute(w, map[string]interface{}{
		csrf.TemplateTag: csrf.TemplateField(r),
		BasePathField:    basePath,
		"Next":           next,
		"ErrMsg":         "Invalid email / password",
	})
}

func getAuthOneTimeSignIn(w http.ResponseWriter, r *http.Request) {
	basePath := r.Context().Value(ctxBasePath).(string)
	next := cleanNextURL(r.FormValue("next"), basePath)
	templates.OneTimeSignin.Execute(w, map[string]interface{}{
		csrf.TemplateTag: csrf.TemplateField(r),
		BasePathField:    basePath,
		"Next":           next,
	})
}

func postAuthOneTimeSignIn(w http.ResponseWriter, r *http.Request) {
	domainID := r.Context().Value(ctxDomain).(*models.Domain).DomainID
	basePath := r.Context().Value(ctxBasePath).(string)
	next := cleanNextURL(r.PostFormValue("next"), basePath)
	email := r.PostFormValue("email")
	errMsg := "E-mail not found"

	user := models.GetUserByEmail(domainID, email)
	if user != nil {
		println(user.Email)
		errMsg = "A one time sign-in link has been sent to your email"
		token := models.UpdateUserOneTimeLoginTokenByID(user.UserID)
		link := "http://" + r.Host + basePath + "/auth/otsignin/" + token + "?next=" + next

		// domain := models.GetDomainByID(domainID) // TODO
		forumName := "Orange Forum" // TODO
		subject := forumName + " sign-in link"
		body := "Someone (hopefully you) requested a sign-in link for " + forumName + ".\r\n" +
			"If you want to sign-in, visit " + link + "\r\n\r\nIf not, just ignore this message."
		sendMail(user.Email, subject, body)
	}

	templates.OneTimeSignin.Execute(w, map[string]interface{}{
		csrf.TemplateTag: csrf.TemplateField(r),
		BasePathField:    basePath,
		"Next":           next,
		"ErrMsg":         errMsg,
	})
}

func getAuthOneTimeSignInDone(w http.ResponseWriter, r *http.Request) {
	domainID := r.Context().Value(ctxDomain).(*models.Domain).DomainID
	basePath := r.Context().Value(ctxBasePath).(string)
	next := cleanNextURL(r.FormValue("next"), basePath)
	token := chi.URLParam(r, "token")
	user := models.GetUserByOneTimeToken(domainID, token)
	if user != nil {
		if err := authenticate(user.UserID, basePath, w); err == nil {
			http.Redirect(w, r, next, http.StatusSeeOther)
			return
		}
	}
	http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
}

func getAuthSignUp(w http.ResponseWriter, r *http.Request) {
	domain := r.Context().Value(ctxDomain).(*models.Domain)
	basePath := r.Context().Value(ctxBasePath).(string)
	next := cleanNextURL(r.FormValue("next"), basePath)
	signupToken := chi.URLParam(r, "signupToken")

	if signupToken != "" && signupToken != domain.SignupToken {
		http.Error(w, http.StatusText(404), http.StatusNotFound)
		return
	}

	if !domain.IsRegularSignupEnabled {
		if domain.SignupToken == "" || signupToken == "" || domain.SignupToken != signupToken {
			http.Error(w, "Signup is disabled. Contact the admin.", http.StatusForbidden)
			return
		}
	}
	templates.Signup.Execute(w, map[string]interface{}{
		csrf.TemplateTag: csrf.TemplateField(r),
		BasePathField:    basePath,
		"Next":           next,
	})
}

func validateEmail(email string) string {
	errMsg := ""
	if !strings.Contains(email, "@") {
		errMsg = "Invalid email"
	}
	if len(email) < 3 {
		errMsg = "Email should have at least 3 characters"
	} else if emailReg.ReplaceAllString(email, "") != email {
		errMsg = "Email should not have non-alphanumeric characters"
	}
	return errMsg
}

func postAuthSignUp(w http.ResponseWriter, r *http.Request) {
	domain := r.Context().Value(ctxDomain).(*models.Domain)
	basePath := r.Context().Value(ctxBasePath).(string)
	next := cleanNextURL(r.FormValue("next"), basePath)
	signupToken := chi.URLParam(r, "signupToken")

	email := r.PostFormValue("email")
	passwd := r.PostFormValue("password")
	passwd2 := r.PostFormValue("password2")

	email = strings.Trim(email, " ")

	if signupToken != "" && signupToken != domain.SignupToken {
		http.Error(w, http.StatusText(404), http.StatusNotFound)
		return
	}

	errMsg := ""

	errMsg = validateEmail(email)

	if len(passwd) < 6 {
		errMsg = "Password should have at least 6 characters"
	} else if passwd != passwd2 {
		errMsg = "Passwords do not match"
	}

	existingUser := models.GetUserByEmail(domain.DomainID, email)
	if existingUser != nil {
		errMsg = "E-mail already registered"
	}

	if !domain.IsRegularSignupEnabled {
		if domain.SignupToken == "" || signupToken == "" || domain.SignupToken != signupToken {
			http.Error(w, "Signup is disabled. Contact the admin.", http.StatusForbidden)
			return
		}
	}

	if errMsg == "" {
		displayName := strings.Split(email, "@")[0]

		err := models.CreateUser(domain.DomainID, email, displayName, passwd)
		if err != nil {
			glog.Errorf("Error creating user: %s", err.Error())
			errMsg = "Error during signup."
		}
	}

	if errMsg == "" {
		glog.Infof("Created user: %s for domainID: %d", email, domain.DomainID)
		user := models.GetUserByEmail(domain.DomainID, email)
		err := authenticate(user.UserID, basePath, w)
		if err != nil {
			glog.Errorf("Error authenticating: %s", err.Error())
		}
		http.Redirect(w, r, next, http.StatusSeeOther)
	}

	templates.Signup.Execute(w, map[string]interface{}{
		csrf.TemplateTag: csrf.TemplateField(r),
		BasePathField:    basePath,
		"ErrMsg":         errMsg,
		"Next":           next,
	})
}

func getAuthChangePass(w http.ResponseWriter, r *http.Request) {
	basePath := r.Context().Value(ctxBasePath).(string)
	templates.ChangePass.Execute(w, map[string]interface{}{
		csrf.TemplateTag: csrf.TemplateField(r),
		BasePathField:    basePath,
	})
}

func postAuthChangePass(w http.ResponseWriter, r *http.Request) {
	domainID := r.Context().Value(ctxDomain).(*models.Domain).DomainID
	basePath := r.Context().Value(ctxBasePath).(string)

	user := r.Context().Value(CtxUserKey).(*models.User)
	oldPasswd := r.PostFormValue("old_password")
	passwd := r.PostFormValue("password")
	passwd2 := r.PostFormValue("password2")

	var errMsg string
	if len(passwd) < 6 {
		errMsg = "New password should have at least 6 characters"
	} else if passwd != passwd2 {
		errMsg = "New passwords do not match"
	}

	oldUser := models.GetUserByPasswd(domainID, user.Email, oldPasswd)
	if oldUser == nil || oldUser.UserID != user.UserID {
		errMsg = "Old password incorrect"
	}

	if errMsg == "" {
		err := models.UpdateUserPasswdByID(user.UserID, passwd)
		if err == nil {
			errMsg = "Changed password successfully"
		} else {
			errMsg = "Error changing password"
			glog.Errorf("Error changing password: %s", err.Error())
		}
	}
	templates.ChangePass.Execute(w, map[string]interface{}{
		csrf.TemplateTag: csrf.TemplateField(r),
		BasePathField:    basePath,
		"ErrMsg":         errMsg,
	})
}

func getAuthLogout(w http.ResponseWriter, r *http.Request) {
	basePath := r.Context().Value(ctxBasePath).(string)

	http.SetCookie(w, &http.Cookie{Name: "jwt", Value: "", Path: basePath + "/", Expires: time.Now().Add(-300 * time.Hour), HttpOnly: true})
	http.SetCookie(w, &http.Cookie{Name: "csrftoken", Value: "", Path: basePath + "/", Expires: time.Now().Add(-300 * time.Hour)})
	if user, ok := r.Context().Value(CtxUserKey).(*models.User); ok {
		models.LogOutUserByID(user.UserID)
	}
	http.Redirect(w, r, basePath+"/", http.StatusSeeOther)
}

func init() {
	userNameReg = regexp.MustCompile("[^a-zA-Z0-9]+")
	emailReg = regexp.MustCompile("[^a-zA-Z0-9@\\.-]+")
	nextURLReg = regexp.MustCompile("[^a-zA-Z0-9-/]+")
}
<commit_msg>Capitalize auto generated display name on signup<commit_after>// Copyright (c) 2021 Orange Forum authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package views

import (
	"context"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/go-chi/chi"
	"github.com/go-chi/jwtauth"
	"github.com/golang/glog"
	"github.com/gorilla/csrf"
	"github.com/lestrrat-go/jwx/jwt"
	"github.com/s-gv/orangeforum/models"
	"github.com/s-gv/orangeforum/templates"
)

const CtxUserKey = contextKey("user")

var userNameReg *regexp.Regexp
var nextURLReg *regexp.Regexp
var emailReg *regexp.Regexp

func cleanNextURL(next string, basePath string) string {
	if next == "" || next[0] != '/' {
		return basePath
	}
	return next
}

func authenticate(id int, basePath string, w http.ResponseWriter) error {
	_, tokenString, err := tokenAuth.Encode(map[string]interface{}{
		"user_id": strconv.Itoa(id),
		"iat":     time.Now(),
		"exp":     time.Now().Add(365 * 24 * time.Hour),
	})
	if err == nil {
		cookie := http.Cookie{
			Name:     "jwt",
			Value:    tokenString,
			Path:     basePath + "/",
			Expires:  time.Now().Add(365 * 24 * time.Hour),
			HttpOnly: true,
		}
		http.SetCookie(w, &cookie)
	}
	return err
}

func mustAuth(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		domainID := r.Context().Value(ctxDomain).(*models.Domain).DomainID
		token, claims, err := jwtauth.FromContext(r.Context())
		basePath, _ := r.Context().Value(ctxBasePath).(string)

		if err == nil && token != nil && jwt.Validate(token) == nil {
			if uid, ok := claims["user_id"].(string); ok {
				if iat, ok := claims["iat"].(time.Time); ok {
					userID, _ := strconv.Atoi(uid)
					user := models.GetUserByID(userID)
					if user != nil && user.LogoutAt.Time.Before(iat) && user.DomainID == domainID && !user.BannedAt.Valid {
						ctx := context.WithValue(r.Context(), CtxUserKey, user)
						// Token is authenticated, pass it through
						next.ServeHTTP(w, r.WithContext(ctx))
						return
					}
				}
			}
		}

		http.SetCookie(w, &http.Cookie{
			Name:     "jwt",
			Value:    "",
			Path:     "/",
			Expires:  time.Now().Add(-300 * time.Hour),
			HttpOnly: true,
		})
		if r.Method == "GET" {
			http.Redirect(w, r, basePath+"/auth/signin?next="+r.URL.Path, http.StatusSeeOther)
		} else {
			http.Redirect(w, r, basePath+"/", http.StatusSeeOther)
		}
	})
}

func canAuth(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		domainID := r.Context().Value(ctxDomain).(*models.Domain).DomainID
		token, claims, err := jwtauth.FromContext(r.Context())

		if err == nil && token != nil && jwt.Validate(token) == nil {
			if uid, ok := claims["user_id"].(string); ok {
				if iat, ok := claims["iat"].(time.Time); ok {
					userID, _ := strconv.Atoi(uid)
					user := models.GetUserByID(userID)
					if user != nil && user.LogoutAt.Time.Before(iat) && user.DomainID == domainID {
						ctx := context.WithValue(r.Context(), CtxUserKey, user)
						// Token is authenticated, pass it through
						next.ServeHTTP(w, r.WithContext(ctx))
						return
					}
				}
			}
		}

		next.ServeHTTP(w, r)
	})
}

func getAuthSignIn(w http.ResponseWriter, r *http.Request) {
	basePath := r.Context().Value(ctxBasePath).(string)
	next := cleanNextURL(r.FormValue("next"), basePath)
	templates.Signin.Execute(w, map[string]interface{}{
		csrf.TemplateTag: csrf.TemplateField(r),
		BasePathField:    basePath,
		"Next":           next,
	})
}

func postAuthSignIn(w http.ResponseWriter, r *http.Request) {
	domainID := r.Context().Value(ctxDomain).(*models.Domain).DomainID
	basePath := r.Context().Value(ctxBasePath).(string)
	next := cleanNextURL(r.FormValue("next"), basePath)
	email := r.PostFormValue("email")
	passwd := r.PostFormValue("password")
	user := models.GetUserByPasswd(domainID, email, passwd)

	if user != nil && !user.BannedAt.Valid {
		err := authenticate(user.UserID, basePath, w)
		if err != nil {
			glog.Errorf("Error authenticating: %s", err.Error())
		}
		http.Redirect(w, r, next, http.StatusSeeOther)
	}
	templates.Signin.Execute(w, map[string]interface{}{
		csrf.TemplateTag: csrf.TemplateField(r),
		BasePathField:    basePath,
		"Next":           next,
		"ErrMsg":         "Invalid email / password",
	})
}

func getAuthOneTimeSignIn(w http.ResponseWriter, r *http.Request) {
	basePath := r.Context().Value(ctxBasePath).(string)
	next := cleanNextURL(r.FormValue("next"), basePath)
	templates.OneTimeSignin.Execute(w, map[string]interface{}{
		csrf.TemplateTag: csrf.TemplateField(r),
		BasePathField:    basePath,
		"Next":           next,
	})
}

func postAuthOneTimeSignIn(w http.ResponseWriter, r *http.Request) {
	domainID := r.Context().Value(ctxDomain).(*models.Domain).DomainID
	basePath := r.Context().Value(ctxBasePath).(string)
	next := cleanNextURL(r.PostFormValue("next"), basePath)
	email := r.PostFormValue("email")
	errMsg := "E-mail not found"

	user := models.GetUserByEmail(domainID, email)
	if user != nil {
		println(user.Email)
		errMsg = "A one time sign-in link has been sent to your email"
		token := models.UpdateUserOneTimeLoginTokenByID(user.UserID)
		link := "http://" + r.Host + basePath + "/auth/otsignin/" + token + "?next=" + next

		// domain := models.GetDomainByID(domainID) // TODO
		forumName := "Orange Forum" // TODO
		subject := forumName + " sign-in link"
		body := "Someone (hopefully you) requested a sign-in link for " + forumName + ".\r\n" +
			"If you want to sign-in, visit " + link + "\r\n\r\nIf not, just ignore this message."
		sendMail(user.Email, subject, body)
	}

	templates.OneTimeSignin.Execute(w, map[string]interface{}{
		csrf.TemplateTag: csrf.TemplateField(r),
		BasePathField:    basePath,
		"Next":           next,
		"ErrMsg":         errMsg,
	})
}

func getAuthOneTimeSignInDone(w http.ResponseWriter, r *http.Request) {
	domainID := r.Context().Value(ctxDomain).(*models.Domain).DomainID
	basePath := r.Context().Value(ctxBasePath).(string)
	next := cleanNextURL(r.FormValue("next"), basePath)
	token := chi.URLParam(r, "token")
	user := models.GetUserByOneTimeToken(domainID, token)
	if user != nil {
		if err := authenticate(user.UserID, basePath, w); err == nil {
			http.Redirect(w, r, next, http.StatusSeeOther)
			return
		}
	}
	http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
}

func getAuthSignUp(w http.ResponseWriter, r *http.Request) {
	domain := r.Context().Value(ctxDomain).(*models.Domain)
	basePath := r.Context().Value(ctxBasePath).(string)
	next := cleanNextURL(r.FormValue("next"), basePath)
	signupToken := chi.URLParam(r, "signupToken")

	if signupToken != "" && signupToken != domain.SignupToken {
		http.Error(w, http.StatusText(404), http.StatusNotFound)
		return
	}

	if !domain.IsRegularSignupEnabled {
		if domain.SignupToken == "" || signupToken == "" || domain.SignupToken != signupToken {
			http.Error(w, "Signup is disabled. Contact the admin.", http.StatusForbidden)
			return
		}
	}
	templates.Signup.Execute(w, map[string]interface{}{
		csrf.TemplateTag: csrf.TemplateField(r),
		BasePathField:    basePath,
		"Next":           next,
	})
}

func validateEmail(email string) string {
	errMsg := ""
	if !strings.Contains(email, "@") {
		errMsg = "Invalid email"
	}
	if len(email) < 3 {
		errMsg = "Email should have at least 3 characters"
	} else if emailReg.ReplaceAllString(email, "") != email {
		errMsg = "Email should not have non-alphanumeric characters"
	}
	return errMsg
}

func postAuthSignUp(w http.ResponseWriter, r *http.Request) {
	domain := r.Context().Value(ctxDomain).(*models.Domain)
	basePath := r.Context().Value(ctxBasePath).(string)
	next := cleanNextURL(r.FormValue("next"), basePath)
	signupToken := chi.URLParam(r, "signupToken")

	email := r.PostFormValue("email")
	passwd := r.PostFormValue("password")
	passwd2 := r.PostFormValue("password2")

	email = strings.Trim(email, " ")

	if signupToken != "" && signupToken != domain.SignupToken {
		http.Error(w, http.StatusText(404), http.StatusNotFound)
		return
	}

	errMsg := ""

	errMsg = validateEmail(email)

	if len(passwd) < 6 {
		errMsg = "Password should have at least 6 characters"
	} else if passwd != passwd2 {
		errMsg = "Passwords do not match"
	}

	existingUser := models.GetUserByEmail(domain.DomainID, email)
	if existingUser != nil {
		errMsg = "E-mail already registered"
	}

	if !domain.IsRegularSignupEnabled {
		if domain.SignupToken == "" || signupToken == "" || domain.SignupToken != signupToken {
			http.Error(w, "Signup is disabled. Contact the admin.", http.StatusForbidden)
			return
		}
	}

	if errMsg == "" {
		displayName := strings.Split(email, "@")[0]
		displayName = strings.Title(strings.ToLower(displayName))

		err := models.CreateUser(domain.DomainID, email, displayName, passwd)
		if err != nil {
			glog.Errorf("Error creating user: %s", err.Error())
			errMsg = "Error during signup."
		}
	}

	if errMsg == "" {
		glog.Infof("Created user: %s for domainID: %d", email, domain.DomainID)
		user := models.GetUserByEmail(domain.DomainID, email)
		err := authenticate(user.UserID, basePath, w)
		if err != nil {
			glog.Errorf("Error authenticating: %s", err.Error())
		}
		http.Redirect(w, r, next, http.StatusSeeOther)
	}

	templates.Signup.Execute(w, map[string]interface{}{
		csrf.TemplateTag: csrf.TemplateField(r),
		BasePathField:    basePath,
		"ErrMsg":         errMsg,
		"Next":           next,
	})
}

func getAuthChangePass(w http.ResponseWriter, r *http.Request) {
	basePath := r.Context().Value(ctxBasePath).(string)
	templates.ChangePass.Execute(w, map[string]interface{}{
		csrf.TemplateTag: csrf.TemplateField(r),
		BasePathField:    basePath,
	})
}

func postAuthChangePass(w http.ResponseWriter, r *http.Request) {
	domainID := r.Context().Value(ctxDomain).(*models.Domain).DomainID
	basePath := r.Context().Value(ctxBasePath).(string)

	user := r.Context().Value(CtxUserKey).(*models.User)
	oldPasswd := r.PostFormValue("old_password")
	passwd := r.PostFormValue("password")
	passwd2 := r.PostFormValue("password2")

	var errMsg string
	if len(passwd) < 6 {
		errMsg = "New password should have at least 6 characters"
	} else if passwd != passwd2 {
		errMsg = "New passwords do not match"
	}

	oldUser := models.GetUserByPasswd(domainID, user.Email, oldPasswd)
	if oldUser == nil || oldUser.UserID != user.UserID {
		errMsg = "Old password incorrect"
	}

	if errMsg == "" {
		err := models.UpdateUserPasswdByID(user.UserID, passwd)
		if err == nil {
			errMsg = "Changed password successfully"
		} else {
			errMsg = "Error changing password"
			glog.Errorf("Error changing password: %s", err.Error())
		}
	}
	templates.ChangePass.Execute(w, map[string]interface{}{
		csrf.TemplateTag: csrf.TemplateField(r),
		BasePathField:    basePath,
		"ErrMsg":         errMsg,
	})
}

func getAuthLogout(w http.ResponseWriter, r *http.Request) {
	basePath := r.Context().Value(ctxBasePath).(string)

	http.SetCookie(w, &http.Cookie{Name: "jwt", Value: "", Path: basePath + "/", Expires: time.Now().Add(-300 * time.Hour), HttpOnly: true})
	http.SetCookie(w, &http.Cookie{Name: "csrftoken", Value: "", Path: basePath + "/", Expires: time.Now().Add(-300 * time.Hour)})
	if user, ok := r.Context().Value(CtxUserKey).(*models.User); ok {
		models.LogOutUserByID(user.UserID)
	}
	http.Redirect(w, r, basePath+"/", http.StatusSeeOther)
}

func init() {
	userNameReg = regexp.MustCompile("[^a-zA-Z0-9]+")
	emailReg = regexp.MustCompile("[^a-zA-Z0-9@\\.-]+")
	nextURLReg = regexp.MustCompile("[^a-zA-Z0-9-/]+")
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"os"
	"fmt"
	"github.com/janosgyerik/dupfinder"
	"github.com/janosgyerik/dupfinder/finder"
	"github.com/janosgyerik/dupfinder/pathreader"
)

func exit() {
	flag.Usage()
	os.Exit(1)
}

type Params struct {
	paths   []string
	minSize int64
	stdin   bool
	stdin0  bool
}

func parseArgs() Params {
	minSizePtr := flag.Int64("minSize", 1, "minimum file size")
	stdinPtr := flag.Bool("stdin", false, "read paths from stdin")
	zeroPtr := flag.Bool("0", false, "read paths from stdin, null-delimited")

	flag.Parse()

	var paths []string
	if *zeroPtr {
		paths = pathreader.ReadPathsFromLines(os.Stdin)
	} else if *stdinPtr {
		paths = pathreader.ReadPathsFromNullDelimited(os.Stdin)
	} else {
		paths = pathreader.FilterPaths(flag.Args())
	}

	if len(paths) == 0 {
		exit()
	}

	return Params{
		paths: paths,
		minSize: *minSizePtr,
	}
}

func main() {
	params := parseArgs()

	filefinder := finder.NewFinder(finder.Filters.MinSize(params.minSize))

	paths := []string{}
	for _, path := range params.paths {
		paths = append(paths, filefinder.FindAll(path)...)
	}

	result := dupfinder.FindDuplicates(paths)

	if len(result.Failures) > 0 {
		fmt.Println("# I/O errors in files:")
		for _, failure := range result.Failures {
			fmt.Printf("# %s\n", failure.Path)
		}
		fmt.Println()
	}

	for _, dup := range result.Groups {
		for _, path := range dup.GetPaths() {
			fmt.Println(path)
		}
		fmt.Println()
	}
}
<commit_msg>Fix accidentally flipped -stdin and -0<commit_after>package main

import (
	"flag"
	"os"
	"fmt"
	"github.com/janosgyerik/dupfinder"
	"github.com/janosgyerik/dupfinder/finder"
	"github.com/janosgyerik/dupfinder/pathreader"
)

func exit() {
	flag.Usage()
	os.Exit(1)
}

type Params struct {
	paths   []string
	minSize int64
	stdin   bool
	stdin0  bool
}

func parseArgs() Params {
	minSizePtr := flag.Int64("minSize", 1, "minimum file size")
	stdinPtr := flag.Bool("stdin", false, "read paths from stdin")
	zeroPtr := flag.Bool("0", false, "read paths from stdin, null-delimited")

	flag.Parse()

	var paths []string
	if *zeroPtr {
		paths = pathreader.ReadPathsFromNullDelimited(os.Stdin)
	} else if *stdinPtr {
		paths = pathreader.ReadPathsFromLines(os.Stdin)
	} else {
		paths = pathreader.FilterPaths(flag.Args())
	}

	if len(paths) == 0 {
		exit()
	}

	return Params{
		paths: paths,
		minSize: *minSizePtr,
	}
}

func main() {
	params := parseArgs()

	filefinder := finder.NewFinder(finder.Filters.MinSize(params.minSize))

	paths := []string{}
	for _, path := range params.paths {
		paths = append(paths, filefinder.FindAll(path)...)
	}

	result := dupfinder.FindDuplicates(paths)

	if len(result.Failures) > 0 {
		fmt.Println("# I/O errors in files:")
		for _, failure := range result.Failures {
			fmt.Printf("# %s\n", failure.Path)
		}
		fmt.Println()
	}

	for _, dup := range result.Groups {
		for _, path := range dup.GetPaths() {
			fmt.Println(path)
		}
		fmt.Println()
	}
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package envoy

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"strings"
	"time"

	"github.com/lestrrat-go/jwx/v2/jwa"
	"github.com/lestrrat-go/jwx/v2/jwk"
	"github.com/lestrrat-go/jwx/v2/jws"
	"github.com/lestrrat-go/jwx/v2/jwt"

	"github.com/apigee/apigeecli/clilog"
)

const keyFile = "remote-service.key"
const certFile = "remote-service.crt"
const kidFile = "remote-service.properties"
const use = "sig"
const kidFormat = "kid="

func readFile(name string) (data []byte, err error) {
	data, err = ioutil.ReadFile(name)
	return
}

func writeToFile(name string, data string) error {

	f, err := os.Create(name)
	if err != nil {
		log.Printf("failed to open file: %s\n", err)
		return err
	}

	_, err = f.WriteString(data)
	if err != nil {
		log.Printf("failed to open file: %s\n", err)
		return err
	}

	f.Close()

	return nil
}

func getPrivateKey(privateKey string) (interface{}, error) {
	pemPrivateKey := fmt.Sprintf("%v", privateKey)
	block, _ := pem.Decode([]byte(pemPrivateKey))
	privKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		clilog.Error.Println("error parsing Private Key: ", err)
		return nil, err
	}
	return privKey, nil
}

func GenerateToken(folder string, expiry int) (string, error) {

	var jwtKidFile, jwtKeyFile, privateKey, kid string
	var err error

	if jwtKidFile, jwtKeyFile, _, err = checkFiles(folder); err != nil {
		return "", err
	}

	const aud = "remote-service-client"
	const iss = "apigee-remote-service-envoy"
	const tokenType = "JWT"

	if privateKey, err = getFileContents(jwtKeyFile); err != nil {
		return "", err
	}

	if kid, err = getFileContents(jwtKidFile); err != nil {
		return "", err
	}

	privKey, err := getPrivateKey(privateKey)

	if err != nil {
		return "", err
	}

	now := time.Now()
	hdr := jws.NewHeaders()
	if err = hdr.Set(jws.AlgorithmKey, jwa.RS256); err != nil {
		return "", err
	}
	if err = hdr.Set(jws.TypeKey, tokenType); err != nil {
		return "", err
	}

	if err = hdr.Set(jws.KeyIDKey, getKid(kid)); err != nil {
		return "", err
	}

	token := jwt.New()

	if err = token.Set(jwt.AudienceKey, aud); err != nil {
		return "", err
	}
	if err = token.Set(jwt.IssuerKey, iss); err != nil {
		return "", err
	}
	if err = token.Set(jwt.IssuedAtKey, now.Unix()); err != nil {
		return "", err
	}
	if err = token.Set(jwt.ExpirationKey, now.Unix()+(int64(expiry*60))); err != nil {
		return "", err
	}

	buf, err := json.Marshal(token)
	if err != nil {
		return "", err
	}

	payload, err := jws.Sign(buf, jws.WithKey(jwa.RS256, privKey), jws.WithHeaders(hdr))
	if err != nil {
		clilog.Error.Println("error parsing Private Key: ", err)
		return "", err
	}
	clilog.Info.Println("jwt token : ", string(payload))
	return string(payload), nil
}

func Generatekeys(kid string, folder string) (err error) {

	var jwtCertFile, jwtKeyFile string

	if folder != "" {
		jwtKeyFile = path.Join(folder, keyFile)
		jwtCertFile = path.Join(folder, certFile)
	} else {
		jwtKeyFile = keyFile
		jwtCertFile = certFile
	}

	privkey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return err
	}

	pemdata := pem.EncodeToMemory(
		&pem.Block{
			Type:  "RSA PRIVATE KEY",
			Bytes: x509.MarshalPKCS1PrivateKey(privkey),
		},
	)

	if err = writeToFile(jwtKeyFile, string(pemdata)); err != nil {
		return err
	}

	key, err := jwk.FromRaw(&privkey.PublicKey)
	if err != nil {
		return err
	}
	if err = key.Set(jwk.KeyUsageKey, use); err != nil {
		return err
	}
	if err = key.Set(jwk.KeyIDKey, kid); err != nil {
		return err
	}

	if fileExists(jwtCertFile) {
		//append cert
		return nil
	} else { //create new cert file
		jsonbuf, err := json.MarshalIndent(key, "", "  ")
		if err != nil {
			return err
		}

		set, err := jwk.Parse(jsonbuf)
		if err != nil {
			return err
		}
		jsonbuf, err = json.MarshalIndent(set, "", "  ")
		if err != nil {
			return err
		}

		return writeToFile(jwtCertFile, string(jsonbuf))
	}
}

func Generatekid(kid string, folder string) (err error) {
	var jwtKidFile string
	if folder != "" {
		jwtKidFile = path.Join(folder, kidFile)
	} else {
		jwtKidFile = kidFile
	}
	data := kidFormat + kid
	return writeToFile(jwtKidFile, data)
}

func AddKey(kid string, folder string) (err error) {

	var jwtCertFile string
	if jwtCertFile = path.Join(folder, certFile); !fileExists(jwtCertFile) {
		return fmt.Errorf("remote-service.crt not found in %s", folder)
	}

	data, err := readFile(jwtCertFile)
	if err != nil {
		return err
	}

	set, err := jwk.Parse(data)
	if err != nil {
		return err
	}

	privkey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return err
	}

	pemdata := pem.EncodeToMemory(
		&pem.Block{
			Type:  "RSA PRIVATE KEY",
			Bytes: x509.MarshalPKCS1PrivateKey(privkey),
		},
	)

	if err = writeToFile(keyFile, string(pemdata)); err != nil {
		return err
	}

	newKey, err := jwk.FromRaw(&privkey.PublicKey)
	if err != nil {
		return err
	}
	if err = newKey.Set(jwk.KeyUsageKey, use); err != nil {
		return err
	}
	if err = newKey.Set(jwk.KeyIDKey, kid); err != nil {
		return err
	}

	set.Keys = append(set.Keys, newKey)

	jsonbuf, err := json.MarshalIndent(set, "", "  ")
	if err != nil {
		return err
	}

	return writeToFile(certFile, string(jsonbuf))
}

func fileExists(file string) bool {
	if _, err := os.Stat(file); os.IsNotExist(err) {
		return false
	}
	return true
}

func getFileContents(filename string) (content string, err error) {
	var contentBytes []byte
	if contentBytes, err = ioutil.ReadFile(filename); err != nil {
		return "", err
	}
	return strings.TrimSuffix(string(contentBytes), "\n"), nil
}

func getKid(kid string) string {
	return strings.ReplaceAll(kid, kidFormat, "")
}

func checkFiles(folder string) (jwtKidFile string, jwtKeyFile string, jwtCertFile string, err error) {

	if jwtKidFile = path.Join(folder, kidFile); !fileExists(jwtKidFile) {
		return "", "", "", fmt.Errorf("remote-service.properties not found in %s", folder)
	}

	if jwtKeyFile = path.Join(folder, keyFile); !fileExists(jwtKeyFile) {
		return "", "", "", fmt.Errorf("remote-service.key not found in %s", folder)
	}

	if jwtCertFile = path.Join(folder, certFile); !fileExists(jwtCertFile) {
		return "", "", "", fmt.Errorf("remote-service.crt not found in %s", folder)
	}

	return jwtKidFile, jwtKeyFile, jwtCertFile, nil
}
<commit_msg>Use jwk.Set method<commit_after>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package envoy

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"strings"
	"time"

	"github.com/lestrrat-go/jwx/v2/jwa"
	"github.com/lestrrat-go/jwx/v2/jwk"
	"github.com/lestrrat-go/jwx/v2/jws"
	"github.com/lestrrat-go/jwx/v2/jwt"

	"github.com/apigee/apigeecli/clilog"
)

const keyFile = "remote-service.key"
const certFile = "remote-service.crt"
const kidFile = "remote-service.properties"
const use = "sig"
const kidFormat = "kid="

func readFile(name string) (data []byte, err error) {
	data, err = ioutil.ReadFile(name)
	return
}

func writeToFile(name string, data string) error {

	f, err := os.Create(name)
	if err != nil {
		log.Printf("failed to open file: %s\n", err)
		return err
	}

	_, err = f.WriteString(data)
	if err != nil {
		log.Printf("failed to open file: %s\n", err)
		return err
	}

	f.Close()

	return nil
}

func getPrivateKey(privateKey string) (interface{}, error) {
	pemPrivateKey := fmt.Sprintf("%v", privateKey)
	block, _ := pem.Decode([]byte(pemPrivateKey))
	privKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		clilog.Error.Println("error parsing Private Key: ", err)
		return nil, err
	}
	return privKey, nil
}

func GenerateToken(folder string, expiry int) (string, error) {

	var jwtKidFile, jwtKeyFile, privateKey, kid string
	var err error

	if jwtKidFile, jwtKeyFile, _, err = checkFiles(folder); err != nil {
		return "", err
	}

	const aud = "remote-service-client"
	const iss = "apigee-remote-service-envoy"
	const tokenType = "JWT"

	if privateKey, err = getFileContents(jwtKeyFile); err != nil {
		return "", err
	}

	if kid, err = getFileContents(jwtKidFile); err != nil {
		return "", err
	}

	privKey, err := getPrivateKey(privateKey)

	if err != nil {
		return "", err
	}

	now := time.Now()
	hdr := jws.NewHeaders()
	if err = hdr.Set(jws.AlgorithmKey, jwa.RS256); err != nil {
		return "", err
	}
	if err = hdr.Set(jws.TypeKey, tokenType); err != nil {
		return "", err
	}

	if err = hdr.Set(jws.KeyIDKey, getKid(kid)); err != nil {
		return "", err
	}

	token := jwt.New()

	if err = token.Set(jwt.AudienceKey, aud); err != nil {
		return "", err
	}
	if err = token.Set(jwt.IssuerKey, iss); err != nil {
		return "", err
	}
	if err = token.Set(jwt.IssuedAtKey, now.Unix()); err != nil {
		return "", err
	}
	if err = token.Set(jwt.ExpirationKey, now.Unix()+(int64(expiry*60))); err != nil {
		return "", err
	}

	buf, err := json.Marshal(token)
	if err != nil {
		return "", err
	}

	payload, err := jws.Sign(buf, jws.WithKey(jwa.RS256, privKey), jws.WithHeaders(hdr))
	if err != nil {
		clilog.Error.Println("error parsing Private Key: ", err)
		return "", err
	}
	clilog.Info.Println("jwt token : ", string(payload))
	return string(payload), nil
}

func Generatekeys(kid string, folder string) (err error) {

	var jwtCertFile, jwtKeyFile string

	if folder != "" {
		jwtKeyFile = path.Join(folder, keyFile)
		jwtCertFile = path.Join(folder, certFile)
	} else {
		jwtKeyFile = keyFile
		jwtCertFile = certFile
	}

	privkey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return err
	}

	pemdata := pem.EncodeToMemory(
		&pem.Block{
			Type:  "RSA PRIVATE KEY",
			Bytes: x509.MarshalPKCS1PrivateKey(privkey),
		},
	)

	if err = writeToFile(jwtKeyFile, string(pemdata)); err != nil {
		return err
	}

	key, err := jwk.FromRaw(&privkey.PublicKey)
	if err != nil {
		return err
	}
	if err = key.Set(jwk.KeyUsageKey, use); err != nil {
		return err
	}
	if err = key.Set(jwk.KeyIDKey, kid); err != nil {
		return err
	}

	if fileExists(jwtCertFile) {
		//append cert
		return nil
	} else { //create new cert file
		jsonbuf, err := json.MarshalIndent(key, "", "  ")
		if err != nil {
			return err
		}

		set, err := jwk.Parse(jsonbuf)
		if err != nil {
			return err
		}
		jsonbuf, err = json.MarshalIndent(set, "", "  ")
		if err != nil {
			return err
		}

		return writeToFile(jwtCertFile, string(jsonbuf))
	}
}

func Generatekid(kid string, folder string) (err error) {
	var jwtKidFile string
	if folder != "" {
		jwtKidFile = path.Join(folder, kidFile)
	} else {
		jwtKidFile = kidFile
	}
	data := kidFormat + kid
	return writeToFile(jwtKidFile, data)
}

func AddKey(kid string, folder string) (err error) {

	var jwtCertFile string
	if jwtCertFile = path.Join(folder, certFile); !fileExists(jwtCertFile) {
		return fmt.Errorf("remote-service.crt not found in %s", folder)
	}

	data, err := readFile(jwtCertFile)
	if err != nil {
		return err
	}

	set, err := jwk.Parse(data)
	if err != nil {
		return err
	}

	privkey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return err
	}

	pemdata := pem.EncodeToMemory(
		&pem.Block{
			Type:  "RSA PRIVATE KEY",
			Bytes: x509.MarshalPKCS1PrivateKey(privkey),
		},
	)

	if err = writeToFile(keyFile, string(pemdata)); err != nil {
		return err
	}

	newKey, err := jwk.FromRaw(&privkey.PublicKey)
	if err != nil {
		return err
	}
	if err = newKey.Set(jwk.KeyUsageKey, use); err != nil {
		return err
	}
	if err = newKey.Set(jwk.KeyIDKey, kid); err != nil {
		return err
	}

	set.AddKey(newKey)

	jsonbuf, err := json.MarshalIndent(set, "", "  ")
	if err != nil {
		return err
	}

	return writeToFile(certFile, string(jsonbuf))
}

func fileExists(file string) bool {
	if _, err := os.Stat(file); os.IsNotExist(err) {
		return false
	}
	return true
}

func getFileContents(filename string) (content string, err error) {
	var contentBytes []byte
	if contentBytes, err = ioutil.ReadFile(filename); err != nil {
		return "", err
	}
	return strings.TrimSuffix(string(contentBytes), "\n"), nil
}

func getKid(kid string) string {
	return strings.ReplaceAll(kid, kidFormat, "")
}

func checkFiles(folder string) (jwtKidFile string, jwtKeyFile string, jwtCertFile string, err error) {

	if jwtKidFile = path.Join(folder, kidFile); !fileExists(jwtKidFile) {
		return "", "", "", fmt.Errorf("remote-service.properties not found in %s", folder)
	}

	if jwtKeyFile = path.Join(folder, keyFile); !fileExists(jwtKeyFile) {
		return "", "", "", fmt.Errorf("remote-service.key not found in %s", folder)
	}

	if jwtCertFile = path.Join(folder, certFile); !fileExists(jwtCertFile) {
		return "", "", "", fmt.Errorf("remote-service.crt not found in %s", folder)
	}

	return jwtKidFile, jwtKeyFile, jwtCertFile, nil
}
<|endoftext|>


<commit_before>package log

import (
	"fmt"
	"runtime"
	"strings"

	"github.com/sirupsen/logrus"
)

// Level refers to the level of logging
type Level string

// Logger is an interface that needs to be implemented in order to log on Ragnarok.
type Logger interface {
	Debug(...interface{})
	Debugln(...interface{})
	Debugf(string, ...interface{})

	Info(...interface{})
	Infoln(...interface{})
	Infof(string, ...interface{})

	Warn(...interface{})
	Warnln(...interface{})
	Warnf(string, ...interface{})

	Error(...interface{})
	Errorln(...interface{})
	Errorf(string, ...interface{})

	Fatal(...interface{})
	Fatalln(...interface{})
	Fatalf(string, ...interface{})

	With(key string, value interface{}) Logger
	Set(level Level) error
}

type logger struct {
	entry *logrus.Entry
}

func (l logger) Debug(args ...interface{}) {
	l.sourced().Debug(args...)
}

func (l logger) Debugln(args ...interface{}) {
	l.sourced().Debugln(args...)
}

func (l logger) Debugf(format string, args ...interface{}) {
	l.sourced().Debugf(format, args...)
}

func (l logger) Info(args ...interface{}) {
	l.sourced().Info(args...)
}

func (l logger) Infoln(args ...interface{}) {
	l.sourced().Infoln(args...)
}

func (l logger) Infof(format string, args ...interface{}) {
	l.sourced().Infof(format, args...)
}

func (l logger) Warn(args ...interface{}) {
	l.sourced().Warn(args...)
}

func (l logger) Warnln(args ...interface{}) {
	l.sourced().Warnln(args...)
}

func (l logger) Warnf(format string, args ...interface{}) {
	l.sourced().Warnf(format, args...)
}

func (l logger) Error(args ...interface{}) {
	l.sourced().Error(args...)
}

func (l logger) Errorln(args ...interface{}) {
	l.sourced().Errorln(args...)
}

func (l logger) Errorf(format string, args ...interface{}) {
	l.sourced().Errorf(format, args...)
}

func (l logger) Fatal(args ...interface{}) {
	l.sourced().Fatal(args...)
}

func (l logger) Fatalln(args ...interface{}) {
	l.sourced().Fatalln(args...)
}

func (l logger) Fatalf(format string, args ...interface{}) {
	l.sourced().Fatalf(format, args...)
}

func (l logger) With(key string, value interface{}) Logger {
	return &logger{l.entry.WithField(key, value)}
}

func (l *logger) Set(level Level) error {
	leLev, err := logrus.ParseLevel(string(level))
	if err != nil {
		return err
	}
	l.entry.Logger.Level = leLev
	return nil
}

func (l logger) sourced() *logrus.Entry {
	_, file, line, ok := runtime.Caller(2)
	if !ok {
		file = "<???>"
		line = 1
	} else {
		slash := strings.LastIndex(file, "/")
		file = file[slash+1:]
	}
	return l.entry.WithField("src", fmt.Sprintf("%s:%d", file, line))
}

var baseLogger = &logger{
	entry: &logrus.Entry{
		Logger: logrus.New(),
	},
}

// Base returns the base logger
func Base() Logger {
	return baseLogger
}

// Debug logs debug message
func Debug(args ...interface{}) {
	baseLogger.sourced().Debug(args...)
}

// Debugln logs debug message
func Debugln(args ...interface{}) {
	baseLogger.sourced().Debugln(args...)
}

// Debugf logs debug message
func Debugf(format string, args ...interface{}) {
	baseLogger.sourced().Debugf(format, args...)
}

// Info logs info message
func Info(args ...interface{}) {
	baseLogger.sourced().Info(args...)
}

// Infoln logs info message
func Infoln(args ...interface{}) {
	baseLogger.sourced().Infoln(args...)
}

// Infof logs info message
func Infof(format string, args ...interface{}) {
	baseLogger.sourced().Infof(format, args...)
}

// Warn logs warn message
func Warn(args ...interface{}) {
	baseLogger.sourced().Warn(args...)
}

// Warnln logs warn message
func Warnln(args ...interface{}) {
	baseLogger.sourced().Warnln(args...)
}

// Warnf logs warn message
func Warnf(format string, args ...interface{}) {
	baseLogger.sourced().Warnf(format, args...)
}

// Error logs error message
func Error(args ...interface{}) {
	baseLogger.sourced().Error(args...)
}

// Errorln logs error message
func Errorln(args ...interface{}) {
	baseLogger.sourced().Errorln(args...)
}

// Errorf logs error message
func Errorf(format string, args ...interface{}) {
	baseLogger.sourced().Errorf(format, args...)
}

// Fatal logs fatal message
func Fatal(args ...interface{}) {
	baseLogger.sourced().Fatal(args...)
}

// Fatalln logs fatal message
func Fatalln(args ...interface{}) {
	baseLogger.sourced().Fatalln(args...)
}

// Fatalf logs fatal message
func Fatalf(format string, args ...interface{}) {
	baseLogger.sourced().Fatalf(format, args...)
}

// With adds a key:value to the logger
func With(key string, value interface{}) Logger {
	return baseLogger.With(key, value)
}

// Set will set the logger level
func Set(level Level) error {
	return baseLogger.Set(level)
}
<commit_msg>Improve log package<commit_after>package log

import (
	"fmt"
	"runtime"
	"strings"

	"github.com/sirupsen/logrus"
)

// Level refers to the level of logging
type Level string

// Logger is an interface that needs to be implemented in order to log on Ragnarok.
type Logger interface {
	Debug(...interface{})
	Debugln(...interface{})
	Debugf(string, ...interface{})

	Info(...interface{})
	Infoln(...interface{})
	Infof(string, ...interface{})

	Warn(...interface{})
	Warnln(...interface{})
	Warnf(string, ...interface{})

	Error(...interface{})
	Errorln(...interface{})
	Errorf(string, ...interface{})

	Fatal(...interface{})
	Fatalln(...interface{})
	Fatalf(string, ...interface{})

	Panic(...interface{})
	Panicln(...interface{})
	Panicf(string, ...interface{})

	With(key string, value interface{}) Logger
	WithField(key string, value interface{}) Logger
	Set(level Level) error
}

type logger struct {
	entry *logrus.Entry
}

func (l logger) Debug(args ...interface{}) {
	l.sourced().Debug(args...)
}

func (l logger) Debugln(args ...interface{}) {
	l.sourced().Debugln(args...)
}

func (l logger) Debugf(format string, args ...interface{}) {
	l.sourced().Debugf(format, args...)
}

func (l logger) Info(args ...interface{}) {
	l.sourced().Info(args...)
}

func (l logger) Infoln(args ...interface{}) {
	l.sourced().Infoln(args...)
}

func (l logger) Infof(format string, args ...interface{}) {
	l.sourced().Infof(format, args...)
}

func (l logger) Warn(args ...interface{}) {
	l.sourced().Warn(args...)
}

func (l logger) Warnln(args ...interface{}) {
	l.sourced().Warnln(args...)
}

func (l logger) Warnf(format string, args ...interface{}) {
	l.sourced().Warnf(format, args...)
}

func (l logger) Error(args ...interface{}) {
	l.sourced().Error(args...)
}

func (l logger) Errorln(args ...interface{}) {
	l.sourced().Errorln(args...)
}

func (l logger) Errorf(format string, args ...interface{}) {
	l.sourced().Errorf(format, args...)
}

func (l logger) Fatal(args ...interface{}) {
	l.sourced().Fatal(args...)
}

func (l logger) Fatalln(args ...interface{}) {
	l.sourced().Fatalln(args...)
}

func (l logger) Fatalf(format string, args ...interface{}) {
	l.sourced().Fatalf(format, args...)
}
func (l logger) Panic(args ...interface{}) {
	l.sourced().Panic(args...)
}
func (l logger) Panicln(args ...interface{}) {
	l.sourced().Panicln(args...)
}
func (l logger) Panicf(format string, args ...interface{}) {
	l.sourced().Panicf(format, args...)
}

func (l logger) With(key string, value interface{}) Logger {
	return &logger{l.entry.WithField(key, value)}
}

func (l logger) WithField(key string, value interface{}) Logger {
	return &logger{l.entry.WithField(key, value)}
}

func (l *logger) Set(level Level) error {
	leLev, err := logrus.ParseLevel(string(level))
	if err != nil {
		return err
	}
	l.entry.Logger.Level = leLev
	return nil
}

func (l logger) sourced() *logrus.Entry {
	_, file, line, ok := runtime.Caller(2)
	if !ok {
		file = "<???>"
		line = 1
	} else {
		slash := strings.LastIndex(file, "/")
		file = file[slash+1:]
	}
	return l.entry.WithField("src", fmt.Sprintf("%s:%d", file, line))
}

var baseLogger = &logger{
	entry: &logrus.Entry{
		Logger: logrus.New(),
	},
}

// Base returns the base logger
func Base() Logger {
	return baseLogger
}

// Debug logs debug message
func Debug(args ...interface{}) {
	baseLogger.sourced().Debug(args...)
}

// Debugln logs debug message
func Debugln(args ...interface{}) {
	baseLogger.sourced().Debugln(args...)
}

// Debugf logs debug message
func Debugf(format string, args ...interface{}) {
	baseLogger.sourced().Debugf(format, args...)
}

// Info logs info message
func Info(args ...interface{}) {
	baseLogger.sourced().Info(args...)
}

// Infoln logs info message
func Infoln(args ...interface{}) {
	baseLogger.sourced().Infoln(args...)
}

// Infof logs info message
func Infof(format string, args ...interface{}) {
	baseLogger.sourced().Infof(format, args...)
}

// Warn logs warn message
func Warn(args ...interface{}) {
	baseLogger.sourced().Warn(args...)
}

// Warnln logs warn message
func Warnln(args ...interface{}) {
	baseLogger.sourced().Warnln(args...)
}

// Warnf logs warn message
func Warnf(format string, args ...interface{}) {
	baseLogger.sourced().Warnf(format, args...)
}

// Error logs error message
func Error(args ...interface{}) {
	baseLogger.sourced().Error(args...)
}

// Errorln logs error message
func Errorln(args ...interface{}) {
	baseLogger.sourced().Errorln(args...)
}

// Errorf logs error message
func Errorf(format string, args ...interface{}) {
	baseLogger.sourced().Errorf(format, args...)
}

// Fatal logs fatal message
func Fatal(args ...interface{}) {
	baseLogger.sourced().Fatal(args...)
}

// Fatalln logs fatal message
func Fatalln(args ...interface{}) {
	baseLogger.sourced().Fatalln(args...)
}

// Fatalf logs fatal message
func Fatalf(format string, args ...interface{}) {
	baseLogger.sourced().Fatalf(format, args...)
}

// With adds a key:value to the logger
func With(key string, value interface{}) Logger {
	return baseLogger.With(key, value)
}

// WithField adds a key:value to the logger
func WithField(key string, value interface{}) Logger {
	return baseLogger.WithField(key, value)
}

// Set will set the logger level
func Set(level Level) error {
	return baseLogger.Set(level)
}

// Panic logs panic message
func Panic(args ...interface{}) {
	baseLogger.Panic(args...)
}

// Panicln logs panicln message
func Panicln(args ...interface{}) {
	baseLogger.Panicln(args...)
}

// Panicf logs panicln message
func Panicf(format string, args ...interface{}) {
	baseLogger.Panicf(format, args...)
}
<|endoftext|>