<commit_before>/*
Package manners provides a wrapper for a standard net/http server that
ensures all active HTTP client have completed their current request
before the server shuts down.

It can be used a drop-in replacement for the standard http package,
or can wrap a pre-configured Server.

eg.
	myHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	  w.Write([]byte("Hello\n"))
	})

	http.Handle("/hello", myHandler)

	log.Fatal(manners.ListenAndServe(":8080", nil))

or for a customized server:
	s := manners.NewWithServer(&http.Server{
		Addr:           ":8080",
		Handler:        myHandler,
		ReadTimeout:    10 * time.Second,
		WriteTimeout:   10 * time.Second,
		MaxHeaderBytes: 1 << 20,
	})
	log.Fatal(s.ListenAndServe())

The server will shutdown cleanly when the Close() method is called:

	go func() {
		sigchan := make(chan os.Signal, 1)
		signal.Notify(sigchan, os.Interrupt, os.Kill)
		<-sigchan
		log.Info("Shutting down...")
		manners.Close()
	}()

	http.Handle("/hello", myHandler)
	log.Fatal(manners.ListenAndServe(":8080", nil))
*/
package manners

import (
	"crypto/tls"
	"net"
	"net/http"
	"os"
	"sync"
	"sync/atomic"
)

// interface describing a waitgroup, so unit
// tests can mock out an instrumentable version
type waitgroup interface {
	Add(delta int)
	Done()
	Wait()
}

// StateHandler can be called by the server if the state of the connection changes.
// Notice that it passed previous state and the new state as parameters.
type StateHandler func(net.Conn, http.ConnState, http.ConnState)

type Options struct {
	Server       *http.Server
	StateHandler StateHandler
	Listener     net.Listener
}

// NewServer creates a new GracefulServer. The server will begin shutting down when
// a value is passed to the Shutdown channel.
func NewServer() *GracefulServer {
	return NewWithServer(new(http.Server))
}

// NewWithServer wraps an existing http.Server object and returns a GracefulServer
// that supports all of the original Server operations.
func NewWithServer(s *http.Server) *GracefulServer {
	return &GracefulServer{
		Server:   s,
		shutdown: make(chan struct{}),
		wg:       new(sync.WaitGroup),
	}
}

func NewWithOptions(o Options) *GracefulServer {
	// Set up listener
	var listener *GracefulListener
	if o.Listener != nil {
		g, ok := o.Listener.(*GracefulListener)
		if !ok {
			listener = NewListener(o.Listener)
		} else {
			listener = g
		}
	}

	return &GracefulServer{
		listener:     listener,
		Server:       o.Server,
		stateHandler: o.StateHandler,
		shutdown:     make(chan struct{}),
		wg:           new(sync.WaitGroup),
	}
}

// A GracefulServer maintains a WaitGroup that counts how many in-flight
// requests the server is handling. When it receives a shutdown signal,
// it stops accepting new requests but does not actually shut down until
// all in-flight requests terminate.
//
// GracefulServer embeds the underlying net/http.Server making its non-override
// methods and properties avaiable.
//
// It must be initialized by calling NewServer or NewWithServer
type GracefulServer struct {
	*http.Server
	shutdown chan struct{}
	wg       waitgroup
	listener *GracefulListener

	// used by test code
	up chan net.Listener

	stateHandler StateHandler
}

// Close stops the server from accepting new requets and beings shutting down.
func (s *GracefulServer) Close() {
	close(s.shutdown)
}

// ListenAndServe provides a graceful equivalent of net/http.Serve.ListenAndServe.
func (s *GracefulServer) ListenAndServe() error {
	if s.listener == nil {
		oldListener, err := net.Listen("tcp", s.Addr)
		if err != nil {
			return err
		}
		s.listener = NewListener(oldListener.(*net.TCPListener))
	}
	return s.Serve(s.listener)
}

// ListenAndServeTLS provides a graceful equivalent of net/http.Serve.ListenAndServeTLS.
func (s *GracefulServer) ListenAndServeTLS(certFile, keyFile string) error {
	addr := s.Addr
	if addr == "" {
		addr = ":https"
	}
	config := &tls.Config{}
	if s.TLSConfig != nil {
		*config = *s.TLSConfig
	}
	if config.NextProtos == nil {
		config.NextProtos = []string{"http/1.1"}
	}

	var err error
	config.Certificates = make([]tls.Certificate, 1)
	config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return err
	}

	return s.ListenAndServeTLSWithConfig(config)
}

// ListenAndServeTLS provides a graceful equivalent of net/http.Serve.ListenAndServeTLS.
func (s *GracefulServer) ListenAndServeTLSWithConfig(config *tls.Config) error {
	addr := s.Addr
	if addr == "" {
		addr = ":https"
	}

	if s.listener == nil {
		ln, err := net.Listen("tcp", addr)
		if err != nil {
			return err
		}

		tlsListener := NewTLSListener(TCPKeepAliveListener{ln.(*net.TCPListener)}, config)
		s.listener = NewListener(tlsListener)
	}
	return s.Serve(s.listener)
}

func (gs *GracefulServer) GetFile() (*os.File, error) {
	return gs.listener.GetFile()
}

func (gs *GracefulServer) HijackListener(s *http.Server, config *tls.Config) (*GracefulServer, error) {
	listener, err := gs.listener.Clone()
	if err != nil {
		return nil, err
	}

	if config != nil {
		listener = NewTLSListener(TCPKeepAliveListener{listener.(*net.TCPListener)}, config)
	}

	other := NewWithServer(s)
	other.listener = NewListener(listener)
	return other, nil
}

// Serve provides a graceful equivalent net/http.Server.Serve.
//
// If listener is not an instance of *GracefulListener is will be wrapped
// to become one.
func (s *GracefulServer) Serve(listener net.Listener) error {
	// accept a net.Listener to preserve the interface compatibility with the standard
	// http.Server, but we except a GracefluListener
	gracefulListener, ok := listener.(*GracefulListener)
	if !ok {
		gracefulListener = NewListener(listener)
		listener = gracefulListener
	}
	s.listener = gracefulListener

	var closing int32

	go func() {
		<-s.shutdown
		atomic.StoreInt32(&closing, 1)
		s.Server.SetKeepAlivesEnabled(false)
		listener.Close()
	}()

	orgConnState := s.Server.ConnState
	s.ConnState = func(conn net.Conn, newState http.ConnState) {
		gconn := conn.(*gracefulConn)
		switch newState {
		case http.StateNew:
			// new_conn -> StateNew
			s.StartRoutine()

		case http.StateActive:
			// (StateNew, StateIdle) -> StateActive
			if gconn.lastHTTPState == http.StateIdle {
				// transitioned from idle back to active
				s.StartRoutine()
			}

		case http.StateIdle:
			// StateActive -> StateIdle
			if atomic.LoadInt32(&closing) == 1 {
				// rapidly close newly idle connections; if not they may make
				// one more request before SetKeepAliveEnabled(false)  takes effect.
				conn.Close()
			}
			s.FinishRoutine()

		case http.StateClosed, http.StateHijacked:
			// (StateNew, StateActive, StateIdle) -> (StateClosed, StateHiJacked)
			if gconn.lastHTTPState != http.StateIdle {
				// if it was idle it's already been decremented
				s.FinishRoutine()
			}
		}
		if s.stateHandler != nil {
			s.stateHandler(conn, gconn.lastHTTPState, newState)
		}
		gconn.lastHTTPState = newState
		if orgConnState != nil {
			orgConnState(conn, newState)
		}
	}

	// only used by unit tests
	if s.up != nil {
		// notify test that server is up; wait for signal to continue
		s.up <- listener
	}
	err := s.Server.Serve(listener)

	// This block is reached when the server has received a shut down command.
	if err == nil {
		s.wg.Wait()
		return nil
	} else if _, ok := err.(listenerAlreadyClosed); ok {
		s.wg.Wait()
		return nil
	}
	return err
}

// StartRoutine increments the server's WaitGroup. Use this if a web request starts more
// goroutines and these goroutines are not guaranteed to finish before the
// request.
func (s *GracefulServer) StartRoutine() {
	s.wg.Add(1)
}

// FinishRoutine decrements the server's WaitGroup. Used this to complement StartRoutine().
func (s *GracefulServer) FinishRoutine() {
	s.wg.Done()
}

var (
	servers []*GracefulServer
	m       sync.Mutex
)

// ListenAndServe provides a graceful version of function provided by the net/http package.
func ListenAndServe(addr string, handler http.Handler) error {
	server := NewWithServer(&http.Server{Addr: addr, Handler: handler})
	m.Lock()
	servers = append(servers, server)
	m.Unlock()
	return server.ListenAndServe()
}

// ListenAndServeTLS provides a graceful version of function provided by the net/http package.
func ListenAndServeTLS(addr string, certFile string, keyFile string, handler http.Handler) error {
	server := NewWithServer(&http.Server{Addr: addr, Handler: handler})
	m.Lock()
	servers = append(servers, server)
	m.Unlock()
	return server.ListenAndServeTLS(certFile, keyFile)
}

// Serve provides a graceful version of function provided by the net/http package.
func Serve(l net.Listener, handler http.Handler) error {
	server := NewWithServer(&http.Server{Handler: handler})
	m.Lock()
	servers = append(servers, server)
	m.Unlock()
	return server.Serve(l)
}

// Close triggers a shutdown of all running Graceful servers.
func Close() {
	m.Lock()
	for _, s := range servers {
		s.Close()
	}
	servers = nil
	m.Unlock()
}
<commit_msg>expose waitgroup.Wait so shutdown handlers can wait on the server<commit_after>/*
Package manners provides a wrapper for a standard net/http server that
ensures all active HTTP client have completed their current request
before the server shuts down.

It can be used a drop-in replacement for the standard http package,
or can wrap a pre-configured Server.

eg.
	myHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	  w.Write([]byte("Hello\n"))
	})

	http.Handle("/hello", myHandler)

	log.Fatal(manners.ListenAndServe(":8080", nil))

or for a customized server:
	s := manners.NewWithServer(&http.Server{
		Addr:           ":8080",
		Handler:        myHandler,
		ReadTimeout:    10 * time.Second,
		WriteTimeout:   10 * time.Second,
		MaxHeaderBytes: 1 << 20,
	})
	log.Fatal(s.ListenAndServe())

The server will shutdown cleanly when the Close() method is called:

	go func() {
		sigchan := make(chan os.Signal, 1)
		signal.Notify(sigchan, os.Interrupt, os.Kill)
		<-sigchan
		log.Info("Shutting down...")
		manners.Close()
	}()

	http.Handle("/hello", myHandler)
	log.Fatal(manners.ListenAndServe(":8080", nil))
*/
package manners

import (
	"crypto/tls"
	"net"
	"net/http"
	"os"
	"sync"
	"sync/atomic"
)

// interface describing a waitgroup, so unit
// tests can mock out an instrumentable version
type waitgroup interface {
	Add(delta int)
	Done()
	Wait()
}

// StateHandler can be called by the server if the state of the connection changes.
// Notice that it passed previous state and the new state as parameters.
type StateHandler func(net.Conn, http.ConnState, http.ConnState)

type Options struct {
	Server       *http.Server
	StateHandler StateHandler
	Listener     net.Listener
}

// NewServer creates a new GracefulServer. The server will begin shutting down when
// a value is passed to the Shutdown channel.
func NewServer() *GracefulServer {
	return NewWithServer(new(http.Server))
}

// NewWithServer wraps an existing http.Server object and returns a GracefulServer
// that supports all of the original Server operations.
func NewWithServer(s *http.Server) *GracefulServer {
	return &GracefulServer{
		Server:   s,
		shutdown: make(chan struct{}),
		wg:       new(sync.WaitGroup),
	}
}

func NewWithOptions(o Options) *GracefulServer {
	// Set up listener
	var listener *GracefulListener
	if o.Listener != nil {
		g, ok := o.Listener.(*GracefulListener)
		if !ok {
			listener = NewListener(o.Listener)
		} else {
			listener = g
		}
	}

	return &GracefulServer{
		listener:     listener,
		Server:       o.Server,
		stateHandler: o.StateHandler,
		shutdown:     make(chan struct{}),
		wg:           new(sync.WaitGroup),
	}
}

// A GracefulServer maintains a WaitGroup that counts how many in-flight
// requests the server is handling. When it receives a shutdown signal,
// it stops accepting new requests but does not actually shut down until
// all in-flight requests terminate.
//
// GracefulServer embeds the underlying net/http.Server making its non-override
// methods and properties avaiable.
//
// It must be initialized by calling NewServer or NewWithServer
type GracefulServer struct {
	*http.Server
	shutdown chan struct{}
	wg       waitgroup
	listener *GracefulListener

	// used by test code
	up chan net.Listener

	stateHandler StateHandler
}

// Close stops the server from accepting new requets and beings shutting down.
func (s *GracefulServer) Close() {
	close(s.shutdown)
}

// ListenAndServe provides a graceful equivalent of net/http.Serve.ListenAndServe.
func (s *GracefulServer) ListenAndServe() error {
	if s.listener == nil {
		oldListener, err := net.Listen("tcp", s.Addr)
		if err != nil {
			return err
		}
		s.listener = NewListener(oldListener.(*net.TCPListener))
	}
	return s.Serve(s.listener)
}

// ListenAndServeTLS provides a graceful equivalent of net/http.Serve.ListenAndServeTLS.
func (s *GracefulServer) ListenAndServeTLS(certFile, keyFile string) error {
	addr := s.Addr
	if addr == "" {
		addr = ":https"
	}
	config := &tls.Config{}
	if s.TLSConfig != nil {
		*config = *s.TLSConfig
	}
	if config.NextProtos == nil {
		config.NextProtos = []string{"http/1.1"}
	}

	var err error
	config.Certificates = make([]tls.Certificate, 1)
	config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return err
	}

	return s.ListenAndServeTLSWithConfig(config)
}

// ListenAndServeTLS provides a graceful equivalent of net/http.Serve.ListenAndServeTLS.
func (s *GracefulServer) ListenAndServeTLSWithConfig(config *tls.Config) error {
	addr := s.Addr
	if addr == "" {
		addr = ":https"
	}

	if s.listener == nil {
		ln, err := net.Listen("tcp", addr)
		if err != nil {
			return err
		}

		tlsListener := NewTLSListener(TCPKeepAliveListener{ln.(*net.TCPListener)}, config)
		s.listener = NewListener(tlsListener)
	}
	return s.Serve(s.listener)
}

func (gs *GracefulServer) GetFile() (*os.File, error) {
	return gs.listener.GetFile()
}

func (gs *GracefulServer) HijackListener(s *http.Server, config *tls.Config) (*GracefulServer, error) {
	listener, err := gs.listener.Clone()
	if err != nil {
		return nil, err
	}

	if config != nil {
		listener = NewTLSListener(TCPKeepAliveListener{listener.(*net.TCPListener)}, config)
	}

	other := NewWithServer(s)
	other.listener = NewListener(listener)
	return other, nil
}

// Serve provides a graceful equivalent net/http.Server.Serve.
//
// If listener is not an instance of *GracefulListener is will be wrapped
// to become one.
func (s *GracefulServer) Serve(listener net.Listener) error {
	// accept a net.Listener to preserve the interface compatibility with the standard
	// http.Server, but we except a GracefluListener
	gracefulListener, ok := listener.(*GracefulListener)
	if !ok {
		gracefulListener = NewListener(listener)
		listener = gracefulListener
	}
	s.listener = gracefulListener

	var closing int32

	go func() {
		<-s.shutdown
		atomic.StoreInt32(&closing, 1)
		s.Server.SetKeepAlivesEnabled(false)
		listener.Close()
	}()

	orgConnState := s.Server.ConnState
	s.ConnState = func(conn net.Conn, newState http.ConnState) {
		gconn := conn.(*gracefulConn)
		switch newState {
		case http.StateNew:
			// new_conn -> StateNew
			s.StartRoutine()

		case http.StateActive:
			// (StateNew, StateIdle) -> StateActive
			if gconn.lastHTTPState == http.StateIdle {
				// transitioned from idle back to active
				s.StartRoutine()
			}

		case http.StateIdle:
			// StateActive -> StateIdle
			if atomic.LoadInt32(&closing) == 1 {
				// rapidly close newly idle connections; if not they may make
				// one more request before SetKeepAliveEnabled(false)  takes effect.
				conn.Close()
			}
			s.FinishRoutine()

		case http.StateClosed, http.StateHijacked:
			// (StateNew, StateActive, StateIdle) -> (StateClosed, StateHiJacked)
			if gconn.lastHTTPState != http.StateIdle {
				// if it was idle it's already been decremented
				s.FinishRoutine()
			}
		}
		if s.stateHandler != nil {
			s.stateHandler(conn, gconn.lastHTTPState, newState)
		}
		gconn.lastHTTPState = newState
		if orgConnState != nil {
			orgConnState(conn, newState)
		}
	}

	// only used by unit tests
	if s.up != nil {
		// notify test that server is up; wait for signal to continue
		s.up <- listener
	}
	err := s.Server.Serve(listener)

	// This block is reached when the server has received a shut down command.
	if err == nil {
		return nil
	} else if _, ok := err.(listenerAlreadyClosed); ok {
		return nil
	}

	return err
}

// StartRoutine increments the server's WaitGroup. Use this if a web request starts more
// goroutines and these goroutines are not guaranteed to finish before the
// request.
func (s *GracefulServer) StartRoutine() {
	s.wg.Add(1)
}

// FinishRoutine decrements the server's WaitGroup. Used this to complement StartRoutine().
func (s *GracefulServer) FinishRoutine() {
	s.wg.Done()
}

func (s *GracefulServer) Wait() {
	s.wg.Wait()
}

var (
	servers []*GracefulServer
	m       sync.Mutex
)

// ListenAndServe provides a graceful version of function provided by the net/http package.
func ListenAndServe(addr string, handler http.Handler) error {
	server := NewWithServer(&http.Server{Addr: addr, Handler: handler})
	m.Lock()
	servers = append(servers, server)
	m.Unlock()
	return server.ListenAndServe()
}

// ListenAndServeTLS provides a graceful version of function provided by the net/http package.
func ListenAndServeTLS(addr string, certFile string, keyFile string, handler http.Handler) error {
	server := NewWithServer(&http.Server{Addr: addr, Handler: handler})
	m.Lock()
	servers = append(servers, server)
	m.Unlock()
	return server.ListenAndServeTLS(certFile, keyFile)
}

// Serve provides a graceful version of function provided by the net/http package.
func Serve(l net.Listener, handler http.Handler) error {
	server := NewWithServer(&http.Server{Handler: handler})
	m.Lock()
	servers = append(servers, server)
	m.Unlock()
	return server.Serve(l)
}

// Close triggers a shutdown of all running Graceful servers.
func Close() {
	m.Lock()
	for _, s := range servers {
		s.Close()
	}
	servers = nil
	m.Unlock()
}
<|endoftext|>


<commit_before>package main

func serverFunc(quitChan chan struct{}) {
	for {
		select {
		case <-quitChan:
			return
		default:
			//do things
		}
	}
}
<commit_msg>server things again<commit_after>package main

import "net" //now look at this net that I just found

func handleConn(conn net.Conn) {
	//do things
}

func serverFunc(quitChan chan struct{}) {
	ln, _ := net.Listen("tcp", ":8080")
	for {
		select {
		case <-quitChan:
			return
		default:
			conn, _ := ln.Accept()
			go handleConn(conn)
		}
	}
}
<|endoftext|>


<commit_before>package mdns

import (
	"fmt"
	"log"
	"net"
	"sync"

	"github.com/miekg/dns"
)

const (
	ipv4mdns              = "224.0.0.251"
	ipv6mdns              = "ff02::fb"
	mdnsPort              = 5353
	forceUnicastResponses = false
)

var (
	ipv4Addr = &net.UDPAddr{
		IP:   net.ParseIP(ipv4mdns),
		Port: mdnsPort,
	}
	ipv6Addr = &net.UDPAddr{
		IP:   net.ParseIP(ipv6mdns),
		Port: mdnsPort,
	}
)

// Config is used to configure the mDNS server
type Config struct {
	// Zone must be provided to support responding to queries
	Zone Zone

	// Iface if provided binds the multicast listener to the given
	// interface. If not provided, the system default multicase interface
	// is used.
	Iface *net.Interface
}

// mDNS server is used to listen for mDNS queries and respond if we
// have a matching local record
type Server struct {
	config *Config

	ipv4List *net.UDPConn
	ipv6List *net.UDPConn

	shutdown     bool
	shutdownCh   chan struct{}
	shutdownLock sync.Mutex
}

// NewServer is used to create a new mDNS server from a config
func NewServer(config *Config) (*Server, error) {
	// Create the listeners
	ipv4List, _ := net.ListenMulticastUDP("udp4", config.Iface, ipv4Addr)
	ipv6List, _ := net.ListenMulticastUDP("udp6", config.Iface, ipv6Addr)

	// Check if we have any listener
	if ipv4List == nil && ipv6List == nil {
		return nil, fmt.Errorf("No multicast listeners could be started")
	}

	s := &Server{
		config:     config,
		ipv4List:   ipv4List,
		ipv6List:   ipv6List,
		shutdownCh: make(chan struct{}),
	}

	if ipv4List != nil {
		go s.recv(s.ipv4List)
	}

	if ipv6List != nil {
		go s.recv(s.ipv6List)
	}

	return s, nil
}

// Shutdown is used to shutdown the listener
func (s *Server) Shutdown() error {
	s.shutdownLock.Lock()
	defer s.shutdownLock.Unlock()

	if s.shutdown {
		return nil
	}
	s.shutdown = true
	close(s.shutdownCh)

	if s.ipv4List != nil {
		s.ipv4List.Close()
	}
	if s.ipv6List != nil {
		s.ipv6List.Close()
	}
	return nil
}

// recv is a long running routine to receive packets from an interface
func (s *Server) recv(c *net.UDPConn) {
	if c == nil {
		return
	}
	buf := make([]byte, 65536)
	for !s.shutdown {
		n, from, err := c.ReadFrom(buf)
		if err != nil {
			continue
		}
		if err := s.parsePacket(buf[:n], from); err != nil {
			log.Printf("[ERR] mdns: Failed to handle query: %v", err)
		}
	}
}

// parsePacket is used to parse an incoming packet
func (s *Server) parsePacket(packet []byte, from net.Addr) error {
	var msg dns.Msg
	if err := msg.Unpack(packet); err != nil {
		log.Printf("[ERR] mdns: Failed to unpack packet: %v", err)
		return err
	}
	return s.handleQuery(&msg, from)
}

// handleQuery is used to handle an incoming query
func (s *Server) handleQuery(query *dns.Msg, from net.Addr) error {
	if query.Opcode != dns.OpcodeQuery {
		// "In both multicast query and multicast response messages, the OPCODE MUST
		// be zero on transmission (only standard queries are currently supported
		// over multicast).  Multicast DNS messages received with an OPCODE other
		// than zero MUST be silently ignored."  Note: OpcodeQuery == 0
		return fmt.Errorf("mdns: received query with non-zero Opcode %v: %v", query.Opcode, *query)
	}
	if query.Rcode != 0 {
		// "In both multicast query and multicast response messages, the Response
		// Code MUST be zero on transmission.  Multicast DNS messages received with
		// non-zero Response Codes MUST be silently ignored."
		return fmt.Errorf("mdns: received query with non-zero Rcode %v: %v", query.Rcode, *query)
	}

	// TODO(reddaly): Handle "TC (Truncated) Bit":
	//    In query messages, if the TC bit is set, it means that additional
	//    Known-Answer records may be following shortly.  A responder SHOULD
	//    record this fact, and wait for those additional Known-Answer records,
	//    before deciding whether to respond.  If the TC bit is clear, it means
	//    that the querying host has no additional Known Answers.
	if query.Truncated {
		return fmt.Errorf("[ERR] mdns: support for DNS requests with high truncated bit not implemented: %v", *query)
	}

	var unicastAnswer, multicastAnswer []dns.RR

	// Handle each question
	for _, q := range query.Question {
		mrecs, urecs := s.handleQuestion(q)
		multicastAnswer = append(multicastAnswer, mrecs...)
		unicastAnswer = append(unicastAnswer, urecs...)
	}

	// See section 18 of RFC 6762 for rules about DNS headers.
	resp := func(unicast bool) *dns.Msg {
		// 18.1: ID (Query Identifier)
		// 0 for multicast response, query.Id for unicast response
		id := uint16(0)
		if unicast {
			id = query.Id
		}

		var answer []dns.RR
		if unicast {
			answer = unicastAnswer
		} else {
			answer = multicastAnswer
		}
		if len(answer) == 0 {
			return nil
		}

		return &dns.Msg{
			MsgHdr: dns.MsgHdr{
				Id: id,

				// 18.2: QR (Query/Response) Bit - must be set to 1 in response.
				Response: true,

				// 18.3: OPCODE - must be zero in response (OpcodeQuery == 0)
				Opcode: dns.OpcodeQuery,

				// 18.4: AA (Authoritative Answer) Bit - must be set to 1
				Authoritative: true,

				// The following fields must all be set to 0:
				// 18.5: TC (TRUNCATED) Bit
				// 18.6: RD (Recursion Desired) Bit
				// 18.7: RA (Recursion Available) Bit
				// 18.8: Z (Zero) Bit
				// 18.9: AD (Authentic Data) Bit
				// 18.10: CD (Checking Disabled) Bit
				// 18.11: RCODE (Response Code)
			},
			// 18.12 pertains to questions (handled by handleQuestion)
			// 18.13 pertains to resource records (handled by handleQuestion)

			// 18.14: Name Compression - responses should be compressed (though see
			// caveats in the RFC), so set the Compress bit (part of the dns library
			// API, not part of the DNS packet) to true.
			Compress: true,

			Answer: answer,
		}
	}

	if mresp := resp(false); mresp != nil {
		if err := s.sendResponse(mresp, from, false); err != nil {
			return fmt.Errorf("mdns: error sending multicast response: %v", err)
		}
	}
	if uresp := resp(true); uresp != nil {
		if err := s.sendResponse(uresp, from, true); err != nil {
			return fmt.Errorf("mdns: error sending unicast response: %v", err)
		}
	}
	return nil
}

// handleQuestion is used to handle an incoming question
//
// The response to a question may be transmitted over multicast, unicast, or
// both.  The return values are DNS records for each transmission type.
func (s *Server) handleQuestion(q dns.Question) (multicastRecs, unicastRecs []dns.RR) {
	records := s.config.Zone.Records(q)

	if len(records) == 0 {
		return nil, nil
	}

	// Handle unicast and multicast responses.
	// TODO(reddaly): The decision about sending over unicast vs. multicast is not
	// yet fully compliant with RFC 6762.  For example, the unicast bit should be
	// ignored if the records in question are close to TTL expiration.  For now,
	// we just use the unicast bit to make the decision, as per the spec:
	//     RFC 6762, section 18.12.  Repurposing of Top Bit of qclass in Question
	//     Section
	//
	//     In the Question Section of a Multicast DNS query, the top bit of the
	//     qclass field is used to indicate that unicast responses are preferred
	//     for this particular question.  (See Section 5.4.)
	if q.Qclass&(1<<15) != 0 || forceUnicastResponses {
		return nil, records
	}
	return records, nil
}

// sendResponse is used to send a response packet
func (s *Server) sendResponse(resp *dns.Msg, from net.Addr, unicast bool) error {
	// TODO(reddaly): Respect the unicast argument, and allow sending responses
	// over multicast.
	buf, err := resp.Pack()
	if err != nil {
		return err
	}

	// Determine the socket to send from
	addr := from.(*net.UDPAddr)
	if addr.IP.To4() != nil {
		_, err = s.ipv4List.WriteToUDP(buf, addr)
		return err
	} else {
		_, err = s.ipv6List.WriteToUDP(buf, addr)
		return err
	}
}
<commit_msg>New option in server config: DisableMulticastLoopback.<commit_after>package mdns

import (
	"fmt"
	"log"
	"net"
	"sync"

	"github.com/hashicorp/go.net/ipv4"
	"github.com/hashicorp/go.net/ipv6"
	"github.com/miekg/dns"
)

const (
	ipv4mdns              = "224.0.0.251"
	ipv6mdns              = "ff02::fb"
	mdnsPort              = 5353
	forceUnicastResponses = false
)

var (
	ipv4Addr = &net.UDPAddr{
		IP:   net.ParseIP(ipv4mdns),
		Port: mdnsPort,
	}
	ipv6Addr = &net.UDPAddr{
		IP:   net.ParseIP(ipv6mdns),
		Port: mdnsPort,
	}
)

// Config is used to configure the mDNS server
type Config struct {
	// Zone must be provided to support responding to queries
	Zone Zone

	// Iface if provided binds the multicast listener to the given
	// interface. If not provided, the system default multicase interface
	// is used.
	Iface *net.Interface

	// Whether to set the IP_MULTICAST_LOOP socket option on the multicast sockets
	// opened.  Setting this to true allows mDNS clients on the same machine to
	// discover the service. See
	// http://stackoverflow.com/questions/1719156/is-there-a-way-to-test-multicast-ip-on-same-box
	DisableMulticastLoopback bool
}

// mDNS server is used to listen for mDNS queries and respond if we
// have a matching local record
type Server struct {
	config *Config

	ipv4List *net.UDPConn
	ipv6List *net.UDPConn

	shutdown     bool
	shutdownCh   chan struct{}
	shutdownLock sync.Mutex
}

// NewServer is used to create a new mDNS server from a config
func NewServer(config *Config) (*Server, error) {
	// Create the listeners
	// TODO(reddaly): Handle errors returned by ListenMulticastUDP
	ipv4List, _ := net.ListenMulticastUDP("udp4", config.Iface, ipv4Addr)
	ipv6List, _ := net.ListenMulticastUDP("udp6", config.Iface, ipv6Addr)

	{
		p := ipv4.NewPacketConn(ipv4List)
		if err := p.SetMulticastLoopback(!config.DisableMulticastLoopback); err != nil {
			return nil, err
		}
	}
	{
		p := ipv6.NewPacketConn(ipv6List)
		if err := p.SetMulticastLoopback(!config.DisableMulticastLoopback); err != nil {
			return nil, err
		}
	}

	// Check if we have any listener
	if ipv4List == nil && ipv6List == nil {
		return nil, fmt.Errorf("No multicast listeners could be started")
	}

	s := &Server{
		config:     config,
		ipv4List:   ipv4List,
		ipv6List:   ipv6List,
		shutdownCh: make(chan struct{}),
	}

	if ipv4List != nil {
		go s.recv(s.ipv4List)
	}

	if ipv6List != nil {
		go s.recv(s.ipv6List)
	}

	return s, nil
}

// Shutdown is used to shutdown the listener
func (s *Server) Shutdown() error {
	s.shutdownLock.Lock()
	defer s.shutdownLock.Unlock()

	if s.shutdown {
		return nil
	}
	s.shutdown = true
	close(s.shutdownCh)

	if s.ipv4List != nil {
		s.ipv4List.Close()
	}
	if s.ipv6List != nil {
		s.ipv6List.Close()
	}
	return nil
}

// recv is a long running routine to receive packets from an interface
func (s *Server) recv(c *net.UDPConn) {
	if c == nil {
		return
	}
	buf := make([]byte, 65536)
	for !s.shutdown {
		n, from, err := c.ReadFrom(buf)
		if err != nil {
			continue
		}
		if err := s.parsePacket(buf[:n], from); err != nil {
			log.Printf("[ERR] mdns: Failed to handle query: %v", err)
		}
	}
}

// parsePacket is used to parse an incoming packet
func (s *Server) parsePacket(packet []byte, from net.Addr) error {
	var msg dns.Msg
	if err := msg.Unpack(packet); err != nil {
		log.Printf("[ERR] mdns: Failed to unpack packet: %v", err)
		return err
	}
	return s.handleQuery(&msg, from)
}

// handleQuery is used to handle an incoming query
func (s *Server) handleQuery(query *dns.Msg, from net.Addr) error {
	if query.Opcode != dns.OpcodeQuery {
		// "In both multicast query and multicast response messages, the OPCODE MUST
		// be zero on transmission (only standard queries are currently supported
		// over multicast).  Multicast DNS messages received with an OPCODE other
		// than zero MUST be silently ignored."  Note: OpcodeQuery == 0
		return fmt.Errorf("mdns: received query with non-zero Opcode %v: %v", query.Opcode, *query)
	}
	if query.Rcode != 0 {
		// "In both multicast query and multicast response messages, the Response
		// Code MUST be zero on transmission.  Multicast DNS messages received with
		// non-zero Response Codes MUST be silently ignored."
		return fmt.Errorf("mdns: received query with non-zero Rcode %v: %v", query.Rcode, *query)
	}

	// TODO(reddaly): Handle "TC (Truncated) Bit":
	//    In query messages, if the TC bit is set, it means that additional
	//    Known-Answer records may be following shortly.  A responder SHOULD
	//    record this fact, and wait for those additional Known-Answer records,
	//    before deciding whether to respond.  If the TC bit is clear, it means
	//    that the querying host has no additional Known Answers.
	if query.Truncated {
		return fmt.Errorf("[ERR] mdns: support for DNS requests with high truncated bit not implemented: %v", *query)
	}

	var unicastAnswer, multicastAnswer []dns.RR

	// Handle each question
	for _, q := range query.Question {
		mrecs, urecs := s.handleQuestion(q)
		multicastAnswer = append(multicastAnswer, mrecs...)
		unicastAnswer = append(unicastAnswer, urecs...)
	}

	// See section 18 of RFC 6762 for rules about DNS headers.
	resp := func(unicast bool) *dns.Msg {
		// 18.1: ID (Query Identifier)
		// 0 for multicast response, query.Id for unicast response
		id := uint16(0)
		if unicast {
			id = query.Id
		}

		var answer []dns.RR
		if unicast {
			answer = unicastAnswer
		} else {
			answer = multicastAnswer
		}
		if len(answer) == 0 {
			return nil
		}

		return &dns.Msg{
			MsgHdr: dns.MsgHdr{
				Id: id,

				// 18.2: QR (Query/Response) Bit - must be set to 1 in response.
				Response: true,

				// 18.3: OPCODE - must be zero in response (OpcodeQuery == 0)
				Opcode: dns.OpcodeQuery,

				// 18.4: AA (Authoritative Answer) Bit - must be set to 1
				Authoritative: true,

				// The following fields must all be set to 0:
				// 18.5: TC (TRUNCATED) Bit
				// 18.6: RD (Recursion Desired) Bit
				// 18.7: RA (Recursion Available) Bit
				// 18.8: Z (Zero) Bit
				// 18.9: AD (Authentic Data) Bit
				// 18.10: CD (Checking Disabled) Bit
				// 18.11: RCODE (Response Code)
			},
			// 18.12 pertains to questions (handled by handleQuestion)
			// 18.13 pertains to resource records (handled by handleQuestion)

			// 18.14: Name Compression - responses should be compressed (though see
			// caveats in the RFC), so set the Compress bit (part of the dns library
			// API, not part of the DNS packet) to true.
			Compress: true,

			Answer: answer,
		}
	}

	if mresp := resp(false); mresp != nil {
		if err := s.sendResponse(mresp, from, false); err != nil {
			return fmt.Errorf("mdns: error sending multicast response: %v", err)
		}
	}
	if uresp := resp(true); uresp != nil {
		if err := s.sendResponse(uresp, from, true); err != nil {
			return fmt.Errorf("mdns: error sending unicast response: %v", err)
		}
	}
	return nil
}

// handleQuestion is used to handle an incoming question
//
// The response to a question may be transmitted over multicast, unicast, or
// both.  The return values are DNS records for each transmission type.
func (s *Server) handleQuestion(q dns.Question) (multicastRecs, unicastRecs []dns.RR) {
	records := s.config.Zone.Records(q)

	if len(records) == 0 {
		return nil, nil
	}

	// Handle unicast and multicast responses.
	// TODO(reddaly): The decision about sending over unicast vs. multicast is not
	// yet fully compliant with RFC 6762.  For example, the unicast bit should be
	// ignored if the records in question are close to TTL expiration.  For now,
	// we just use the unicast bit to make the decision, as per the spec:
	//     RFC 6762, section 18.12.  Repurposing of Top Bit of qclass in Question
	//     Section
	//
	//     In the Question Section of a Multicast DNS query, the top bit of the
	//     qclass field is used to indicate that unicast responses are preferred
	//     for this particular question.  (See Section 5.4.)
	if q.Qclass&(1<<15) != 0 || forceUnicastResponses {
		return nil, records
	}
	return records, nil
}

// sendResponse is used to send a response packet
func (s *Server) sendResponse(resp *dns.Msg, from net.Addr, unicast bool) error {
	// TODO(reddaly): Respect the unicast argument, and allow sending responses
	// over multicast.
	buf, err := resp.Pack()
	if err != nil {
		return err
	}

	// Determine the socket to send from
	addr := from.(*net.UDPAddr)
	if addr.IP.To4() != nil {
		_, err = s.ipv4List.WriteToUDP(buf, addr)
		return err
	} else {
		_, err = s.ipv6List.WriteToUDP(buf, addr)
		return err
	}
}
<|endoftext|>


<commit_before>package explicitfilepath

import (
	"fmt"
	"os"
	"path/filepath"
	"testing"

	_ "github.com/containers/image/v5/internal/testing/explicitfilepath-tmpdir"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

type pathResolvingTestCase struct {
	setup    func(*testing.T, string) string
	expected string
}

var testCases = []pathResolvingTestCase{
	{ // A straightforward subdirectory hierarchy
		func(t *testing.T, top string) string {
			err := os.MkdirAll(filepath.Join(top, "dir1/dir2/dir3"), 0755)
			require.NoError(t, err)
			return "dir1/dir2/dir3"
		},
		"dir1/dir2/dir3",
	},
	{ // Missing component
		func(t *testing.T, top string) string {
			return "thisismissing/dir2"
		},
		"",
	},
	{ // Symlink on the path
		func(t *testing.T, top string) string {
			err := os.MkdirAll(filepath.Join(top, "dir1/dir2"), 0755)
			require.NoError(t, err)
			err = os.Symlink("dir1", filepath.Join(top, "link1"))
			require.NoError(t, err)
			return "link1/dir2"
		},
		"dir1/dir2",
	},
	{ // Trailing symlink
		func(t *testing.T, top string) string {
			err := os.MkdirAll(filepath.Join(top, "dir1/dir2"), 0755)
			require.NoError(t, err)
			err = os.Symlink("dir2", filepath.Join(top, "dir1/link2"))
			require.NoError(t, err)
			return "dir1/link2"
		},
		"dir1/dir2",
	},
	{ // Symlink pointing nowhere, as a non-final component
		func(t *testing.T, top string) string {
			err := os.Symlink("thisismissing", filepath.Join(top, "link1"))
			require.NoError(t, err)
			return "link1/dir2"
		},
		"",
	},
	{ // Trailing symlink pointing nowhere (but note that a missing non-symlink would be accepted)
		func(t *testing.T, top string) string {
			err := os.Symlink("thisismissing", filepath.Join(top, "link1"))
			require.NoError(t, err)
			return "link1"
		},
		"",
	},
	{ // Relative components in a path
		func(t *testing.T, top string) string {
			err := os.MkdirAll(filepath.Join(top, "dir1/dir2/dir3"), 0755)
			require.NoError(t, err)
			return "dir1/./dir2/../dir2/dir3"
		},
		"dir1/dir2/dir3",
	},
	{ // Trailing relative components
		func(t *testing.T, top string) string {
			err := os.MkdirAll(filepath.Join(top, "dir1/dir2"), 0755)
			require.NoError(t, err)
			return "dir1/dir2/.."
		},
		"dir1",
	},
	{ // Relative components in symlink
		func(t *testing.T, top string) string {
			err := os.MkdirAll(filepath.Join(top, "dir1/dir2"), 0755)
			require.NoError(t, err)
			err = os.Symlink("../dir1/dir2", filepath.Join(top, "dir1/link2"))
			require.NoError(t, err)
			return "dir1/link2"
		},
		"dir1/dir2",
	},
	{ // Relative component pointing "into" a symlink
		func(t *testing.T, top string) string {
			err := os.MkdirAll(filepath.Join(top, "dir1/dir2/dir3"), 0755)
			require.NoError(t, err)
			err = os.Symlink("dir3", filepath.Join(top, "dir1/dir2/link3"))
			require.NoError(t, err)
			return "dir1/dir2/link3/../.."
		},
		"dir1",
	},
	{ // Unreadable directory
		func(t *testing.T, top string) string {
			err := os.MkdirAll(filepath.Join(top, "unreadable/dir2"), 0755)
			require.NoError(t, err)
			err = os.Chmod(filepath.Join(top, "unreadable"), 000)
			require.NoError(t, err)
			return "unreadable/dir2"
		},
		"",
	},
}

func testPathsAreSameFile(t *testing.T, path1, path2, description string) {
	fi1, err := os.Stat(path1)
	require.NoError(t, err)
	fi2, err := os.Stat(path2)
	require.NoError(t, err)
	assert.True(t, os.SameFile(fi1, fi2), description)
}

func runPathResolvingTestCase(t *testing.T, f func(string) (string, error), c pathResolvingTestCase, suffix string) {
	topDir := t.TempDir()
	defer func() {
		// Clean up after the "Unreadable directory" case; os.RemoveAll just fails without this.
		_ = os.Chmod(filepath.Join(topDir, "unreadable"), 0755) // Ignore errors, especially if this does not exist.
	}()

	input := c.setup(t, topDir) + suffix // Do not call filepath.Join() on input, it calls filepath.Clean() internally!
	description := fmt.Sprintf("%s vs. %s%s", input, c.expected, suffix)

	fullOutput, err := ResolvePathToFullyExplicit(topDir + "/" + input)
	if c.expected == "" {
		assert.Error(t, err, description)
	} else {
		require.NoError(t, err, input)
		fullExpected := topDir + "/" + c.expected + suffix
		assert.Equal(t, fullExpected, fullOutput)

		// Either the two paths resolve to the same existing file, or to the same name in the same existing parent.
		if _, err := os.Lstat(fullExpected); err == nil {
			testPathsAreSameFile(t, fullOutput, fullExpected, description)
		} else {
			require.True(t, os.IsNotExist(err))
			_, err := os.Stat(fullOutput)
			require.Error(t, err)
			require.True(t, os.IsNotExist(err))

			parentExpected, fileExpected := filepath.Split(fullExpected)
			parentOutput, fileOutput := filepath.Split(fullOutput)
			assert.Equal(t, fileExpected, fileOutput)
			testPathsAreSameFile(t, parentOutput, parentExpected, description)
		}
	}
}

func TestResolvePathToFullyExplicit(t *testing.T) {
	for _, c := range testCases {
		runPathResolvingTestCase(t, ResolvePathToFullyExplicit, c, "")
		runPathResolvingTestCase(t, ResolvePathToFullyExplicit, c, "/trailing")
	}
}

func TestResolveExistingPathToFullyExplicit(t *testing.T) {
	for _, c := range testCases {
		runPathResolvingTestCase(t, resolveExistingPathToFullyExplicit, c, "")
	}
}
<commit_msg>Actually test the caller-requested function<commit_after>package explicitfilepath

import (
	"fmt"
	"os"
	"path/filepath"
	"testing"

	_ "github.com/containers/image/v5/internal/testing/explicitfilepath-tmpdir"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

type pathResolvingTestCase struct {
	setup    func(*testing.T, string) string
	expected string
}

var testCases = []pathResolvingTestCase{
	{ // A straightforward subdirectory hierarchy
		func(t *testing.T, top string) string {
			err := os.MkdirAll(filepath.Join(top, "dir1/dir2/dir3"), 0755)
			require.NoError(t, err)
			return "dir1/dir2/dir3"
		},
		"dir1/dir2/dir3",
	},
	{ // Missing component
		func(t *testing.T, top string) string {
			return "thisismissing/dir2"
		},
		"",
	},
	{ // Symlink on the path
		func(t *testing.T, top string) string {
			err := os.MkdirAll(filepath.Join(top, "dir1/dir2"), 0755)
			require.NoError(t, err)
			err = os.Symlink("dir1", filepath.Join(top, "link1"))
			require.NoError(t, err)
			return "link1/dir2"
		},
		"dir1/dir2",
	},
	{ // Trailing symlink
		func(t *testing.T, top string) string {
			err := os.MkdirAll(filepath.Join(top, "dir1/dir2"), 0755)
			require.NoError(t, err)
			err = os.Symlink("dir2", filepath.Join(top, "dir1/link2"))
			require.NoError(t, err)
			return "dir1/link2"
		},
		"dir1/dir2",
	},
	{ // Symlink pointing nowhere, as a non-final component
		func(t *testing.T, top string) string {
			err := os.Symlink("thisismissing", filepath.Join(top, "link1"))
			require.NoError(t, err)
			return "link1/dir2"
		},
		"",
	},
	{ // Trailing symlink pointing nowhere (but note that a missing non-symlink would be accepted)
		func(t *testing.T, top string) string {
			err := os.Symlink("thisismissing", filepath.Join(top, "link1"))
			require.NoError(t, err)
			return "link1"
		},
		"",
	},
	{ // Relative components in a path
		func(t *testing.T, top string) string {
			err := os.MkdirAll(filepath.Join(top, "dir1/dir2/dir3"), 0755)
			require.NoError(t, err)
			return "dir1/./dir2/../dir2/dir3"
		},
		"dir1/dir2/dir3",
	},
	{ // Trailing relative components
		func(t *testing.T, top string) string {
			err := os.MkdirAll(filepath.Join(top, "dir1/dir2"), 0755)
			require.NoError(t, err)
			return "dir1/dir2/.."
		},
		"dir1",
	},
	{ // Relative components in symlink
		func(t *testing.T, top string) string {
			err := os.MkdirAll(filepath.Join(top, "dir1/dir2"), 0755)
			require.NoError(t, err)
			err = os.Symlink("../dir1/dir2", filepath.Join(top, "dir1/link2"))
			require.NoError(t, err)
			return "dir1/link2"
		},
		"dir1/dir2",
	},
	{ // Relative component pointing "into" a symlink
		func(t *testing.T, top string) string {
			err := os.MkdirAll(filepath.Join(top, "dir1/dir2/dir3"), 0755)
			require.NoError(t, err)
			err = os.Symlink("dir3", filepath.Join(top, "dir1/dir2/link3"))
			require.NoError(t, err)
			return "dir1/dir2/link3/../.."
		},
		"dir1",
	},
	{ // Unreadable directory
		func(t *testing.T, top string) string {
			err := os.MkdirAll(filepath.Join(top, "unreadable/dir2"), 0755)
			require.NoError(t, err)
			err = os.Chmod(filepath.Join(top, "unreadable"), 000)
			require.NoError(t, err)
			return "unreadable/dir2"
		},
		"",
	},
}

func testPathsAreSameFile(t *testing.T, path1, path2, description string) {
	fi1, err := os.Stat(path1)
	require.NoError(t, err)
	fi2, err := os.Stat(path2)
	require.NoError(t, err)
	assert.True(t, os.SameFile(fi1, fi2), description)
}

func runPathResolvingTestCase(t *testing.T, f func(string) (string, error), c pathResolvingTestCase, suffix string) {
	topDir := t.TempDir()
	defer func() {
		// Clean up after the "Unreadable directory" case; os.RemoveAll just fails without this.
		_ = os.Chmod(filepath.Join(topDir, "unreadable"), 0755) // Ignore errors, especially if this does not exist.
	}()

	input := c.setup(t, topDir) + suffix // Do not call filepath.Join() on input, it calls filepath.Clean() internally!
	description := fmt.Sprintf("%s vs. %s%s", input, c.expected, suffix)

	fullOutput, err := f(topDir + "/" + input)
	if c.expected == "" {
		assert.Error(t, err, description)
	} else {
		require.NoError(t, err, input)
		fullExpected := topDir + "/" + c.expected + suffix
		assert.Equal(t, fullExpected, fullOutput)

		// Either the two paths resolve to the same existing file, or to the same name in the same existing parent.
		if _, err := os.Lstat(fullExpected); err == nil {
			testPathsAreSameFile(t, fullOutput, fullExpected, description)
		} else {
			require.True(t, os.IsNotExist(err))
			_, err := os.Stat(fullOutput)
			require.Error(t, err)
			require.True(t, os.IsNotExist(err))

			parentExpected, fileExpected := filepath.Split(fullExpected)
			parentOutput, fileOutput := filepath.Split(fullOutput)
			assert.Equal(t, fileExpected, fileOutput)
			testPathsAreSameFile(t, parentOutput, parentExpected, description)
		}
	}
}

func TestResolvePathToFullyExplicit(t *testing.T) {
	for _, c := range testCases {
		runPathResolvingTestCase(t, ResolvePathToFullyExplicit, c, "")
		runPathResolvingTestCase(t, ResolvePathToFullyExplicit, c, "/trailing")
	}
}

func TestResolveExistingPathToFullyExplicit(t *testing.T) {
	for _, c := range testCases {
		runPathResolvingTestCase(t, resolveExistingPathToFullyExplicit, c, "")
	}
}
<|endoftext|>


<commit_before><commit_msg>call constant<commit_after><|endoftext|>


<commit_before>package kuiperbelt

import (
	"errors"
	"io"
	"log"
	"net/http"

	"golang.org/x/net/websocket"
)

var (
	sessionKeyNotExistError            = errors.New("session key is not exist.")
	connectCallbackIsNotAvailableError = errors.New("connect callback is not available.")
	callbackClient                     = new(http.Client)
)

type WebSocketServer struct {
	Config Config
}

func (s *WebSocketServer) WebSocketHandler(ws *websocket.Conn) {
	session, err := s.NewWebSocketSession(ws)
	if err != nil {
		log.Println("connect error:", err)
		return
	}
	AddSession(session)
	defer DelSession(session.Key())
	log.Println("connected key:", session.Key())
	session.WaitClose()
}

func (s *WebSocketServer) Register() {
	http.HandleFunc("/connect", func(w http.ResponseWriter, r *http.Request) {
		s := websocket.Server{Handler: websocket.Handler(s.WebSocketHandler)}
		s.ServeHTTP(w, r)
	})
}

type WebSocketSession struct {
	*websocket.Conn
	key     string
	closeCh chan struct{}
	Config  Config
}

func (s *WebSocketServer) NewWebSocketSession(ws *websocket.Conn) (*WebSocketSession, error) {
	req := ws.Request()
	defer req.Body.Close()
	callbackRequest, err := http.NewRequest(req.Method, s.Config.Callback.Connect, req.Body)
	if err != nil {
		return nil, err
	}
	callbackRequest.Header = req.Header
	resp, err := callbackClient.Do(callbackRequest)
	if err != nil {
		return nil, connectCallbackIsNotAvailableError
	}
	defer resp.Body.Close()
	key := resp.Header.Get(s.Config.SessionHeader)
	if key == "" {
		return nil, sessionKeyNotExistError
	}
	closeCh := make(chan struct{})
	session := &WebSocketSession{ws, key, closeCh, s.Config}
	io.Copy(session, resp.Body)

	return session, nil
}

func (s *WebSocketSession) Key() string {
	return s.key
}

func (s *WebSocketSession) Close() error {
	err := s.Conn.Close()
	if err != nil {
		return err
	}
	s.closeCh <- struct{}{}
	return nil
}

func (s *WebSocketSession) WaitClose() {
	<-s.closeCh
}
<commit_msg>fix: duplicate variable name<commit_after>package kuiperbelt

import (
	"errors"
	"io"
	"log"
	"net/http"

	"golang.org/x/net/websocket"
)

var (
	sessionKeyNotExistError            = errors.New("session key is not exist.")
	connectCallbackIsNotAvailableError = errors.New("connect callback is not available.")
	callbackClient                     = new(http.Client)
)

type WebSocketServer struct {
	Config Config
}

func (s *WebSocketServer) WebSocketHandler(ws *websocket.Conn) {
	session, err := s.NewWebSocketSession(ws)
	if err != nil {
		log.Println("connect error:", err)
		return
	}
	AddSession(session)
	defer DelSession(session.Key())
	log.Println("connected key:", session.Key())
	session.WaitClose()
}

func (s *WebSocketServer) Register() {
	http.HandleFunc("/connect", func(w http.ResponseWriter, r *http.Request) {
		server := websocket.Server{Handler: websocket.Handler(s.WebSocketHandler)}
		server.ServeHTTP(w, r)
	})
}

type WebSocketSession struct {
	*websocket.Conn
	key     string
	closeCh chan struct{}
	Config  Config
}

func (s *WebSocketServer) NewWebSocketSession(ws *websocket.Conn) (*WebSocketSession, error) {
	req := ws.Request()
	defer req.Body.Close()
	callbackRequest, err := http.NewRequest(req.Method, s.Config.Callback.Connect, req.Body)
	if err != nil {
		return nil, err
	}
	callbackRequest.Header = req.Header
	resp, err := callbackClient.Do(callbackRequest)
	if err != nil {
		return nil, connectCallbackIsNotAvailableError
	}
	defer resp.Body.Close()
	key := resp.Header.Get(s.Config.SessionHeader)
	if key == "" {
		return nil, sessionKeyNotExistError
	}
	closeCh := make(chan struct{})
	session := &WebSocketSession{ws, key, closeCh, s.Config}
	io.Copy(session, resp.Body)

	return session, nil
}

func (s *WebSocketSession) Key() string {
	return s.key
}

func (s *WebSocketSession) Close() error {
	err := s.Conn.Close()
	if err != nil {
		return err
	}
	s.closeCh <- struct{}{}
	return nil
}

func (s *WebSocketSession) WaitClose() {
	<-s.closeCh
}
<|endoftext|>


<commit_before>// +build ignore

package main

import (
	"encoding/json"
	"flag"
	"log"
	"math/rand"
	"net/http"
	"strconv"
	"time"

	"github.com/gorilla/websocket"
	"fmt"
)

var addr = flag.String("addr", "localhost:8080", "http service address")

// Upgrader specifies parameters for upgrading an HTTP connection to a WebSocket connection.
// Without parameters, default options will be applied (ReadBufferSize WriteBufferSize are set to 4096
var upgrader = websocket.Upgrader{} // use default options

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

	token := r.URL.Query().Get("token")

	fmt.Println("token=", token)

	// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
	// c (i.e. Conn type) represents a WebSocket connection.
	c, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Print("upgrade:", err)
		return
	}

	defer c.Close()

	for {

		id := strconv.Itoa(rand.Intn(4))

		var action Action

		switch id {
		case "1":
			action = Action{
				Name:       "action" + id,
				Parameters: []ActionParameter{{Name: "param", Value: int(1)}},
			}

		case "2":
			action = Action{
				Name:       "action" + id,
				Parameters: []ActionParameter{{Name: "param", Value: "2"}},
			}

		case "3":
			action = Action{
				Name:       "action" + id,
				Parameters: []ActionParameter{{Name: "param1", Value: 3.3}, {Name: "param2", Value: true}},
			}

		default:
			action = Action{
				Name: "action" + id,
			}
		}

		jsonAction, err := json.Marshal(action)
		if err != nil {
			log.Println("marshal:", err)
			return
		}

		err = c.WriteMessage(websocket.TextMessage, []byte(jsonAction))
		if err != nil {
			log.Println("write:", err)
			break
		}

		log.Printf("sent: %s", jsonAction)

		// messageType int, message []byte, err error
		messageType, message, err := c.ReadMessage()
		if err != nil {
			log.Println("read:", err)
			break
		}

		log.Printf("recv: %s (type=%d)", message, messageType)

		time.Sleep(time.Second * 2)
	}
}

func main() {
	flag.Parse()
	log.SetFlags(0)
	http.HandleFunc("/action", handleActionFunc)
	log.Fatal(http.ListenAndServe(*addr, nil))
}
<commit_msg>Manage several clients by storing websocket connections<commit_after>// +build ignore

package main

import (
	"encoding/json"
	"flag"
	"log"
	"math/rand"
	"net/http"
	"strconv"
	"bufio"
	"os"
	"fmt"

	"github.com/gorilla/websocket"
)

var addr = flag.String("addr", "localhost:8080", "http service address")

// Upgrader specifies parameters for upgrading an HTTP connection to a WebSocket connection.
// Without parameters, default options will be applied (ReadBufferSize WriteBufferSize are set to 4096
var upgrader = websocket.Upgrader{} // use default options

type WebSocketStore struct {
	token  string
	socket *websocket.Conn
}

var clients []WebSocketStore

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

	t := r.URL.Query().Get("token")

	// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
	// c (i.e. Conn type) represents a WebSocket connection.
	c, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Print("upgrade:", err)
		return
	}

	clients = append(clients, WebSocketStore{token: t, socket: c})

	// TODO: find when to close the WebSocket
	//defer c.Close()
}

func sendAction(c *websocket.Conn) {

	id := strconv.Itoa(rand.Intn(4))

	var action Action

	switch id {
	case "1":
		action = Action{
			Name:       "action" + id,
			Parameters: []ActionParameter{{Name: "param", Value: int(1)}},
		}

	case "2":
		action = Action{
			Name:       "action" + id,
			Parameters: []ActionParameter{{Name: "param", Value: "2"}},
		}

	case "3":
		action = Action{
			Name:       "action" + id,
			Parameters: []ActionParameter{{Name: "param1", Value: 3.3}, {Name: "param2", Value: true}},
		}

	default:
		action = Action{
			Name: "action" + id,
		}
	}

	jsonAction, err := json.Marshal(action)
	if err != nil {
		log.Println("marshal:", err)
		return
	}

	err = c.WriteMessage(websocket.TextMessage, []byte(jsonAction))
	if err != nil {
		log.Println("write:", err)
		return
	}

	log.Printf("sent: %s", jsonAction)

	// messageType int, message []byte, err error
	messageType, message, err := c.ReadMessage()
	if err != nil {
		log.Println("read:", err)
		return
	}

	log.Printf("recv: %s (type=%d)", message, messageType)
}

func main() {
	flag.Parse()
	log.SetFlags(0)

	clients = make([]WebSocketStore, 0)
	go func() {
		reader := bufio.NewReader(os.Stdin)

		for {

			for {
				if len(clients) > 0 {
					break
				}
				fmt.Println("No client for now. Press [enter] to refresh")
				reader.ReadString('\n')
			}

			fmt.Println("List of the clients:")
			for i, v := range clients {
				fmt.Printf("%d- %s\n", i, v.token)
			}
			fmt.Print("Choose a client: ")
			var choice int
			fmt.Scanf("%d", &choice)

			sendAction(clients[choice].socket)
		}
	}()
	http.HandleFunc("/action", handleActionFunc)
	log.Fatal(http.ListenAndServe(*addr, nil))
}
<|endoftext|>


<commit_before>package torrent_center

import (
	"net/http"
	"github.com/bamboV/torrent"
	"encoding/json"
	"strconv"
	"strings"
)

type Server struct{
	center Center
}

func NewServer(center Center) Server {
	return Server{
		center: center,
	}
}

func (s *Server) Start () {
	mux := http.NewServeMux()
	mux.HandleFunc("/trackers", func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case http.MethodGet:
			s.write(s.center.GetTrackers(), w)
			break
		case http.MethodPost:
			name := r.PostForm.Get("name")
			url := r.PostForm.Get("url")
			if name == "" || url == "" {
				w.WriteHeader(400)
				return
			}
			tracker := Tracker{
				Name: name,
				Url: url,
			}
			s.write(s.center.AddTracker(tracker), w)
			break
		}
	})
	mux.HandleFunc("/trackers/", func(w http.ResponseWriter, r *http.Request) {
		name := strings.Trim(r.URL.Path, "/trackers/")
		switch r.Method {
		case http.MethodGet:
			s.write(s.center.GetTracker(name), w)
			break
		case http.MethodDelete:
			s.center.DeleteTracker(Tracker{Name: name})
			break
		case http.MethodPut:
			name := r.Form.Get("name")
			url := r.Form.Get("url")
			if name == "" || url == "" {
				w.WriteHeader(400)
				return
			}
			tracker := Tracker{
				Name: name,
				Url: url,
			}
			s.write(s.center.UpdateTracker(tracker), w)
			break
		}
	})

	mux.HandleFunc("/distributions", func(w http.ResponseWriter, r *http.Request) {
		phrase := r.URL.Query().Get("phrase")

		if phrase == "" {
			w.WriteHeader(400)
			return
		}

		s.write(s.searchInAllTrackers(phrase),w)
	})

	mux.HandleFunc("/distributions/", func(w http.ResponseWriter, r *http.Request) {
		params := strings.Trim(r.URL.Path, "/distributions/")

		split := strings.Split(params, "/")

		trackerName := split[0]

		tracker := s.center.GetTracker(trackerName)

		if tracker.Url == "" {
			w.WriteHeader(404)
			return
		}

		if len(split) > 1 {
			id, err := strconv.Atoi(split[1])

			if err != nil {
				w.WriteHeader(400)
				return
			}

			s.write(tracker.Get(id), w)
		} else {
			phrase := r.URL.Query().Get("phrase")
			if phrase == "" {
				w.WriteHeader(400)
				return
			}
			s.write(tracker.Search(phrase), w)
		}
	})

	mux.HandleFunc("/magnet", func(w http.ResponseWriter, r *http.Request) {
		magnet := r.PostForm.Get("magnet")
		if magnet == ""  {
			w.WriteHeader(500)
			return
		}

		if s.center.Download(magnet) {
			w.WriteHeader(200)
		} else {
			w.WriteHeader(500)
		}
	})

	err := http.ListenAndServe(":80", mux)

	if err != nil {
		panic(err)
	}
}

func (s *Server) write(object interface{}, w http.ResponseWriter) {
	str, _ := json.Marshal(object)
	w.Write(str)
}

func (s *Server) getDistribution(trackerName string, id int) torrent.Distribution {
	tracker := s.center.GetTracker(trackerName)

	return tracker.Get(id)
}

func (s *Server) search(trackerName string, phrase string) []torrent.Distribution {
	tracker := s.center.GetTracker(trackerName)

	return tracker.Search(phrase)
}

func (s *Server) searchInAllTrackers(phrase string) []TrackerSearchResult {
	trackers := s.center.GetTrackers()

	result := []TrackerSearchResult{}

	for _, value := range trackers {
		search := value.Search(phrase)
		if len(search) > 0 {
			result = append(result, TrackerSearchResult{
				Name: value.Name,
				Items: search,
			})
		}
	}

	return result
}

<commit_msg>Fixed params parsing<commit_after>package torrent_center

import (
	"net/http"
	"github.com/bamboV/torrent"
	"encoding/json"
	"strconv"
	"strings"
)

type Server struct{
	center Center
}

func NewServer(center Center) Server {
	return Server{
		center: center,
	}
}

type trackerRequest struct {
	name string
	url string
}

type magnetRequest struct {
	magnet string
}

func (s *Server) Start () {
	mux := http.NewServeMux()
	mux.HandleFunc("/trackers", func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case http.MethodGet:
			s.write(s.center.GetTrackers(), w)
			break
		case http.MethodPost:
			decoder := json.NewDecoder(r.Body)
			reqParams := trackerRequest{}
			err := decoder.Decode(reqParams)

			if err != nil || reqParams.name == "" || reqParams.url == "" {
				w.WriteHeader(400)
				return
			}
			tracker := Tracker{
				Name: reqParams.name,
				Url: reqParams.url,
			}
			s.write(s.center.AddTracker(tracker), w)
			break
		}
	})
	mux.HandleFunc("/trackers/", func(w http.ResponseWriter, r *http.Request) {
		name := strings.Trim(r.URL.Path, "/trackers/")
		switch r.Method {
		case http.MethodGet:
			s.write(s.center.GetTracker(name), w)
			break
		case http.MethodDelete:
			s.center.DeleteTracker(Tracker{Name: name})
			break
		case http.MethodPut:
			decoder := json.NewDecoder(r.Body)
			reqParams := trackerRequest{}
			err := decoder.Decode(reqParams)

			if err != nil || reqParams.name == "" || reqParams.url == "" {
				w.WriteHeader(400)
				return
			}
			tracker := Tracker{
				Name: reqParams.name,
				Url: reqParams.url,
			}
			s.write(s.center.UpdateTracker(tracker), w)
			break
		}
	})

	mux.HandleFunc("/distributions", func(w http.ResponseWriter, r *http.Request) {
		phrase := r.URL.Query().Get("phrase")

		if phrase == "" {
			w.WriteHeader(400)
			return
		}

		s.write(s.searchInAllTrackers(phrase),w)
	})

	mux.HandleFunc("/distributions/", func(w http.ResponseWriter, r *http.Request) {
		params := strings.Trim(r.URL.Path, "/distributions/")

		split := strings.Split(params, "/")

		trackerName := split[0]

		tracker := s.center.GetTracker(trackerName)

		if tracker.Url == "" {
			w.WriteHeader(404)
			return
		}

		if len(split) > 1 {
			id, err := strconv.Atoi(split[1])

			if err != nil {
				w.WriteHeader(400)
				return
			}

			s.write(tracker.Get(id), w)
		} else {
			phrase := r.URL.Query().Get("phrase")
			if phrase == "" {
				w.WriteHeader(400)
				return
			}
			s.write(tracker.Search(phrase), w)
		}
	})

	mux.HandleFunc("/magnet", func(w http.ResponseWriter, r *http.Request) {
		decoder := json.NewDecoder(r.Body)
		reqParams := magnetRequest{}
		err := decoder.Decode(reqParams)

		if err != nil || reqParams.magnet == "" {
			w.WriteHeader(500)
			return
		}

		if s.center.Download(reqParams.magnet) {
			w.WriteHeader(200)
		} else {
			w.WriteHeader(500)
		}
	})

	err := http.ListenAndServe(":80", mux)

	if err != nil {
		panic(err)
	}
}

func (s *Server) decodeRequestBody () trackerRequest {

}

func (s *Server) write(object interface{}, w http.ResponseWriter) {
	str, _ := json.Marshal(object)
	w.Write(str)
}

func (s *Server) getDistribution(trackerName string, id int) torrent.Distribution {
	tracker := s.center.GetTracker(trackerName)

	return tracker.Get(id)
}

func (s *Server) search(trackerName string, phrase string) []torrent.Distribution {
	tracker := s.center.GetTracker(trackerName)

	return tracker.Search(phrase)
}

func (s *Server) searchInAllTrackers(phrase string) []TrackerSearchResult {
	trackers := s.center.GetTrackers()

	result := []TrackerSearchResult{}

	for _, value := range trackers {
		search := value.Search(phrase)
		if len(search) > 0 {
			result = append(result, TrackerSearchResult{
				Name: value.Name,
				Items: search,
			})
		}
	}

	return result
}

<|endoftext|>


<commit_before>// Package hath is Hentai@Home client implementation in golang
package hath // import "cydev.ru/hath"

import (
	"crypto/sha1"
	"encoding/hex"
	"errors"
	"io"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"sync"
	"time"

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

// Server should handle requests from users (and rpc?)
// speedtests
// server commands:
//     /servercmd/<command>/<additional:kwds>/<timestamp:int>/<key>'
// image file request:
//     /h/<fileid>/<additional:kwds>/<filename>
type Server interface {
	http.Handler
}

type speedTest struct{}

func (s speedTest) Read(b []byte) (n int, err error) {
	n = len(b)
	return n, err
}

// DefaultServer uses hard drive to respond
type DefaultServer struct {
	api        *Client
	cfg        ServerConfig
	frontend   Frontend
	db         DataBase
	e          *gin.Engine
	useQuery   chan File
	wg         *sync.WaitGroup
	started    bool
	commands   map[string]commandHandler
	stop       chan bool
	updateLock sync.Locker
}

const (
	argsDelimiter       = ";"
	argsEqual           = "="
	argsKeystamp        = "keystamp"
	timestampMaxDelta   = 60
	useQuerySize        = 100
	tmpFolder           = "tmp"
	cmdKeyStart         = "hentai@home-servercmd"
	cmdKeyDelimiter     = "-"
	size100MB           = 10 * size10MB
	headerContentLength = "Content-Length"
)

// ProxyMode sets proxy security politics
type ProxyMode byte

const (
	// ProxyDisabled no requests allowed
	ProxyDisabled ProxyMode = iota + 1 // starts with 1
	// ProxyLocalNetworksProtected allows requests from local network with passkey
	ProxyLocalNetworksProtected // 2
	// ProxyLocalNetworksOpen allows any requests from local network
	ProxyLocalNetworksOpen // 3
	// ProxyAllNetworksProtected allows requests from any network with passkey (not recommended)
	ProxyAllNetworksProtected // 4
	// ProxyAllNetworksOpen allows any requests from any network (very not recommended)
	ProxyAllNetworksOpen // 5
)

const (
	proxyModeMin  = int64(ProxyDisabled)
	proxyModeMax  = int64(ProxyAllNetworksOpen)
	proxyModeBits = 4
)

func (p ProxyMode) String() string {
	if p == ProxyDisabled {
		return "disabled"
	}
	if p == ProxyLocalNetworksOpen {
		return "open for local networks"
	}
	if p == ProxyAllNetworksProtected {
		return "local networks with passkey"
	}
	if p == ProxyAllNetworksProtected {
		return "all networks with passkey"
	}
	if p == ProxyAllNetworksOpen {
		return "open for all networks"
	}
	return "unknown"
}

// Args represents additional arguments in request string
type Args map[string]string

// Get returns string value
func (a Args) Get(k string) string {
	return a[k]
}

// GetInt parses and returns integer value
func (a Args) GetInt(k string) int {
	i, _ := strconv.Atoi(a[k])
	return i
}

// GetInt64 parses and returns 64bit integer value
func (a Args) GetInt64(k string) int64 {
	i, _ := strconv.ParseInt(a[k], 10, 64)
	return i
}

func (a Args) String() string {
	var elems []string
	for k, v := range a {
		elems = append(elems, strings.Join([]string{k, v}, argsEqual))
	}
	return strings.Join(elems, argsDelimiter)
}

// ParseArgs parses arguments from string
//     arg1=val1;arg2=val2
func ParseArgs(s string) (a Args) {
	a = make(Args)
	components := strings.Split(s, argsDelimiter)
	for _, component := range components {
		kw := strings.Split(component, argsEqual)
		if len(kw) != 2 {
			continue
		}
		a[kw[0]] = kw[1]
	}
	return a
}

// handleImage /h/<fileid>/<additional:kwds>/<filename>
func (s *DefaultServer) handleImage(c *gin.Context) {
	fileID := c.Param("fileid")
	args := ParseArgs(c.Param("kwds"))
	// parsing timestamp and keystamp
	stamps := strings.Split(args.Get(argsKeystamp), keyStampDelimiter)
	if len(stamps) != 2 {
		c.String(http.StatusBadRequest, "400: Bad stamp format")
		return
	}
	timestamp, err := strconv.ParseInt(stamps[0], intBase, 64)
	if err != nil {
		c.String(http.StatusBadRequest, "400: Bad timestamp")
		return
	}
	if !s.isDeltaValid(stamps[0]) {
		c.String(http.StatusBadRequest, "400: timestamp delta is too big")
		return
	}
	keyStamp := stamps[1]
	f, err := FileFromID(fileID)
	if err != nil {
		c.String(http.StatusBadRequest, "400: bad file id")
		return
	}
	expectedKeyStamp := f.KeyStamp(s.cfg.Key, timestamp)
	if expectedKeyStamp != keyStamp && !s.cfg.DontCheckSHA1 {
		c.String(http.StatusForbidden, "403: bad keystamp")
		return
	}
	s.useQuery <- f
	s.frontend.Handle(f, c.Writer)
}

func getSHA1(sep string, args []string) string {
	hasher := sha1.New()
	toHash := strings.Join(args, sep)
	h := hasher.Sum([]byte(toHash))
	return hex.EncodeToString(h)
}

type commandHandler func(*gin.Context, Args)

func (s *DefaultServer) isDeltaValid(ts string) bool {
	if s.cfg.DontCheckTimestamps {
		return true
	}
	timestamp, err := strconv.ParseInt(ts, intBase, 64)
	if err != nil {
		return false
	}
	delta := time.Now().Unix() - timestamp
	if delta < 0 {
		delta *= -1
	}
	return delta < maximumTimeLag
}

var (
	// ErrNoFilesToRemove is flag that there is 0 files to remove
	ErrNoFilesToRemove = errors.New("No more unused files")
)

func (s *DefaultServer) removeUnused(deadline time.Time) error {

	files, err := s.db.GetOldFiles(maxRemoveCount, deadline)
	if err != nil {
		return err
	}
	if len(files) == 0 {
		return nil
	}
	if err := s.api.RemoveFiles(files); err != nil {
		return err
	}
	if err := s.db.RemoveBatch(files); err != nil {
		return err
	}
	if err := s.frontend.RemoveBatch(files); err != nil {
		return err
	}
	return nil
}

func (s *DefaultServer) removeAllUnused(deadline time.Time) error {
	log.Println("removing old files")
	defer log.Println("removing old files completed")
	for {
		err := s.removeUnused(deadline)
		// just iterating until error occurs
		if err == nil {
			continue
		}
		// error occured
		// unexpected error check
		if err != ErrNoFilesToRemove {
			return err
		}
		// breaking inner loop
		return nil
	}
}

func (s *DefaultServer) removeLoop() {
	ticker := time.NewTicker(time.Minute)
	defer ticker.Stop()
	defer s.wg.Done()
	for {
		select {
		case t := <-ticker.C:
			deadline := t.Add(-1 * s.cfg.RemoveTimeout)
			count, err := s.db.GetOldFilesCount(deadline)
			if err != nil {
				log.Println("error while getting old files count", err)
			}
			if count == 0 {
				continue
			}
			s.updateLock.Lock()
			defer s.updateLock.Unlock()
			if err := s.removeAllUnused(deadline); err != nil {
				log.Println("error while removing files", err)
			}
		case _ = <-s.stop:
			return
		}
	}
}

// /servercmd/<command>/<additional:kwds>/<timestamp:int>/<key>
func (s *DefaultServer) handleCommand(c *gin.Context) {
	// checking crypto sign
	// parsing params
	key := c.Param("key")
	kwds := c.Param("kwds")
	args := ParseArgs(kwds)
	timestampS := c.Param("timestamp")
	command := c.Param("command")
	sClientID := strconv.FormatInt(s.cfg.ClientID, intBase)

	// checkign timestamp delta
	if !s.isDeltaValid(timestampS) {
		c.String(http.StatusUnauthorized, "403: bad timestamp")
		return
	}

	hashArgs := []string{
		cmdKeyStart,
		kwds,
		sClientID,
		timestampS,
		s.cfg.Key,
	}
	keyCalculated := getSHA1(cmdKeyDelimiter, hashArgs)
	if key != keyCalculated && !s.cfg.DontCheckSHA1 {
		c.String(http.StatusUnauthorized, "403: bad sign")
		return
	}
	handler, ok := s.commands[command]
	if !ok {
		// handler for command not found
		c.String(http.StatusNotFound, "404: command not found")
		return
	}
	handler(c, args)
}

func (s *DefaultServer) commandSpeedTest(c *gin.Context, args Args) {
	size := args.GetInt64("testsize")
	if size <= 0 || size > size100MB {
		size = size100MB
	}
	c.Header(headerContentLength, strconv.FormatInt(size, intBase))
	_, err := io.CopyN(c.Writer, speedTest{}, size)
	if err != nil {
		log.Println("speed test error", err)
	}
}

func (s *DefaultServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	s.e.ServeHTTP(w, r)
}

// lastUsage update loop
func (s *DefaultServer) useLoop() {
	defer s.wg.Done()
	ticker := time.NewTicker(s.cfg.UpdateRate)
	defer ticker.Stop()

	var files []File
	update := func() {
		if files == nil {
			return
		}
		s.updateLock.Lock()
		defer s.updateLock.Unlock()
		if err := s.db.UseBatch(files); err != nil {
			log.Println("error while updating lastUsage", err)
		}
		files = nil
	}
	// we need to update last files before stopping server
	defer update()
	for {
		select {
		case f := <-s.useQuery:
			files = append(files, f)
		case <-ticker.C:
			update()
		case <-s.stop:
			return
		}
	}
}

func (s *DefaultServer) addFromURL(f File, u *url.URL) error {
	rc, err := s.api.GetFile(u)
	if err != nil {
		return err
	}
	defer rc.Close()
	return s.frontend.Add(f, rc)
}

// Start server internal goroutines
func (s *DefaultServer) Start() error {
	go s.stopNotificator()

	// starting lastUsage update loop
	s.wg.Add(1)
	go s.useLoop()

	// starting removal loop
	s.wg.Add(1)
	go s.removeLoop()

	s.started = true
	return nil
}

func (s *DefaultServer) stopNotificator() {
	<-s.stop
	log.Println("stopping server")
}

// Close stops server
func (s *DefaultServer) Close() error {
	close(s.useQuery)
	close(s.stop)
	s.wg.Wait()

	s.db.Close()
	s.started = false
	return nil
}

// ServerConfig cfg for server
type ServerConfig struct {
	Credentials
	Frontend            Frontend
	DataBase            DataBase
	Client              *Client
	DontCheckTimestamps bool
	DontCheckSHA1       bool
	RemoveTimeout       time.Duration
	RemoveRate          time.Duration
	UpdateRate          time.Duration
}

// PopulateDefaults of the config
func (cfg *ServerConfig) PopulateDefaults() {
	if cfg.RemoveTimeout == time.Second*0 {
		cfg.RemoveTimeout = time.Hour * 24 * 30
	}
	if cfg.RemoveRate == time.Second*0 {
		cfg.RemoveRate = time.Hour
	}
	if cfg.Client == nil {
		cfg.Client = NewClient(ClientConfig{Credentials: cfg.Credentials})
	}
	if cfg.UpdateRate == time.Second*0 {
		cfg.UpdateRate = time.Second * 5
	}
}

// NewServer cleares default server with provided client and frontend
func NewServer(cfg ServerConfig) *DefaultServer {
	cfg.PopulateDefaults()
	s := new(DefaultServer)
	s.cfg = cfg
	s.db = cfg.DataBase
	s.frontend = cfg.Frontend

	// routing init
	e := gin.New()
	e.GET("/h/:fileid/:kwds/:filename", s.handleImage)
	e.GET("/servercmd/:command/:kwds/:timestamp/:key", s.handleCommand)

	// routing for commands
	commands := make(map[string]commandHandler)
	commands["speed_test"] = s.commandSpeedTest
	s.commands = commands

	if cfg.DontCheckTimestamps {
		log.Println("warning: not checking timestamps")
	}

	if cfg.DontCheckSHA1 {
		log.Println("warning: not checking sha1")
	}

	s.e = e
	s.useQuery = make(chan File, useQuerySize)
	s.wg = new(sync.WaitGroup)
	s.api = cfg.Client
	s.stop = make(chan bool)
	s.updateLock = new(sync.Mutex)
	return s
}
<commit_msg>server: download command implemented; fix #4<commit_after>// Package hath is Hentai@Home client implementation in golang
package hath // import "cydev.ru/hath"

import (
	"crypto/sha1"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"sync"
	"time"

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

// Server should handle requests from users (and rpc?)
// speedtests
// server commands:
//     /servercmd/<command>/<additional:kwds>/<timestamp:int>/<key>'
// image file request:
//     /h/<fileid>/<additional:kwds>/<filename>
type Server interface {
	http.Handler
}

type speedTest struct{}

func (s speedTest) Read(b []byte) (n int, err error) {
	n = len(b)
	return n, err
}

// DefaultServer uses hard drive to respond
type DefaultServer struct {
	api        *Client
	cfg        ServerConfig
	frontend   Frontend
	db         DataBase
	e          *gin.Engine
	useQuery   chan File
	wg         *sync.WaitGroup
	started    bool
	commands   map[string]commandHandler
	stop       chan bool
	updateLock sync.Locker
}

const (
	argsDelimiter       = ";"
	argsEqual           = "="
	argsKeystamp        = "keystamp"
	timestampMaxDelta   = 60
	useQuerySize        = 100
	tmpFolder           = "tmp"
	cmdKeyStart         = "hentai@home-servercmd"
	cmdKeyDelimiter     = "-"
	size100MB           = 10 * size10MB
	headerContentLength = "Content-Length"
	fileInfoDelimiter   = ":"

	downloadError     = "FAIL"
	downloadSuccess   = "OK"
	downloadInvalid   = "INVALID"
	downloadPath      = "image.php"
	argDownloadFileID = "f"
	argDownloadKey    = "t"
	downloadScheme    = "http"

	cmdSpeedTest = "speed_test"
	cmdDownload  = "cache_files"
)

// ProxyMode sets proxy security politics
type ProxyMode byte

const (
	// ProxyDisabled no requests allowed
	ProxyDisabled ProxyMode = iota + 1 // starts with 1
	// ProxyLocalNetworksProtected allows requests from local network with passkey
	ProxyLocalNetworksProtected // 2
	// ProxyLocalNetworksOpen allows any requests from local network
	ProxyLocalNetworksOpen // 3
	// ProxyAllNetworksProtected allows requests from any network with passkey (not recommended)
	ProxyAllNetworksProtected // 4
	// ProxyAllNetworksOpen allows any requests from any network (very not recommended)
	ProxyAllNetworksOpen // 5
)

const (
	proxyModeMin  = int64(ProxyDisabled)
	proxyModeMax  = int64(ProxyAllNetworksOpen)
	proxyModeBits = 4
)

func (p ProxyMode) String() string {
	if p == ProxyDisabled {
		return "disabled"
	}
	if p == ProxyLocalNetworksOpen {
		return "open for local networks"
	}
	if p == ProxyAllNetworksProtected {
		return "local networks with passkey"
	}
	if p == ProxyAllNetworksProtected {
		return "all networks with passkey"
	}
	if p == ProxyAllNetworksOpen {
		return "open for all networks"
	}
	return "unknown"
}

// Args represents additional arguments in request string
type Args map[string]string

// Get returns string value
func (a Args) Get(k string) string {
	return a[k]
}

// GetInt parses and returns integer value
func (a Args) GetInt(k string) int {
	i, _ := strconv.Atoi(a[k])
	return i
}

// GetInt64 parses and returns 64bit integer value
func (a Args) GetInt64(k string) int64 {
	i, _ := strconv.ParseInt(a[k], 10, 64)
	return i
}

func (a Args) String() string {
	var elems []string
	for k, v := range a {
		elems = append(elems, strings.Join([]string{k, v}, argsEqual))
	}
	return strings.Join(elems, argsDelimiter)
}

// ParseArgs parses arguments from string
//     arg1=val1;arg2=val2
func ParseArgs(s string) (a Args) {
	a = make(Args)
	components := strings.Split(s, argsDelimiter)
	for _, component := range components {
		kw := strings.Split(component, argsEqual)
		if len(kw) != 2 {
			continue
		}
		a[kw[0]] = kw[1]
	}
	return a
}

// handleImage /h/<fileid>/<additional:kwds>/<filename>
func (s *DefaultServer) handleImage(c *gin.Context) {
	fileID := c.Param("fileid")
	args := ParseArgs(c.Param("kwds"))
	// parsing timestamp and keystamp
	stamps := strings.Split(args.Get(argsKeystamp), keyStampDelimiter)
	if len(stamps) != 2 {
		c.String(http.StatusBadRequest, "400: Bad stamp format")
		return
	}
	timestamp, err := strconv.ParseInt(stamps[0], intBase, 64)
	if err != nil {
		c.String(http.StatusBadRequest, "400: Bad timestamp")
		return
	}
	if !s.isDeltaValid(stamps[0]) {
		c.String(http.StatusBadRequest, "400: timestamp delta is too big")
		return
	}
	keyStamp := stamps[1]
	f, err := FileFromID(fileID)
	if err != nil {
		c.String(http.StatusBadRequest, "400: bad file id")
		return
	}
	expectedKeyStamp := f.KeyStamp(s.cfg.Key, timestamp)
	if expectedKeyStamp != keyStamp && !s.cfg.DontCheckSHA1 {
		c.String(http.StatusForbidden, "403: bad keystamp")
		return
	}
	s.useQuery <- f
	s.frontend.Handle(f, c.Writer)
}

func getSHA1(sep string, args []string) string {
	hasher := sha1.New()
	toHash := strings.Join(args, sep)
	h := hasher.Sum([]byte(toHash))
	return hex.EncodeToString(h)
}

type commandHandler func(*gin.Context, Args)

func (s *DefaultServer) isDeltaValid(ts string) bool {
	if s.cfg.DontCheckTimestamps {
		return true
	}
	timestamp, err := strconv.ParseInt(ts, intBase, 64)
	if err != nil {
		return false
	}
	delta := time.Now().Unix() - timestamp
	if delta < 0 {
		delta *= -1
	}
	return delta < maximumTimeLag
}

var (
	// ErrNoFilesToRemove is flag that there is 0 files to remove
	ErrNoFilesToRemove = errors.New("No more unused files")
)

func (s *DefaultServer) removeUnused(deadline time.Time) error {

	files, err := s.db.GetOldFiles(maxRemoveCount, deadline)
	if err != nil {
		return err
	}
	if len(files) == 0 {
		return nil
	}
	if err := s.api.RemoveFiles(files); err != nil {
		return err
	}
	if err := s.db.RemoveBatch(files); err != nil {
		return err
	}
	if err := s.frontend.RemoveBatch(files); err != nil {
		return err
	}
	return nil
}

func (s *DefaultServer) removeAllUnused(deadline time.Time) error {
	log.Println("removing old files")
	defer log.Println("removing old files completed")
	for {
		err := s.removeUnused(deadline)
		// just iterating until error occurs
		if err == nil {
			continue
		}
		// error occured
		// unexpected error check
		if err != ErrNoFilesToRemove {
			return err
		}
		// breaking inner loop
		return nil
	}
}

func (s *DefaultServer) removeLoop() {
	ticker := time.NewTicker(time.Minute)
	defer ticker.Stop()
	defer s.wg.Done()
	for {
		select {
		case t := <-ticker.C:
			deadline := t.Add(-1 * s.cfg.RemoveTimeout)
			count, err := s.db.GetOldFilesCount(deadline)
			if err != nil {
				log.Println("error while getting old files count", err)
			}
			if count == 0 {
				continue
			}
			s.updateLock.Lock()
			defer s.updateLock.Unlock()
			if err := s.removeAllUnused(deadline); err != nil {
				log.Println("error while removing files", err)
			}
		case _ = <-s.stop:
			return
		}
	}
}

// /servercmd/<command>/<additional:kwds>/<timestamp:int>/<key>
func (s *DefaultServer) handleCommand(c *gin.Context) {
	// checking crypto sign
	// parsing params
	key := c.Param("key")
	kwds := c.Param("kwds")
	args := ParseArgs(kwds)
	timestampS := c.Param("timestamp")
	command := c.Param("command")
	sClientID := strconv.FormatInt(s.cfg.ClientID, intBase)

	// checkign timestamp delta
	if !s.isDeltaValid(timestampS) {
		c.String(http.StatusUnauthorized, "403: bad timestamp")
		return
	}

	hashArgs := []string{
		cmdKeyStart,
		kwds,
		sClientID,
		timestampS,
		s.cfg.Key,
	}
	keyCalculated := getSHA1(cmdKeyDelimiter, hashArgs)
	if key != keyCalculated && !s.cfg.DontCheckSHA1 {
		c.String(http.StatusUnauthorized, "403: bad sign")
		return
	}
	handler, ok := s.commands[command]
	if !ok {
		// handler for command not found
		c.String(http.StatusNotFound, "404: command not found")
		return
	}
	handler(c, args)
}

func (s *DefaultServer) commandSpeedTest(c *gin.Context, args Args) {
	size := args.GetInt64("testsize")
	if size <= 0 || size > size100MB {
		size = size100MB
	}
	c.Header(headerContentLength, strconv.FormatInt(size, intBase))
	_, err := io.CopyN(c.Writer, speedTest{}, size)
	if err != nil {
		log.Println("speed test error", err)
	}
}

// commandDownload process request from server to download a list of files
// and store them in database and cache
func (s *DefaultServer) commandDownload(c *gin.Context, args Args) {
	for fileInfo, key := range args {
		elems := strings.Split(fileInfo, fileInfoDelimiter)
		if len(elems) != 2 {
			log.Println("warning:", "got bad file info string from server:", fileInfo)
			continue
		}
		fileID := elems[0]
		host := elems[1]

		writeStatus := func(status string) {
			fmt.Fprintf(c.Writer, "%s:%s\n", status, fileID)
		}

		// parsing and validating fileID
		f, err := FileFromID(fileID)
		if err != nil {
			writeStatus(downloadInvalid)
			continue
		}

		// generating url
		u := new(url.URL)
		u.Host = host
		u.Scheme = downloadScheme
		u.Path = downloadPath
		q := make(url.Values)
		q.Add(argDownloadFileID, fileID)
		q.Add(argDownloadKey, key)
		u.RawQuery = q.Encode()

		// downloading and saving file to db/cache
		if err := s.addFromURL(f, u); err != nil {
			writeStatus(downloadError)
			continue
		}
		writeStatus(downloadSuccess)
	}
}

func (s *DefaultServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	s.e.ServeHTTP(w, r)
}

// lastUsage update loop
func (s *DefaultServer) useLoop() {
	defer s.wg.Done()
	ticker := time.NewTicker(s.cfg.UpdateRate)
	defer ticker.Stop()

	var files []File
	update := func() {
		if files == nil {
			return
		}
		s.updateLock.Lock()
		defer s.updateLock.Unlock()
		if err := s.db.UseBatch(files); err != nil {
			log.Println("error while updating lastUsage", err)
		}
		files = nil
	}
	// we need to update last files before stopping server
	defer update()
	for {
		select {
		case f := <-s.useQuery:
			files = append(files, f)
		case <-ticker.C:
			update()
		case <-s.stop:
			return
		}
	}
}

func (s *DefaultServer) addFromURL(f File, u *url.URL) error {
	rc, err := s.api.GetFile(u)
	if err != nil {
		return err
	}
	defer rc.Close()
	return s.frontend.Add(f, rc)
}

// Start server internal goroutines
func (s *DefaultServer) Start() error {
	go s.stopNotificator()

	// starting lastUsage update loop
	s.wg.Add(1)
	go s.useLoop()

	// starting removal loop
	s.wg.Add(1)
	go s.removeLoop()

	s.started = true
	return nil
}

func (s *DefaultServer) stopNotificator() {
	<-s.stop
	log.Println("stopping server")
}

// Close stops server
func (s *DefaultServer) Close() error {
	close(s.useQuery)
	close(s.stop)
	s.wg.Wait()

	s.db.Close()
	s.started = false
	return nil
}

// ServerConfig cfg for server
type ServerConfig struct {
	Credentials
	Frontend            Frontend
	DataBase            DataBase
	Client              *Client
	DontCheckTimestamps bool
	DontCheckSHA1       bool
	RemoveTimeout       time.Duration
	RemoveRate          time.Duration
	UpdateRate          time.Duration
}

// PopulateDefaults of the config
func (cfg *ServerConfig) PopulateDefaults() {
	if cfg.RemoveTimeout == time.Second*0 {
		cfg.RemoveTimeout = time.Hour * 24 * 30
	}
	if cfg.RemoveRate == time.Second*0 {
		cfg.RemoveRate = time.Hour
	}
	if cfg.Client == nil {
		cfg.Client = NewClient(ClientConfig{Credentials: cfg.Credentials})
	}
	if cfg.UpdateRate == time.Second*0 {
		cfg.UpdateRate = time.Second * 5
	}
}

// NewServer cleares default server with provided client and frontend
func NewServer(cfg ServerConfig) *DefaultServer {
	cfg.PopulateDefaults()
	s := new(DefaultServer)
	s.cfg = cfg
	s.db = cfg.DataBase
	s.frontend = cfg.Frontend

	// routing init
	e := gin.New()
	e.GET("/h/:fileid/:kwds/:filename", s.handleImage)
	e.GET("/servercmd/:command/:kwds/:timestamp/:key", s.handleCommand)

	// routing for commands
	s.commands = map[string]commandHandler{
		cmdSpeedTest: s.commandSpeedTest,
		cmdDownload:  s.commandDownload,
	}

	if cfg.DontCheckTimestamps {
		log.Println("warning: not checking timestamps")
	}

	if cfg.DontCheckSHA1 {
		log.Println("warning: not checking sha1")
	}

	s.e = e
	s.useQuery = make(chan File, useQuerySize)
	s.wg = new(sync.WaitGroup)
	s.api = cfg.Client
	s.stop = make(chan bool)
	s.updateLock = new(sync.Mutex)
	return s
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"net/url"
	"strconv"
	"time"

	"github.com/zaynetro/tweets-sentiment-analysis/Godeps/_workspace/src/github.com/ChimeraCoder/anaconda"
)

const (
	UPDATE_MIN = 1
)

var config = Config{}

func main() {
	parseConfig()

	ticker := time.NewTicker(UPDATE_MIN * time.Minute)

	lastId := int64(662997485976215552)
	for _ = range ticker.C {
		log.Printf("Start processing tweets since: %d\n", lastId)
		lastId = processTweets(lastId)
	}

}

func processTweets(sinceId int64) int64 {
	var lastId int64
	tweets := getTweets(sinceId)
	if l := len(tweets); l > 0 {
		lastId = tweets[l-1].Id
	}
	analysed := analyseTweets(tweets)
	for _, tweet := range analysed {
		//log.Printf("Analysed: %v\n", tweet)
		sendToDb(tweet)
	}
	log.Printf("Processed: %d tweets\n", len(analysed))

	return lastId
}

func getTweets(sinceId int64) []anaconda.Tweet {
	anaconda.SetConsumerKey(config.Twitter.ConsumerKey)
	anaconda.SetConsumerSecret(config.Twitter.ConsumerSecret)
	api := anaconda.NewTwitterApi(config.Twitter.AccessToken, config.Twitter.AccessTokenSecret)

	v := url.Values{}
	v.Set("count", "30")
	if sinceId != 0 {
		v.Set("since_id", strconv.FormatInt(sinceId, 10))
	}
	tweets, err := api.GetHomeTimeline(v)
	if err != nil {
		log.Printf("Can't get home timeline: %s\n", err)
		return []anaconda.Tweet{}
	}

	log.Printf("Fetched %d tweets\n", len(tweets))

	return tweets
}
<commit_msg>Fix saving last fetched tweet<commit_after>package main

import (
	"log"
	"net/url"
	"strconv"
	"time"

	"github.com/zaynetro/tweets-sentiment-analysis/Godeps/_workspace/src/github.com/ChimeraCoder/anaconda"
)

const (
	UPDATE_MIN = 1
)

var config = Config{}

func main() {
	parseConfig()

	ticker := time.NewTicker(UPDATE_MIN * time.Minute)

	lastId := int64(663074549127356416)
	for _ = range ticker.C {
		log.Printf("Start processing tweets since: %d\n", lastId)
		lastId = processTweets(lastId)
	}

}

func processTweets(sinceId int64) int64 {
	var lastId int64
	tweets := getTweets(sinceId)
	if l := len(tweets); l > 0 {
		lastId = tweets[0].Id
	}
	analysed := analyseTweets(tweets)
	for _, tweet := range analysed {
		//log.Printf("Analysed: %v\n", tweet)
		sendToDb(tweet)
	}

	if l := len(analysed); l > 0 {
		log.Printf("Last processed tweet: %s\n", analysed[0].Text)
	}

	log.Printf("Processed: %d tweets\n", len(analysed))

	return lastId
}

func getTweets(sinceId int64) []anaconda.Tweet {
	anaconda.SetConsumerKey(config.Twitter.ConsumerKey)
	anaconda.SetConsumerSecret(config.Twitter.ConsumerSecret)
	api := anaconda.NewTwitterApi(config.Twitter.AccessToken, config.Twitter.AccessTokenSecret)

	v := url.Values{}
	v.Set("count", "30")
	if sinceId != 0 {
		v.Set("since_id", strconv.FormatInt(sinceId, 10))
	}
	tweets, err := api.GetHomeTimeline(v)
	if err != nil {
		log.Printf("Can't get home timeline: %s\n", err)
		return []anaconda.Tweet{}
	}

	log.Printf("Fetched %d tweets\n", len(tweets))

	return tweets
}
<|endoftext|>


<commit_before>package main

import (
	"strings"
)

const (
	FormatLongMonth             = "January"
	FormatMonth                 = "Jan"
	FormatNumMonth              = "1"
	FormatZeroMonth             = "01"
	FormatLongWeekDay           = "Monday"
	FormatWeekDay               = "Mon"
	FormatDay                   = "2"
	FormatUnderDay              = "_2"
	FormatZeroDay               = "02"
	FormatHour                  = "15"
	FormatHour12                = "3"
	FormatZeroHour12            = "03"
	FormatMinute                = "4"
	FormatZeroMinute            = "04"
	FormatSecond                = "5"
	FormatZeroSecond            = "05"
	FormatLongYear              = "2006"
	FormatYear                  = "06"
	FormatPM                    = "PM"
	Formatpm                    = "pm"
	FormatTZ                    = "MST"
	FormatISO8601TZ             = "Z0700" // prints Z for UTC
	FormatISO8601SecondsTZ      = "Z070000"
	FormatISO8601ColonTZ        = "Z07:00" // prints Z for UTC
	FormatISO8601ColonSecondsTZ = "Z07:00:00"
	FormatNumTZ                 = "-0700" // always numeric
	FormatNumSecondsTz          = "-070000"
	FormatNumShortTZ            = "-07"    // always numeric
	FormatNumColonTZ            = "-07:00" // always numeric
	FormatNumColonSecondsTZ     = "-07:00:00"
)

func ConvertFromPhp(s string) string {
	for phpString, goStrig := range PhpFormatMap {
		s = strings.Replace(s, phpString, goStrig, -1)
	}
	return s
}
<commit_msg>Add supported php format strings.<commit_after>package main

import (
	"strings"
	"time"
)

const (
	FormatLongMonth             = "January"
	FormatMonth                 = "Jan"
	FormatNumMonth              = "1"
	FormatZeroMonth             = "01"
	FormatLongWeekDay           = "Monday"
	FormatWeekDay               = "Mon"
	FormatDay                   = "2"
	FormatUnderDay              = "_2"
	FormatZeroDay               = "02"
	FormatHour                  = "15"
	FormatHour12                = "3"
	FormatZeroHour12            = "03"
	FormatMinute                = "4"
	FormatZeroMinute            = "04"
	FormatSecond                = "5"
	FormatZeroSecond            = "05"
	FormatLongYear              = "2006"
	FormatYear                  = "06"
	FormatPM                    = "PM"
	Formatpm                    = "pm"
	FormatTZ                    = "MST"
	FormatISO8601TZ             = "Z0700" // prints Z for UTC
	FormatISO8601SecondsTZ      = "Z070000"
	FormatISO8601ColonTZ        = "Z07:00" // prints Z for UTC
	FormatISO8601ColonSecondsTZ = "Z07:00:00"
	FormatNumTZ                 = "-0700" // always numeric
	FormatNumSecondsTz          = "-070000"
	FormatNumShortTZ            = "-07"    // always numeric
	FormatNumColonTZ            = "-07:00" // always numeric
	FormatNumColonSecondsTZ     = "-07:00:00"
)

var PhpFormatMap = map[string]string{
	"d": FormatZeroDay,
	"D": FormatWeekDay,
	"j": FormatDay,
	"l": FormatLongWeekDay,
	"F": FormatLongMonth,
	"m": FormatZeroMonth,
	"M": FormatMonth,
	"n": FormatNumMonth,
	"Y": FormatLongYear,
	"y": FormatYear,
	"a": Formatpm,
	"A": FormatPM,
	"g": FormatHour12,
	"G": FormatHour,
	"h": FormatZeroHour12,
	"i": FormatZeroMinute,
	"s": FormatZeroSecond,
	"O": FormatNumTZ,
	"P": FormatNumColonTZ,
	"T": FormatTZ,
	"c": time.RFC3339,
	"r": time.RFC1123Z,
}

func ConvertFromPhp(s string) string {
	for phpString, goStrig := range PhpFormatMap {
		s = strings.Replace(s, phpString, goStrig, -1)
	}
	return s
}
<|endoftext|>


<commit_before>// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
//

package ghttp

import (
	"github.com/gogf/gf/text/gstr"
	"github.com/gogf/gf/util/gconv"
	"net/http"
	"net/url"
)

// CORSOptions is the options for CORS feature.
// See https://www.w3.org/TR/cors/ .
type CORSOptions struct {
	AllowDomain      []string // Used for allowing requests from custom domains
	AllowOrigin      string   // Access-Control-Allow-Origin
	AllowCredentials string   // Access-Control-Allow-Credentials
	ExposeHeaders    string   // Access-Control-Expose-Headers
	MaxAge           int      // Access-Control-Max-Age
	AllowMethods     string   // Access-Control-Allow-Methods
	AllowHeaders     string   // Access-Control-Allow-Headers
}

var (
	// defaultAllowHeaders is the default allowed headers for CORS.
	// It's defined another map for better header key searching performance.
	defaultAllowHeaders    = "Origin,Content-Type,Accept,User-Agent,Cookie,Authorization,X-Auth-Token,X-Requested-With"
	defaultAllowHeadersMap = make(map[string]struct{})
)

func init() {
	array := gstr.SplitAndTrim(defaultAllowHeaders, ",")
	for _, header := range array {
		defaultAllowHeadersMap[header] = struct{}{}
	}
}

// DefaultCORSOptions returns the default CORS options,
// which allows any cross-domain request.
func (r *Response) DefaultCORSOptions() CORSOptions {
	options := CORSOptions{
		AllowOrigin:      "*",
		AllowMethods:     HTTP_METHODS,
		AllowCredentials: "true",
		AllowHeaders:     defaultAllowHeaders,
		MaxAge:           3628800,
	}
	// Allow all client's custom headers in default.
	if headers := r.Request.Header.Get("Access-Control-Request-Headers"); headers != "" {
		array := gstr.SplitAndTrim(headers, ",")
		for _, header := range array {
			if _, ok := defaultAllowHeadersMap[header]; !ok {
				options.AllowHeaders += header + ","
			}
		}
	}
	// Allow all anywhere origin in default.
	if origin := r.Request.Header.Get("Origin"); origin != "" {
		options.AllowOrigin = origin
	} else if referer := r.Request.Referer(); referer != "" {
		if p := gstr.PosR(referer, "/", 6); p != -1 {
			options.AllowOrigin = referer[:p]
		} else {
			options.AllowOrigin = referer
		}
	}
	return options
}

// CORS sets custom CORS options.
// See https://www.w3.org/TR/cors/ .
func (r *Response) CORS(options CORSOptions) {
	if r.CORSAllowedOrigin(options) {
		r.Header().Set("Access-Control-Allow-Origin", options.AllowOrigin)
	}
	if options.AllowCredentials != "" {
		r.Header().Set("Access-Control-Allow-Credentials", options.AllowCredentials)
	}
	if options.ExposeHeaders != "" {
		r.Header().Set("Access-Control-Expose-Headers", options.ExposeHeaders)
	}
	if options.MaxAge != 0 {
		r.Header().Set("Access-Control-Max-Age", gconv.String(options.MaxAge))
	}
	if options.AllowMethods != "" {
		r.Header().Set("Access-Control-Allow-Methods", options.AllowMethods)
	}
	if options.AllowHeaders != "" {
		r.Header().Set("Access-Control-Allow-Headers", options.AllowHeaders)
	}
	// No continue service handling if it's OPTIONS request.
	// Note that there's special checks in previous router searching,
	// so if it goes to here it means there's already serving handler exist.
	if gstr.Equal(r.Request.Method, "OPTIONS") {
		if r.Status == 0 {
			r.Status = http.StatusOK
		}
		// No continue serving.
		r.Request.ExitAll()
	}
}

// CORSAllowed checks whether the current request origin is allowed cross-domain.
func (r *Response) CORSAllowedOrigin(options CORSOptions) bool {
	if options.AllowDomain == nil {
		return true
	}
	origin := r.Request.Header.Get("Origin")
	if origin == "" {
		return true
	}
	parsed, err := url.Parse(origin)
	if err != nil {
		return false
	}
	for _, v := range options.AllowDomain {
		if gstr.IsSubDomain(parsed.Host, v) {
			return true
		}
	}
	return false
}

// CORSDefault sets CORS with default CORS options,
// which allows any cross-domain request.
func (r *Response) CORSDefault() {
	r.CORS(r.DefaultCORSOptions())
}
<commit_msg>fix: Access-Control-Request-Headers<commit_after>// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
//

package ghttp

import (
	"github.com/gogf/gf/text/gstr"
	"github.com/gogf/gf/util/gconv"
	"net/http"
	"net/url"
)

// CORSOptions is the options for CORS feature.
// See https://www.w3.org/TR/cors/ .
type CORSOptions struct {
	AllowDomain      []string // Used for allowing requests from custom domains
	AllowOrigin      string   // Access-Control-Allow-Origin
	AllowCredentials string   // Access-Control-Allow-Credentials
	ExposeHeaders    string   // Access-Control-Expose-Headers
	MaxAge           int      // Access-Control-Max-Age
	AllowMethods     string   // Access-Control-Allow-Methods
	AllowHeaders     string   // Access-Control-Allow-Headers
}

var (
	// defaultAllowHeaders is the default allowed headers for CORS.
	// It's defined another map for better header key searching performance.
	defaultAllowHeaders    = "Origin,Content-Type,Accept,User-Agent,Cookie,Authorization,X-Auth-Token,X-Requested-With"
	defaultAllowHeadersMap = make(map[string]struct{})
)

func init() {
	array := gstr.SplitAndTrim(defaultAllowHeaders, ",")
	for _, header := range array {
		defaultAllowHeadersMap[header] = struct{}{}
	}
}

// DefaultCORSOptions returns the default CORS options,
// which allows any cross-domain request.
func (r *Response) DefaultCORSOptions() CORSOptions {
	options := CORSOptions{
		AllowOrigin:      "*",
		AllowMethods:     HTTP_METHODS,
		AllowCredentials: "true",
		AllowHeaders:     defaultAllowHeaders,
		MaxAge:           3628800,
	}
	// Allow all client's custom headers in default.
	if headers := r.Request.Header.Get("Access-Control-Request-Headers"); headers != "" {
		array := gstr.SplitAndTrim(headers, ",")
		for _, header := range array {
			if _, ok := defaultAllowHeadersMap[header]; !ok {
				options.AllowHeaders += "," + header
			}
		}
	}
	// Allow all anywhere origin in default.
	if origin := r.Request.Header.Get("Origin"); origin != "" {
		options.AllowOrigin = origin
	} else if referer := r.Request.Referer(); referer != "" {
		if p := gstr.PosR(referer, "/", 6); p != -1 {
			options.AllowOrigin = referer[:p]
		} else {
			options.AllowOrigin = referer
		}
	}
	return options
}

// CORS sets custom CORS options.
// See https://www.w3.org/TR/cors/ .
func (r *Response) CORS(options CORSOptions) {
	if r.CORSAllowedOrigin(options) {
		r.Header().Set("Access-Control-Allow-Origin", options.AllowOrigin)
	}
	if options.AllowCredentials != "" {
		r.Header().Set("Access-Control-Allow-Credentials", options.AllowCredentials)
	}
	if options.ExposeHeaders != "" {
		r.Header().Set("Access-Control-Expose-Headers", options.ExposeHeaders)
	}
	if options.MaxAge != 0 {
		r.Header().Set("Access-Control-Max-Age", gconv.String(options.MaxAge))
	}
	if options.AllowMethods != "" {
		r.Header().Set("Access-Control-Allow-Methods", options.AllowMethods)
	}
	if options.AllowHeaders != "" {
		r.Header().Set("Access-Control-Allow-Headers", options.AllowHeaders)
	}
	// No continue service handling if it's OPTIONS request.
	// Note that there's special checks in previous router searching,
	// so if it goes to here it means there's already serving handler exist.
	if gstr.Equal(r.Request.Method, "OPTIONS") {
		if r.Status == 0 {
			r.Status = http.StatusOK
		}
		// No continue serving.
		r.Request.ExitAll()
	}
}

// CORSAllowed checks whether the current request origin is allowed cross-domain.
func (r *Response) CORSAllowedOrigin(options CORSOptions) bool {
	if options.AllowDomain == nil {
		return true
	}
	origin := r.Request.Header.Get("Origin")
	if origin == "" {
		return true
	}
	parsed, err := url.Parse(origin)
	if err != nil {
		return false
	}
	for _, v := range options.AllowDomain {
		if gstr.IsSubDomain(parsed.Host, v) {
			return true
		}
	}
	return false
}

// CORSDefault sets CORS with default CORS options,
// which allows any cross-domain request.
func (r *Response) CORSDefault() {
	r.CORS(r.DefaultCORSOptions())
}
<|endoftext|>