<commit_before>// Copyright (c) 2013 Joshua Elliott
// Released under the MIT License
// http://opensource.org/licenses/MIT

package turnpike

import (
	//	"code.google.com/p/go.net/websocket"
	"encoding/json"
	"fmt"
	"golang.org/x/net/websocket"
	"io"
	"log"
	"math/rand"
)

const (
	wampProtocolId = "wamp"
)

var clientBacklog = 10

// Client represents a WAMP client that handles RPC and pub/sub.
type Client struct {
	// SessionId is a ID of the session in UUID4 format received at the start of the session.
	SessionId string
	// ProtocolVersion is the version of the WAMP protocol received at the start of the session.
	ProtocolVersion int
	// ServerIdent is the server ID (ie "turnpike, autobahn") received at the start of the session.
	ServerIdent         string
	ws                  *websocket.Conn
	messages            chan string
	prefixes            prefixMap
	eventHandlers       map[string]EventHandler
	calls               map[string]chan CallResult
	sessionOpenCallback func(string)
}

// CallResult represents either a sucess or a failure after a RPC call.
type CallResult struct {
	// Result contains the RPC call result returned by the server.
	Result interface{}
	// Error is nil on call success otherwise it contains the RPC error.
	Error error
}

// EventHandler is an interface for handlers to published events. The topicURI
// is the URI of the event and event is the event contents.
type EventHandler func(topicURI string, event interface{})

// NewClient creates a new WAMP client.
func NewClient() *Client {
	return &Client{
		messages:      make(chan string, clientBacklog),
		prefixes:      make(prefixMap),
		eventHandlers: make(map[string]EventHandler),
		calls:         make(map[string]chan CallResult),
	}
}

// Prefix sets a CURIE prefix at the server for later use when interacting with
// the server. prefix is the first part of a CURIE (ie "calc") and URI is a full
// identifier (ie "http://example.com/simple/calc#") that is mapped to the prefix.
//
// Ref: http://wamp.ws/spec#prefix_message
func (c *Client) Prefix(prefix, URI string) error {
	if debug {
		log.Printf("turnpike: sending prefix %s:%s", prefix, URI)
	}
	err := c.prefixes.registerPrefix(prefix, URI)
	if err != nil {
		return fmt.Errorf("turnpike: %s", err)
	}
	msg, err := createPrefix(prefix, URI)
	if err != nil {
		return fmt.Errorf("turnpike: %s", err)
	}
	c.messages <- string(msg)
	return nil
}

// Call makes a RPC call on the server identified by procURI (in either full URI
// or CURIE format) with zero or more args. Returns a channel that will receive
// the call result (or error) on completion.
//
// Ref: http://wamp.ws/spec#call_message
func (c *Client) Call(procURI string, args ...interface{}) chan CallResult {
	if debug {
		log.Print("turnpike: sending call")
	}
	// Channel size must be 1 to avoid blocking if no one is receiving the channel later.
	resultCh := make(chan CallResult, 1)
	callId := newId(16)
	msg, err := createCall(callId, procURI, args...)
	if err != nil {
		r := CallResult{
			Result: nil,
			Error:  fmt.Errorf("turnpike: %s", err),
		}
		resultCh <- r
		return resultCh
	}
	c.calls[callId] = resultCh
	c.messages <- string(msg)
	return resultCh
}

// Subscribe adds a subscription at the server for events with topicURI lasting
// for the session or until Unsubscribe is called.
//
// Ref: http://wamp.ws/spec#subscribe_message
func (c *Client) Subscribe(topicURI string, f EventHandler) error {
	if debug {
		log.Print("turnpike: sending subscribe")
	}
	msg, err := createSubscribe(topicURI)
	if err != nil {
		return fmt.Errorf("turnpike: %s", err)
	}
	c.messages <- string(msg)
	if f != nil {
		c.eventHandlers[topicURI] = f
	}
	return nil
}

// Unsubscribe removes a previous subscription with topicURI at the server.
//
// Ref: http://wamp.ws/spec#unsubscribe_message
func (c *Client) Unsubscribe(topicURI string) error {
	if debug {
		log.Print("turnpike: sending unsubscribe")
	}
	msg, err := createUnsubscribe(topicURI)
	if err != nil {
		return fmt.Errorf("turnpike: %s", err)
	}
	c.messages <- string(msg)
	delete(c.eventHandlers, topicURI)
	return nil
}

// Publish publishes an event to the topicURI that gets sent to all subscribers
// of that topicURI by the server. opts can can be either empty, one boolean
// that can be used to exclude outself from receiving the event or two lists;
// the first a list of clients to exclude and the second a list of clients that
// are eligible to receive the event. Either list can be empty.
//
// Ref: http://wamp.ws/spec#publish_message
func (c *Client) Publish(topicURI string, event interface{}, opts ...interface{}) error {
	if debug {
		log.Print("turnpike: sending publish")
	}
	msg, err := createPublish(topicURI, event, opts...)
	if err != nil {
		return fmt.Errorf("turnpike: %s", err)
	}
	c.messages <- string(msg)
	return nil
}

// PublishExcludeMe is a short hand for Publish(tobicURI, event, true) that will
// not send the event to ourself.
func (c *Client) PublishExcludeMe(topicURI string, event interface{}) error {
	return c.Publish(topicURI, event, true)
}

func (c *Client) handleCallResult(msg callResultMsg) {
	if debug {
		log.Print("turnpike: handling call result message")
	}
	resultCh, ok := c.calls[msg.CallID]
	if !ok {
		if debug {
			log.Print("turnpike: missing call result handler")
		}
		return
	}
	delete(c.calls, msg.CallID)
	r := CallResult{
		Result: msg.Result,
		Error:  nil,
	}
	resultCh <- r
}

func (c *Client) handleCallError(msg callErrorMsg) {
	if debug {
		log.Print("turnpike: handling call error message")
	}
	resultCh, ok := c.calls[msg.CallID]
	if !ok {
		if debug {
			log.Print("turnpike: missing call result handler")
		}
		return
	}
	delete(c.calls, msg.CallID)
	r := CallResult{
		Error: RPCError{
			URI:         msg.ErrorURI,
			Description: msg.ErrorDesc,
			Details:     msg.ErrorDetails,
		},
	}
	resultCh <- r
}

func (c *Client) handleEvent(msg eventMsg) {
	if debug {
		log.Print("turnpike: handling event message")
	}
	if f, ok := c.eventHandlers[msg.TopicURI]; ok && f != nil {
		f(msg.TopicURI, msg.Event)
	} else {
		if debug {
			log.Printf("turnpike: missing event handler for URI: %s", msg.TopicURI)
		}
	}
}

func (c *Client) receiveWelcome() error {
	if debug {
		log.Print("turnpike: receive welcome")
	}
	var rec string
	err := websocket.Message.Receive(c.ws, &rec)
	if err != nil {
		return fmt.Errorf("Error receiving welcome message [%s]: %s", rec, err)
	}
	if typ := parseMessageType(rec); typ != msgWelcome {
		return fmt.Errorf("First message received was not welcome, instead received: %s", rec)
	}
	var msg welcomeMsg
	err = json.Unmarshal([]byte(rec), &msg)
	if err != nil {
		return fmt.Errorf("Error unmarshalling welcome message: %s", err)
	}
	c.SessionId = msg.SessionId
	c.ProtocolVersion = msg.ProtocolVersion
	c.ServerIdent = msg.ServerIdent
	if debug {
		log.Printf("turnpike: session id: %s", c.SessionId)
		log.Printf("turnpike: protocol version: %d", c.ProtocolVersion)
		log.Printf("turnpike: server ident: %s", c.ServerIdent)
	}

	if c.sessionOpenCallback != nil {
		c.sessionOpenCallback(c.SessionId)
	}

	return nil
}

func (c *Client) receive() {
	for {
		var rec string
		err := websocket.Message.Receive(c.ws, &rec)
		if err != nil {
			if err != io.EOF {
				if debug {
					log.Printf("turnpike: error receiving message, aborting connection: %s", err)
				}
			}
			break
		}
		if debug {
			log.Printf("turnpike: message received: %s", rec)
		}

		data := []byte(rec)

		switch typ := parseMessageType(rec); typ {
		case msgCallResult:
			var msg callResultMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				if debug {
					log.Printf("turnpike: error unmarshalling call result message: %s", err)
				}
				continue
			}
			c.handleCallResult(msg)
		case msgCallError:
			var msg callErrorMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				if debug {
					log.Printf("turnpike: error unmarshalling call error message: %s", err)
				}
				continue
			}
			c.handleCallError(msg)
		case msgEvent:
			var msg eventMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				if debug {
					log.Printf("turnpike: error unmarshalling event message: %s", err)
				}
				continue
			}
			c.handleEvent(msg)
		case msgPrefix, msgCall, msgSubscribe, msgUnsubscribe, msgPublish:
			if debug {
				log.Printf("turnpike: client -> server message received, ignored: %s", messageTypeString(typ))
			}
		case msgWelcome:
			if debug {
				log.Print("turnpike: received extraneous welcome message, ignored")
			}
		default:
			if debug {
				log.Printf("turnpike: invalid message format, message dropped: %s", data)
			}
		}
	}
}

func (c *Client) send() {
	for msg := range c.messages {
		if debug {
			log.Printf("turnpike: sending message: %s", msg)
		}
		if err := websocket.Message.Send(c.ws, msg); err != nil {
			if debug {
				log.Printf("turnpike: error sending message: %s", err)
			}
		}
	}
}

// Connect will connect to server with an optional origin.
// More details here: http://godoc.org/code.google.com/p/go.net/websocket#Dial
func (c *Client) Connect(server, origin string) error {
	if debug {
		log.Print("turnpike: connect")
	}
	var err error
	if c.ws, err = websocket.Dial(server, wampProtocolId, origin); err != nil {
		return fmt.Errorf("Error connecting to websocket server: %s", err)
	}

	// Receive welcome message
	if err = c.receiveWelcome(); err != nil {
		return err
	}
	if debug {
		log.Printf("turnpike: connected to server: %s", server)
	}

	go c.receive()
	go c.send()

	return nil
}

func (c *Client) Disconnect() error {
	if c.ws != nil {
		return c.ws.Close()
	}
	return nil
}

// SetSessionOpenCallback adds a callback function that is run when a new session begins.
// The callback function must accept a string argument that is the session ID.
func (c *Client) SetSessionOpenCallback(f func(string)) {
	c.sessionOpenCallback = f
}

// newId generates a random string of fixed size.
func newId(size int) string {
	const alpha = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz0123456789-_"
	buf := make([]byte, size)
	for i := 0; i < size; i++ {
		buf[i] = alpha[rand.Intn(len(alpha))]
	}
	return string(buf)
}
<commit_msg>Add connect function that allows headers to be passed on the initial connection<commit_after>// Copyright (c) 2013 Joshua Elliott
// Released under the MIT License
// http://opensource.org/licenses/MIT

package turnpike

import (
	//	"code.google.com/p/go.net/websocket"
	"encoding/json"
	"fmt"
	"golang.org/x/net/websocket"
	"io"
	"log"
	"math/rand"
)

const (
	wampProtocolId = "wamp"
)

var clientBacklog = 10

// Client represents a WAMP client that handles RPC and pub/sub.
type Client struct {
	// SessionId is a ID of the session in UUID4 format received at the start of the session.
	SessionId string
	// ProtocolVersion is the version of the WAMP protocol received at the start of the session.
	ProtocolVersion int
	// ServerIdent is the server ID (ie "turnpike, autobahn") received at the start of the session.
	ServerIdent         string
	ws                  *websocket.Conn
	messages            chan string
	prefixes            prefixMap
	eventHandlers       map[string]EventHandler
	calls               map[string]chan CallResult
	sessionOpenCallback func(string)
}

// CallResult represents either a sucess or a failure after a RPC call.
type CallResult struct {
	// Result contains the RPC call result returned by the server.
	Result interface{}
	// Error is nil on call success otherwise it contains the RPC error.
	Error error
}

// EventHandler is an interface for handlers to published events. The topicURI
// is the URI of the event and event is the event contents.
type EventHandler func(topicURI string, event interface{})

// NewClient creates a new WAMP client.
func NewClient() *Client {
	return &Client{
		messages:      make(chan string, clientBacklog),
		prefixes:      make(prefixMap),
		eventHandlers: make(map[string]EventHandler),
		calls:         make(map[string]chan CallResult),
	}
}

// Prefix sets a CURIE prefix at the server for later use when interacting with
// the server. prefix is the first part of a CURIE (ie "calc") and URI is a full
// identifier (ie "http://example.com/simple/calc#") that is mapped to the prefix.
//
// Ref: http://wamp.ws/spec#prefix_message
func (c *Client) Prefix(prefix, URI string) error {
	if debug {
		log.Printf("turnpike: sending prefix %s:%s", prefix, URI)
	}
	err := c.prefixes.registerPrefix(prefix, URI)
	if err != nil {
		return fmt.Errorf("turnpike: %s", err)
	}
	msg, err := createPrefix(prefix, URI)
	if err != nil {
		return fmt.Errorf("turnpike: %s", err)
	}
	c.messages <- string(msg)
	return nil
}

// Call makes a RPC call on the server identified by procURI (in either full URI
// or CURIE format) with zero or more args. Returns a channel that will receive
// the call result (or error) on completion.
//
// Ref: http://wamp.ws/spec#call_message
func (c *Client) Call(procURI string, args ...interface{}) chan CallResult {
	if debug {
		log.Print("turnpike: sending call")
	}
	// Channel size must be 1 to avoid blocking if no one is receiving the channel later.
	resultCh := make(chan CallResult, 1)
	callId := newId(16)
	msg, err := createCall(callId, procURI, args...)
	if err != nil {
		r := CallResult{
			Result: nil,
			Error:  fmt.Errorf("turnpike: %s", err),
		}
		resultCh <- r
		return resultCh
	}
	c.calls[callId] = resultCh
	c.messages <- string(msg)
	return resultCh
}

// Subscribe adds a subscription at the server for events with topicURI lasting
// for the session or until Unsubscribe is called.
//
// Ref: http://wamp.ws/spec#subscribe_message
func (c *Client) Subscribe(topicURI string, f EventHandler) error {
	if debug {
		log.Print("turnpike: sending subscribe")
	}
	msg, err := createSubscribe(topicURI)
	if err != nil {
		return fmt.Errorf("turnpike: %s", err)
	}
	c.messages <- string(msg)
	if f != nil {
		c.eventHandlers[topicURI] = f
	}
	return nil
}

// Unsubscribe removes a previous subscription with topicURI at the server.
//
// Ref: http://wamp.ws/spec#unsubscribe_message
func (c *Client) Unsubscribe(topicURI string) error {
	if debug {
		log.Print("turnpike: sending unsubscribe")
	}
	msg, err := createUnsubscribe(topicURI)
	if err != nil {
		return fmt.Errorf("turnpike: %s", err)
	}
	c.messages <- string(msg)
	delete(c.eventHandlers, topicURI)
	return nil
}

// Publish publishes an event to the topicURI that gets sent to all subscribers
// of that topicURI by the server. opts can can be either empty, one boolean
// that can be used to exclude outself from receiving the event or two lists;
// the first a list of clients to exclude and the second a list of clients that
// are eligible to receive the event. Either list can be empty.
//
// Ref: http://wamp.ws/spec#publish_message
func (c *Client) Publish(topicURI string, event interface{}, opts ...interface{}) error {
	if debug {
		log.Print("turnpike: sending publish")
	}
	msg, err := createPublish(topicURI, event, opts...)
	if err != nil {
		return fmt.Errorf("turnpike: %s", err)
	}
	c.messages <- string(msg)
	return nil
}

// PublishExcludeMe is a short hand for Publish(tobicURI, event, true) that will
// not send the event to ourself.
func (c *Client) PublishExcludeMe(topicURI string, event interface{}) error {
	return c.Publish(topicURI, event, true)
}

func (c *Client) handleCallResult(msg callResultMsg) {
	if debug {
		log.Print("turnpike: handling call result message")
	}
	resultCh, ok := c.calls[msg.CallID]
	if !ok {
		if debug {
			log.Print("turnpike: missing call result handler")
		}
		return
	}
	delete(c.calls, msg.CallID)
	r := CallResult{
		Result: msg.Result,
		Error:  nil,
	}
	resultCh <- r
}

func (c *Client) handleCallError(msg callErrorMsg) {
	if debug {
		log.Print("turnpike: handling call error message")
	}
	resultCh, ok := c.calls[msg.CallID]
	if !ok {
		if debug {
			log.Print("turnpike: missing call result handler")
		}
		return
	}
	delete(c.calls, msg.CallID)
	r := CallResult{
		Error: RPCError{
			URI:         msg.ErrorURI,
			Description: msg.ErrorDesc,
			Details:     msg.ErrorDetails,
		},
	}
	resultCh <- r
}

func (c *Client) handleEvent(msg eventMsg) {
	if debug {
		log.Print("turnpike: handling event message")
	}
	if f, ok := c.eventHandlers[msg.TopicURI]; ok && f != nil {
		f(msg.TopicURI, msg.Event)
	} else {
		if debug {
			log.Printf("turnpike: missing event handler for URI: %s", msg.TopicURI)
		}
	}
}

func (c *Client) receiveWelcome() error {
	if debug {
		log.Print("turnpike: receive welcome")
	}
	var rec string
	err := websocket.Message.Receive(c.ws, &rec)
	if err != nil {
		return fmt.Errorf("Error receiving welcome message [%s]: %s", rec, err)
	}
	if typ := parseMessageType(rec); typ != msgWelcome {
		return fmt.Errorf("First message received was not welcome, instead received: %s", rec)
	}
	var msg welcomeMsg
	err = json.Unmarshal([]byte(rec), &msg)
	if err != nil {
		return fmt.Errorf("Error unmarshalling welcome message: %s", err)
	}
	c.SessionId = msg.SessionId
	c.ProtocolVersion = msg.ProtocolVersion
	c.ServerIdent = msg.ServerIdent
	if debug {
		log.Printf("turnpike: session id: %s", c.SessionId)
		log.Printf("turnpike: protocol version: %d", c.ProtocolVersion)
		log.Printf("turnpike: server ident: %s", c.ServerIdent)
	}

	if c.sessionOpenCallback != nil {
		c.sessionOpenCallback(c.SessionId)
	}

	return nil
}

func (c *Client) receive() {
	for {
		var rec string
		err := websocket.Message.Receive(c.ws, &rec)
		if err != nil {
			if err != io.EOF {
				if debug {
					log.Printf("turnpike: error receiving message, aborting connection: %s", err)
				}
			}
			break
		}
		if debug {
			log.Printf("turnpike: message received: %s", rec)
		}

		data := []byte(rec)

		switch typ := parseMessageType(rec); typ {
		case msgCallResult:
			var msg callResultMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				if debug {
					log.Printf("turnpike: error unmarshalling call result message: %s", err)
				}
				continue
			}
			c.handleCallResult(msg)
		case msgCallError:
			var msg callErrorMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				if debug {
					log.Printf("turnpike: error unmarshalling call error message: %s", err)
				}
				continue
			}
			c.handleCallError(msg)
		case msgEvent:
			var msg eventMsg
			err := json.Unmarshal(data, &msg)
			if err != nil {
				if debug {
					log.Printf("turnpike: error unmarshalling event message: %s", err)
				}
				continue
			}
			c.handleEvent(msg)
		case msgPrefix, msgCall, msgSubscribe, msgUnsubscribe, msgPublish:
			if debug {
				log.Printf("turnpike: client -> server message received, ignored: %s", messageTypeString(typ))
			}
		case msgWelcome:
			if debug {
				log.Print("turnpike: received extraneous welcome message, ignored")
			}
		default:
			if debug {
				log.Printf("turnpike: invalid message format, message dropped: %s", data)
			}
		}
	}
}

func (c *Client) send() {
	for msg := range c.messages {
		if debug {
			log.Printf("turnpike: sending message: %s", msg)
		}
		if err := websocket.Message.Send(c.ws, msg); err != nil {
			if debug {
				log.Printf("turnpike: error sending message: %s", err)
			}
		}
	}
}

func (c *Client) ConnectWithHeaders(server, origin string, headers map[string]string) error {
	return c.connect(server, origin, headers)
}

// Connect will connect to server with an optional origin.
// More details here: http://godoc.org/code.google.com/p/go.net/websocket#Dial
func (c *Client) Connect(server, origin string) error {
	return c.connect(server, origin, nil)
}

func (c *Client) connect(server, origin string, headers map[string]string) error {
	if debug {
		log.Print("turnpike: connect")
	}
	var err error
	if c.ws, err = c.dialWithHeaders(server, wampProtocolId, origin, headers); err != nil {
		return fmt.Errorf("Error connecting to websocket server: %s", err)
	}

	// Receive welcome message
	if err = c.receiveWelcome(); err != nil {
		return err
	}
	if debug {
		log.Printf("turnpike: connected to server: %s", server)
	}

	go c.receive()
	go c.send()

	return nil
}

func (c *Client) dialWithHeaders(url_, protocol, origin string, headers map[string]string) (ws *websocket.Conn, err error) {
	config, err := websocket.NewConfig(url_, origin)
	if err != nil {
		return nil, err
	}
	if protocol != "" {
		config.Protocol = []string{protocol}
	}
	if headers != nil {
		for key, val := range headers {
			config.Header.Add(key, val)
		}
	}
	return websocket.DialConfig(config)
}

func (c *Client) Disconnect() error {
	if c.ws != nil {
		return c.ws.Close()
	}
	return nil
}

// SetSessionOpenCallback adds a callback function that is run when a new session begins.
// The callback function must accept a string argument that is the session ID.
func (c *Client) SetSessionOpenCallback(f func(string)) {
	c.sessionOpenCallback = f
}

// newId generates a random string of fixed size.
func newId(size int) string {
	const alpha = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz0123456789-_"
	buf := make([]byte, size)
	for i := 0; i < size; i++ {
		buf[i] = alpha[rand.Intn(len(alpha))]
	}
	return string(buf)
}
<|endoftext|>


<commit_before>package alidayu

import (
	"crypto/hmac"
	"crypto/md5"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"
)

const (
	// httpURL is for HTTP REST API URL.
	httpURL string = "http://gw.api.taobao.com/router/rest"
	// httpsURL is for HTTPS REST API URL.
	httpsURL string = "https://eco.taobao.com/router/rest"

	// successTagJSON is success tag for JSON in response string.
	successTagJSON string = `"success":true`
	// successTagXML is sucess tag for XML in response string
	successTagXML string = `<success>true</success>`
)

var (
	// DefCommonParams is default common parameters of alidayu APIs
	DefCommonParams = map[string]string{
		"format":      "json", // Response Format("json" or "xml")
		"v":           "2.0",  // API version("2.0")
		"sign_method": "md5",  // Sign method("md5" or "hmac")
	}
)

// Client contains app key and secret and provides method to post HTTP request like http.Request.
type Client struct {
	AppKey    string // App Key
	AppSecret string // App Secret
	UseHTTPS  bool   // Use HTTPS URL or not
	http.Client
}

// IsValid checks if a client is valid.
func (c *Client) IsValid() bool {
	if c.AppKey == "" || c.AppSecret == "" {
		return false
	}

	return true
}

// UpdateCommonParams updates the given parameters by merge default common parameters.
func (c *Client) UpdateCommonParams(params map[string]string) {
	for k, v := range DefCommonParams {
		if _, ok := params[k]; !ok {
			params[k] = v
		}
	}

	// Set App Key.
	params["app_key"] = c.AppKey

	// Set timestamp.
	t := time.Now()
	params["timestamp"] = fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second())
}

// GetSortedQueryStr sorts the keys of parameters and generate sorted query string which can be used to sign(MD5 or HMAC_MD5).
func GetSortedQueryStr(params map[string]string) (sortedQueryStr string) {
	keys := []string{}
	for k := range params {
		keys = append(keys, k)
	}

	str := ""
	sort.Strings(keys)
	for _, k := range keys {
		str += k + params[k]
	}
	return str
}

// SignMD5 gets the sorted query string and sign it using MD5.
func (c *Client) SignMD5(params map[string]string) (signature string) {
	// Sort keys by name to generate MD5 hash.
	// See details at:
	//     http://open.taobao.com/doc2/detail.htm?articleId=101617&docType=1&treeId=1
	str := fmt.Sprintf("%s%s%s", c.AppSecret, GetSortedQueryStr(params), c.AppSecret)

	return fmt.Sprintf("%X", md5.Sum([]byte(str)))

}

// SignHMAC gets the sorted query string and sign it using HMAC_MD5.
func (c *Client) SignHMAC(params map[string]string) (signature string) {
	// Sort keys by name to generate HMAC_MD5 hash.
	// See details at:
	//     http://open.taobao.com/doc2/detail.htm?articleId=101617&docType=1&treeId=1
	str := GetSortedQueryStr(params)

	// HMAC_MD5
	mac := hmac.New(md5.New, []byte(c.AppSecret))
	mac.Write([]byte(str))
	return fmt.Sprintf("%X", mac.Sum(nil))
}

// MakeRequestBody makes the HTTP request body by given parameters for each REST API.
func (c *Client) MakeRequestBody(params map[string]string) (body io.Reader, err error) {
	if !c.IsValid() {
		return nil, fmt.Errorf("Empty App Key or App Secret.")
	}

	values := url.Values{}

	// Check "method".
	if _, ok := params["method"]; !ok {
		return nil, fmt.Errorf("No method specified.")
	}

	// Update Common Params.
	c.UpdateCommonParams(params)

	// Check "format".
	if params["format"] != "json" && params["format"] != "xml" {
		return nil, fmt.Errorf("format error: %v", params["format"])
	}

	sign := ""
	switch params["sign_method"] {
	case "md5":
		sign = c.SignMD5(params)
	case "hmac":
		sign = c.SignHMAC(params)
	default:
		return nil, fmt.Errorf("Incorrect sign_method.")
	}

	params["sign"] = sign

	for k, v := range params {
		values.Set(k, v)
	}

	return strings.NewReader(values.Encode()), nil
}

// Post does the HTTP post for each REST API.
//
//   Params:
//     params: map that contains parameters of REST API. See official docs to fill the parameters.
//   Returns:
//     resp: HTTP Response. Do not forget to call resp.Body.Close() after use.
func (c *Client) Post(params map[string]string) (resp *http.Response, err error) {
	var body io.Reader

	if body, err = c.MakeRequestBody(params); err != nil {
		return nil, err
	}

	urlStr := ""
	if c.UseHTTPS {
		urlStr = httpsURL
	} else {
		urlStr = httpURL
	}

	req, err := http.NewRequest("POST", urlStr, body)
	if err != nil {
		return nil, err
	}

	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	return c.Do(req)
}

// Exec executes the REST API and get the response. It's a wrapper of Post().
//
//   Params:
//     params: map that contains parameters of REST API. See official docs to fill the parameters.
//   Returns:
//     success: If REST API succeeds.
//     result: Raw response string of REST API.
func (c *Client) Exec(params map[string]string) (success bool, result string, err error) {
	resp, err := c.Post(params)
	if err != nil {
		return false, "", err
	}

	defer resp.Body.Close()
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return false, "", err
	}

	result = string(data)

	switch params["format"] {
	case "json":
		success = strings.Contains(result, successTagJSON)
	case "xml":
		success = strings.Contains(result, successTagXML)
	}

	return success, result, nil
}
<commit_msg>Fix misspelling: success.<commit_after>package alidayu

import (
	"crypto/hmac"
	"crypto/md5"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"
)

const (
	// httpURL is for HTTP REST API URL.
	httpURL string = "http://gw.api.taobao.com/router/rest"
	// httpsURL is for HTTPS REST API URL.
	httpsURL string = "https://eco.taobao.com/router/rest"

	// successTagJSON is success tag for JSON in response string.
	successTagJSON string = `"success":true`
	// successTagXML is success tag for XML in response string
	successTagXML string = `<success>true</success>`
)

var (
	// DefCommonParams is default common parameters of alidayu APIs
	DefCommonParams = map[string]string{
		"format":      "json", // Response Format("json" or "xml")
		"v":           "2.0",  // API version("2.0")
		"sign_method": "md5",  // Sign method("md5" or "hmac")
	}
)

// Client contains app key and secret and provides method to post HTTP request like http.Request.
type Client struct {
	AppKey    string // App Key
	AppSecret string // App Secret
	UseHTTPS  bool   // Use HTTPS URL or not
	http.Client
}

// IsValid checks if a client is valid.
func (c *Client) IsValid() bool {
	if c.AppKey == "" || c.AppSecret == "" {
		return false
	}

	return true
}

// UpdateCommonParams updates the given parameters by merge default common parameters.
func (c *Client) UpdateCommonParams(params map[string]string) {
	for k, v := range DefCommonParams {
		if _, ok := params[k]; !ok {
			params[k] = v
		}
	}

	// Set App Key.
	params["app_key"] = c.AppKey

	// Set timestamp.
	t := time.Now()
	params["timestamp"] = fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second())
}

// GetSortedQueryStr sorts the keys of parameters and generate sorted query string which can be used to sign(MD5 or HMAC_MD5).
func GetSortedQueryStr(params map[string]string) (sortedQueryStr string) {
	keys := []string{}
	for k := range params {
		keys = append(keys, k)
	}

	str := ""
	sort.Strings(keys)
	for _, k := range keys {
		str += k + params[k]
	}
	return str
}

// SignMD5 gets the sorted query string and sign it using MD5.
func (c *Client) SignMD5(params map[string]string) (signature string) {
	// Sort keys by name to generate MD5 hash.
	// See details at:
	//     http://open.taobao.com/doc2/detail.htm?articleId=101617&docType=1&treeId=1
	str := fmt.Sprintf("%s%s%s", c.AppSecret, GetSortedQueryStr(params), c.AppSecret)

	return fmt.Sprintf("%X", md5.Sum([]byte(str)))

}

// SignHMAC gets the sorted query string and sign it using HMAC_MD5.
func (c *Client) SignHMAC(params map[string]string) (signature string) {
	// Sort keys by name to generate HMAC_MD5 hash.
	// See details at:
	//     http://open.taobao.com/doc2/detail.htm?articleId=101617&docType=1&treeId=1
	str := GetSortedQueryStr(params)

	// HMAC_MD5
	mac := hmac.New(md5.New, []byte(c.AppSecret))
	mac.Write([]byte(str))
	return fmt.Sprintf("%X", mac.Sum(nil))
}

// MakeRequestBody makes the HTTP request body by given parameters for each REST API.
func (c *Client) MakeRequestBody(params map[string]string) (body io.Reader, err error) {
	if !c.IsValid() {
		return nil, fmt.Errorf("Empty App Key or App Secret.")
	}

	values := url.Values{}

	// Check "method".
	if _, ok := params["method"]; !ok {
		return nil, fmt.Errorf("No method specified.")
	}

	// Update Common Params.
	c.UpdateCommonParams(params)

	// Check "format".
	if params["format"] != "json" && params["format"] != "xml" {
		return nil, fmt.Errorf("format error: %v", params["format"])
	}

	sign := ""
	switch params["sign_method"] {
	case "md5":
		sign = c.SignMD5(params)
	case "hmac":
		sign = c.SignHMAC(params)
	default:
		return nil, fmt.Errorf("Incorrect sign_method.")
	}

	params["sign"] = sign

	for k, v := range params {
		values.Set(k, v)
	}

	return strings.NewReader(values.Encode()), nil
}

// Post does the HTTP post for each REST API.
//
//   Params:
//     params: map that contains parameters of REST API. See official docs to fill the parameters.
//   Returns:
//     resp: HTTP Response. Do not forget to call resp.Body.Close() after use.
func (c *Client) Post(params map[string]string) (resp *http.Response, err error) {
	var body io.Reader

	if body, err = c.MakeRequestBody(params); err != nil {
		return nil, err
	}

	urlStr := ""
	if c.UseHTTPS {
		urlStr = httpsURL
	} else {
		urlStr = httpURL
	}

	req, err := http.NewRequest("POST", urlStr, body)
	if err != nil {
		return nil, err
	}

	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	return c.Do(req)
}

// Exec executes the REST API and get the response. It's a wrapper of Post().
//
//   Params:
//     params: map that contains parameters of REST API. See official docs to fill the parameters.
//   Returns:
//     success: If REST API succeeds.
//     result: Raw response string of REST API.
func (c *Client) Exec(params map[string]string) (success bool, result string, err error) {
	resp, err := c.Post(params)
	if err != nil {
		return false, "", err
	}

	defer resp.Body.Close()
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return false, "", err
	}

	result = string(data)

	switch params["format"] {
	case "json":
		success = strings.Contains(result, successTagJSON)
	case "xml":
		success = strings.Contains(result, successTagXML)
	}

	return success, result, nil
}
<|endoftext|>


<commit_before>package arp

import (
	"bytes"
	"errors"
	"net"
	"syscall"
	"time"

	"github.com/mdlayher/ethernet"
	"github.com/mdlayher/raw"
)

var (
	// errNoIPv4Addr is returned when an interface does not have an IPv4
	// address.
	errNoIPv4Addr = errors.New("no IPv4 address available for interface")
)

// A Client is an ARP client, which can be used to send ARP requests to
// retrieve the MAC address of a machine using its IPv4 address.
type Client struct {
	ifi *net.Interface
	ip  net.IP
	p   net.PacketConn
}

// NewClient creates a new Client using the specified network interface.
// NewClient retrieves the IPv4 address of the interface and binds a raw socket
// to send and receive ARP packets.
func NewClient(ifi *net.Interface) (*Client, error) {
	// Open raw socket to send and receive ARP packets using ethernet frames
	// we build ourselves
	p, err := raw.ListenPacket(ifi, syscall.SOCK_RAW, syscall.ETH_P_ARP)
	if err != nil {
		return nil, err
	}

	// Check for usable IPv4 addresses for the Client
	addrs, err := ifi.Addrs()
	if err != nil {
		return nil, err
	}

	return newClient(ifi, p, addrs)
}

// newClient is the internal, generic implementation of newClient.  It is used
// to allow an arbitrary net.PacketConn to be used in a Client, so testing
// is easier to accomplish.
func newClient(ifi *net.Interface, p net.PacketConn, addrs []net.Addr) (*Client, error) {
	ip, err := firstIPv4Addr(addrs)
	if err != nil {
		return nil, err
	}

	return &Client{
		ifi: ifi,
		ip:  ip,
		p:   p,
	}, nil
}

// Close closes the Client's raw socket and stops sending and receiving
// ARP packets.
func (c *Client) Close() error {
	return c.p.Close()
}

// Request performs an ARP request, attempting to retrieve the MAC address
// of a machine using its IPv4 address.
func (c *Client) Request(ip net.IP) (net.HardwareAddr, error) {
	// Create ARP packet addressed to broadcast MAC to attempt to find the
	// hardware address of the input IP address
	arp, err := NewPacket(OperationRequest, c.ifi.HardwareAddr, c.ip, ethernet.Broadcast, ip)
	if err != nil {
		return nil, err
	}
	arpb, err := arp.MarshalBinary()
	if err != nil {
		return nil, err
	}

	// Create ethernet frame addressed to broadcast MAC to encapsulate the
	// ARP packet
	eth := &ethernet.Frame{
		DestinationMAC: ethernet.Broadcast,
		SourceMAC:      c.ifi.HardwareAddr,
		EtherType:      ethernet.EtherTypeARP,
		Payload:        arpb,
	}
	ethb, err := eth.MarshalBinary()
	if err != nil {
		return nil, err
	}

	// Write frame to ethernet broadcast address
	_, err = c.p.WriteTo(ethb, &raw.Addr{
		HardwareAddr: ethernet.Broadcast,
	})
	if err != nil {
		return nil, err
	}

	// Loop and wait for replies
	buf := make([]byte, 128)
	for {
		n, _, err := c.p.ReadFrom(buf)
		if err != nil {
			return nil, err
		}

		// Unmarshal ethernet frame and ARP packet
		if err := eth.UnmarshalBinary(buf[:n]); err != nil {
			return nil, err
		}
		if err := arp.UnmarshalBinary(eth.Payload); err != nil {
			return nil, err
		}

		// Check if ARP is in reply to our MAC address
		if bytes.Equal(arp.TargetMAC, c.ifi.HardwareAddr) {
			return arp.SenderMAC, nil
		}
	}
}

// Copyright (c) 2012 The Go Authors. All rights reserved.
// Source code in this file is based on src/net/interface_linux.go,
// from the Go standard library.  The Go license can be found here:
// https://golang.org/LICENSE.

// Documentation taken from net.PacketConn interface.  Thanks:
// http://golang.org/pkg/net/#PacketConn.

// SetDeadline sets the read and write deadlines associated with the
// connection.
func (c *Client) SetDeadline(t time.Time) error {
	return c.p.SetDeadline(t)
}

// SetReadDeadline sets the deadline for future raw socket read calls.
// If the deadline is reached, a raw socket read will fail with a timeout
// (see type net.Error) instead of blocking.
// A zero value for t means a raw socket read will not time out.
func (c *Client) SetReadDeadline(t time.Time) error {
	return c.p.SetReadDeadline(t)
}

// SetWriteDeadline sets the deadline for future raw socket write calls.
// If the deadline is reached, a raw socket write will fail with a timeout
// (see type net.Error) instead of blocking.
// A zero value for t means a raw socket write will not time out.
// Even if a write times out, it may return n > 0, indicating that
// some of the data was successfully written.
func (c *Client) SetWriteDeadline(t time.Time) error {
	return c.p.SetWriteDeadline(t)
}

// firstIPv4Addr attempts to retrieve the first detected IPv4 address from an
// input slice of network addresses.
func firstIPv4Addr(addrs []net.Addr) (net.IP, error) {
	for _, a := range addrs {
		if a.Network() != "ip+net" {
			continue
		}

		ip, _, err := net.ParseCIDR(a.String())
		if err != nil {
			return nil, err
		}

		// "If ip is not an IPv4 address, To4 returns nil."
		// Reference: http://golang.org/pkg/net/#IP.To4
		if ip4 := ip.To4(); ip4 != nil {
			return ip4, nil
		}
	}

	return nil, errNoIPv4Addr
}
<commit_msg>client: add additional sanity checks in Client.Request<commit_after>package arp

import (
	"bytes"
	"errors"
	"net"
	"syscall"
	"time"

	"github.com/mdlayher/ethernet"
	"github.com/mdlayher/raw"
)

var (
	// errNoIPv4Addr is returned when an interface does not have an IPv4
	// address.
	errNoIPv4Addr = errors.New("no IPv4 address available for interface")
)

// A Client is an ARP client, which can be used to send ARP requests to
// retrieve the MAC address of a machine using its IPv4 address.
type Client struct {
	ifi *net.Interface
	ip  net.IP
	p   net.PacketConn
}

// NewClient creates a new Client using the specified network interface.
// NewClient retrieves the IPv4 address of the interface and binds a raw socket
// to send and receive ARP packets.
func NewClient(ifi *net.Interface) (*Client, error) {
	// Open raw socket to send and receive ARP packets using ethernet frames
	// we build ourselves
	p, err := raw.ListenPacket(ifi, syscall.SOCK_RAW, syscall.ETH_P_ARP)
	if err != nil {
		return nil, err
	}

	// Check for usable IPv4 addresses for the Client
	addrs, err := ifi.Addrs()
	if err != nil {
		return nil, err
	}

	return newClient(ifi, p, addrs)
}

// newClient is the internal, generic implementation of newClient.  It is used
// to allow an arbitrary net.PacketConn to be used in a Client, so testing
// is easier to accomplish.
func newClient(ifi *net.Interface, p net.PacketConn, addrs []net.Addr) (*Client, error) {
	ip, err := firstIPv4Addr(addrs)
	if err != nil {
		return nil, err
	}

	return &Client{
		ifi: ifi,
		ip:  ip,
		p:   p,
	}, nil
}

// Close closes the Client's raw socket and stops sending and receiving
// ARP packets.
func (c *Client) Close() error {
	return c.p.Close()
}

// Request performs an ARP request, attempting to retrieve the MAC address
// of a machine using its IPv4 address.
func (c *Client) Request(ip net.IP) (net.HardwareAddr, error) {
	// Create ARP packet addressed to broadcast MAC to attempt to find the
	// hardware address of the input IP address
	arp, err := NewPacket(OperationRequest, c.ifi.HardwareAddr, c.ip, ethernet.Broadcast, ip)
	if err != nil {
		return nil, err
	}
	arpb, err := arp.MarshalBinary()
	if err != nil {
		return nil, err
	}

	// Create ethernet frame addressed to broadcast MAC to encapsulate the
	// ARP packet
	eth := &ethernet.Frame{
		DestinationMAC: ethernet.Broadcast,
		SourceMAC:      c.ifi.HardwareAddr,
		EtherType:      ethernet.EtherTypeARP,
		Payload:        arpb,
	}
	ethb, err := eth.MarshalBinary()
	if err != nil {
		return nil, err
	}

	// Write frame to ethernet broadcast address
	_, err = c.p.WriteTo(ethb, &raw.Addr{
		HardwareAddr: ethernet.Broadcast,
	})
	if err != nil {
		return nil, err
	}

	// Loop and wait for replies
	buf := make([]byte, 128)
	for {
		n, _, err := c.p.ReadFrom(buf)
		if err != nil {
			return nil, err
		}

		// Unmarshal ethernet frame and check:
		//   - Frame is for our MAC address
		//   - Frame has ARP EtherType
		if err := eth.UnmarshalBinary(buf[:n]); err != nil {
			return nil, err
		}
		if !bytes.Equal(eth.DestinationMAC, c.ifi.HardwareAddr) {
			continue
		}
		if eth.EtherType != ethernet.EtherTypeARP {
			continue
		}

		// Unmarshal ARP packet and check:
		//   - Packet is a reply, not a request
		//   - Packet is for our IP address
		//   - Packet is for our MAC address
		if err := arp.UnmarshalBinary(eth.Payload); err != nil {
			return nil, err
		}
		if arp.Operation != OperationReply {
			continue
		}
		if !bytes.Equal(arp.TargetIP, c.ip) {
			continue
		}
		if !bytes.Equal(arp.TargetMAC, c.ifi.HardwareAddr) {
			continue
		}

		return arp.SenderMAC, nil
	}
}

// Copyright (c) 2012 The Go Authors. All rights reserved.
// Source code in this file is based on src/net/interface_linux.go,
// from the Go standard library.  The Go license can be found here:
// https://golang.org/LICENSE.

// Documentation taken from net.PacketConn interface.  Thanks:
// http://golang.org/pkg/net/#PacketConn.

// SetDeadline sets the read and write deadlines associated with the
// connection.
func (c *Client) SetDeadline(t time.Time) error {
	return c.p.SetDeadline(t)
}

// SetReadDeadline sets the deadline for future raw socket read calls.
// If the deadline is reached, a raw socket read will fail with a timeout
// (see type net.Error) instead of blocking.
// A zero value for t means a raw socket read will not time out.
func (c *Client) SetReadDeadline(t time.Time) error {
	return c.p.SetReadDeadline(t)
}

// SetWriteDeadline sets the deadline for future raw socket write calls.
// If the deadline is reached, a raw socket write will fail with a timeout
// (see type net.Error) instead of blocking.
// A zero value for t means a raw socket write will not time out.
// Even if a write times out, it may return n > 0, indicating that
// some of the data was successfully written.
func (c *Client) SetWriteDeadline(t time.Time) error {
	return c.p.SetWriteDeadline(t)
}

// firstIPv4Addr attempts to retrieve the first detected IPv4 address from an
// input slice of network addresses.
func firstIPv4Addr(addrs []net.Addr) (net.IP, error) {
	for _, a := range addrs {
		if a.Network() != "ip+net" {
			continue
		}

		ip, _, err := net.ParseCIDR(a.String())
		if err != nil {
			return nil, err
		}

		// "If ip is not an IPv4 address, To4 returns nil."
		// Reference: http://golang.org/pkg/net/#IP.To4
		if ip4 := ip.To4(); ip4 != nil {
			return ip4, nil
		}
	}

	return nil, errNoIPv4Addr
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strings"
	"time"

	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/terminal"
)

const MSG_BUFFER int = 10

const HELP_TEXT string = `-> Available commands:
   /about
   /exit
   /help
   /list
   /nick $NAME
   /whois $NAME
`

const ABOUT_TEXT string = `-> ssh-chat is made by @shazow.

   It is a custom ssh server built in Go to serve a chat experience
   instead of a shell.

   Source: https://github.com/shazow/ssh-chat

   For more, visit shazow.net or follow at twitter.com/shazow
`

type Client struct {
	Server        *Server
	Conn          *ssh.ServerConn
	Msg           chan string
	Name          string
	Color         string
	Op            bool
	ready         chan struct{}
	term          *terminal.Terminal
	termWidth     int
	termHeight    int
	silencedUntil time.Time
}

func NewClient(server *Server, conn *ssh.ServerConn) *Client {
	return &Client{
		Server: server,
		Conn:   conn,
		Name:   conn.User(),
		Color:  RandomColor(),
		Msg:    make(chan string, MSG_BUFFER),
		ready:  make(chan struct{}, 1),
	}
}

func (c *Client) ColoredName() string {
    return ColorString(c.Color, c.Name)
}

func (c *Client) Write(msg string) {
	c.term.Write([]byte(msg + "\r\n"))
}

func (c *Client) WriteLines(msg []string) {
	for _, line := range msg {
		c.Write(line)
	}
}

func (c *Client) IsSilenced() bool {
	return c.silencedUntil.After(time.Now())
}

func (c *Client) Silence(d time.Duration) {
	c.silencedUntil = time.Now().Add(d)
}

func (c *Client) Resize(width int, height int) error {
	err := c.term.SetSize(width, height)
	if err != nil {
		logger.Errorf("Resize failed: %dx%d", width, height)
		return err
	}
	c.termWidth, c.termHeight = width, height
	return nil
}

func (c *Client) Rename(name string) {
	c.Name = name
	c.term.SetPrompt(fmt.Sprintf("[%s] ", c.ColoredName()))
}

func (c *Client) Fingerprint() string {
	return c.Conn.Permissions.Extensions["fingerprint"]
}

func (c *Client) handleShell(channel ssh.Channel) {
	defer channel.Close()

	// FIXME: This shouldn't live here, need to restructure the call chaining.
	c.Server.Add(c)
	go func() {
		// Block until done, then remove.
		c.Conn.Wait()
		c.Server.Remove(c)
	}()

	go func() {
		for msg := range c.Msg {
			c.Write(msg)
		}
	}()

	for {
		line, err := c.term.ReadLine()
		if err != nil {
			break
		}

		parts := strings.SplitN(line, " ", 3)
		isCmd := strings.HasPrefix(parts[0], "/")

		if isCmd {
			// TODO: Factor this out.
			switch parts[0] {
			case "/test-colors": // Shh, this command is a secret!
				c.Write(ColorString("32", "Lorem ipsum dolor sit amet,"))
				c.Write("consectetur " + ColorString("31;1", "adipiscing") + " elit.")
			case "/exit":
				channel.Close()
			case "/help":
				c.WriteLines(strings.Split(HELP_TEXT, "\n"))
			case "/about":
				c.WriteLines(strings.Split(ABOUT_TEXT, "\n"))
			case "/me":
				me := strings.TrimLeft(line, "/me")
				if me == "" {
					me = " is at a loss for words."
				}
				msg := fmt.Sprintf("** %s%s", c.ColoredName(), me)
				if c.IsSilenced() || len(msg) > 1000 {
					c.Msg <- fmt.Sprintf("-> Message rejected.")
				} else {
					c.Server.Broadcast(msg, nil)
				}
			case "/nick":
				if len(parts) == 2 {
					c.Server.Rename(c, parts[1])
				} else {
					c.Msg <- fmt.Sprintf("-> Missing $NAME from: /nick $NAME")
				}
			case "/whois":
				if len(parts) == 2 {
					client := c.Server.Who(parts[1])
					if client != nil {
						version := RE_STRIP_TEXT.ReplaceAllString(string(client.Conn.ClientVersion()), "")
						if len(version) > 100 {
							version = "Evil Jerk with a superlong string"
						}
						c.Msg <- fmt.Sprintf("-> %s is %s via %s", client.ColoredName(), client.Fingerprint(), version)
					} else {
						c.Msg <- fmt.Sprintf("-> No such name: %s", parts[1])
					}
				} else {
					c.Msg <- fmt.Sprintf("-> Missing $NAME from: /whois $NAME")
				}
			case "/list":
				names := c.Server.List(nil)
				c.Msg <- fmt.Sprintf("-> %d connected: %s", len(names), strings.Join(names, ", "))
			case "/ban":
				if !c.Server.IsOp(c) {
					c.Msg <- fmt.Sprintf("-> You're not an admin.")
				} else if len(parts) != 2 {
					c.Msg <- fmt.Sprintf("-> Missing $NAME from: /ban $NAME")
				} else {
					client := c.Server.Who(parts[1])
					if client == nil {
						c.Msg <- fmt.Sprintf("-> No such name: %s", parts[1])
					} else {
						fingerprint := client.Fingerprint()
						client.Write(fmt.Sprintf("-> Banned by %s.", c.ColoredName()))
						c.Server.Ban(fingerprint, nil)
						client.Conn.Close()
						c.Server.Broadcast(fmt.Sprintf("* %s was banned by %s", parts[1], c.ColoredName()), nil)
					}
				}
			case "/op":
				if !c.Server.IsOp(c) {
					c.Msg <- fmt.Sprintf("-> You're not an admin.")
				} else if len(parts) != 2 {
					c.Msg <- fmt.Sprintf("-> Missing $NAME from: /op $NAME")
				} else {
					client := c.Server.Who(parts[1])
					if client == nil {
						c.Msg <- fmt.Sprintf("-> No such name: %s", parts[1])
					} else {
						fingerprint := client.Fingerprint()
						client.Write(fmt.Sprintf("-> Made op by %s.", c.ColoredName()))
						c.Server.Op(fingerprint)
					}
				}
			case "/silence":
				if !c.Server.IsOp(c) {
					c.Msg <- fmt.Sprintf("-> You're not an admin.")
				} else if len(parts) < 2 {
					c.Msg <- fmt.Sprintf("-> Missing $NAME from: /silence $NAME")
				} else {
					duration := time.Duration(5) * time.Minute
					if len(parts) >= 3 {
						parsedDuration, err := time.ParseDuration(parts[2])
						if err == nil {
							duration = parsedDuration
						}
					}
					client := c.Server.Who(parts[1])
					if client == nil {
						c.Msg <- fmt.Sprintf("-> No such name: %s", parts[1])
					} else {
						client.Silence(duration)
						client.Write(fmt.Sprintf("-> Silenced for %s by %s.", duration, c.ColoredName()))
					}
				}
			default:
				c.Msg <- fmt.Sprintf("-> Invalid command: %s", line)
			}
			continue
		}

		msg := fmt.Sprintf("%s: %s", c.ColoredName(), line)
		if c.IsSilenced() || len(msg) > 1000 || len(line) < 1 {
			c.Msg <- fmt.Sprintf("-> Message rejected.")
			continue
		}
		c.Server.Broadcast(msg, c)
	}

}

func (c *Client) handleChannels(channels <-chan ssh.NewChannel) {
	prompt := fmt.Sprintf("[%s] ", c.ColoredName())

	hasShell := false

	for ch := range channels {
		if t := ch.ChannelType(); t != "session" {
			ch.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
			continue
		}

		channel, requests, err := ch.Accept()
		if err != nil {
			logger.Errorf("Could not accept channel: %v", err)
			continue
		}
		defer channel.Close()

		c.term = terminal.NewTerminal(channel, prompt)
		for req := range requests {
			var width, height int
			var ok bool

			switch req.Type {
			case "shell":
				if c.term != nil && !hasShell {
					go c.handleShell(channel)
					ok = true
					hasShell = true
				}
			case "pty-req":
				width, height, ok = parsePtyRequest(req.Payload)
				if ok {
					err := c.Resize(width, height)
					ok = err == nil
				}
			case "window-change":
				width, height, ok = parseWinchRequest(req.Payload)
				if ok {
					err := c.Resize(width, height)
					ok = err == nil
				}
			}

			if req.WantReply {
				req.Reply(ok, nil)
			}
		}
	}
}
<commit_msg>Better documentation for /help<commit_after>package main

import (
	"fmt"
	"strings"
	"time"

	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/terminal"
)

const MSG_BUFFER int = 10

const HELP_TEXT string = `-> Available commands:
   /about           - About this chat
   /exit            - Exit the chat
   /help            - Show this help text
   /list            - List the users that are currently connected
   /me $ACTION      - Show yourself doing an action
   /nick $NAME      - Rename yourself to a new name
   /whois $NAME     - Display information about another connected user
`
const OP_HELP_TEXT string = `-> Available operator commands:
   /ban $NAME       - Banish a user from the chat
   /op $NAME        - Promote a user to server operator
   /silence $NAME   - Revoke a user's ability to speak
`

const ABOUT_TEXT string = `-> ssh-chat is made by @shazow.

   It is a custom ssh server built in Go to serve a chat experience
   instead of a shell.

   Source: https://github.com/shazow/ssh-chat

   For more, visit shazow.net or follow at twitter.com/shazow
`

type Client struct {
	Server        *Server
	Conn          *ssh.ServerConn
	Msg           chan string
	Name          string
	Color         string
	Op            bool
	ready         chan struct{}
	term          *terminal.Terminal
	termWidth     int
	termHeight    int
	silencedUntil time.Time
}

func NewClient(server *Server, conn *ssh.ServerConn) *Client {
	return &Client{
		Server: server,
		Conn:   conn,
		Name:   conn.User(),
		Color:  RandomColor(),
		Msg:    make(chan string, MSG_BUFFER),
		ready:  make(chan struct{}, 1),
	}
}

func (c *Client) ColoredName() string {
    return ColorString(c.Color, c.Name)
}

func (c *Client) Write(msg string) {
	c.term.Write([]byte(msg + "\r\n"))
}

func (c *Client) WriteLines(msg []string) {
	for _, line := range msg {
		c.Write(line)
	}
}

func (c *Client) IsSilenced() bool {
	return c.silencedUntil.After(time.Now())
}

func (c *Client) Silence(d time.Duration) {
	c.silencedUntil = time.Now().Add(d)
}

func (c *Client) Resize(width int, height int) error {
	err := c.term.SetSize(width, height)
	if err != nil {
		logger.Errorf("Resize failed: %dx%d", width, height)
		return err
	}
	c.termWidth, c.termHeight = width, height
	return nil
}

func (c *Client) Rename(name string) {
	c.Name = name
	c.term.SetPrompt(fmt.Sprintf("[%s] ", c.ColoredName()))
}

func (c *Client) Fingerprint() string {
	return c.Conn.Permissions.Extensions["fingerprint"]
}

func (c *Client) handleShell(channel ssh.Channel) {
	defer channel.Close()

	// FIXME: This shouldn't live here, need to restructure the call chaining.
	c.Server.Add(c)
	go func() {
		// Block until done, then remove.
		c.Conn.Wait()
		c.Server.Remove(c)
	}()

	go func() {
		for msg := range c.Msg {
			c.Write(msg)
		}
	}()

	for {
		line, err := c.term.ReadLine()
		if err != nil {
			break
		}

		parts := strings.SplitN(line, " ", 3)
		isCmd := strings.HasPrefix(parts[0], "/")

		if isCmd {
			// TODO: Factor this out.
			switch parts[0] {
			case "/test-colors": // Shh, this command is a secret!
				c.Write(ColorString("32", "Lorem ipsum dolor sit amet,"))
				c.Write("consectetur " + ColorString("31;1", "adipiscing") + " elit.")
			case "/exit":
				channel.Close()
			case "/help":
				c.WriteLines(strings.Split(HELP_TEXT, "\n"))
				if c.Server.IsOp(c) {
					c.WriteLines(strings.Split(OP_HELP_TEXT, "\n"))
				}
			case "/about":
				c.WriteLines(strings.Split(ABOUT_TEXT, "\n"))
			case "/me":
				me := strings.TrimLeft(line, "/me")
				if me == "" {
					me = " is at a loss for words."
				}
				msg := fmt.Sprintf("** %s%s", c.ColoredName(), me)
				if c.IsSilenced() || len(msg) > 1000 {
					c.Msg <- fmt.Sprintf("-> Message rejected.")
				} else {
					c.Server.Broadcast(msg, nil)
				}
			case "/nick":
				if len(parts) == 2 {
					c.Server.Rename(c, parts[1])
				} else {
					c.Msg <- fmt.Sprintf("-> Missing $NAME from: /nick $NAME")
				}
			case "/whois":
				if len(parts) == 2 {
					client := c.Server.Who(parts[1])
					if client != nil {
						version := RE_STRIP_TEXT.ReplaceAllString(string(client.Conn.ClientVersion()), "")
						if len(version) > 100 {
							version = "Evil Jerk with a superlong string"
						}
						c.Msg <- fmt.Sprintf("-> %s is %s via %s", client.ColoredName(), client.Fingerprint(), version)
					} else {
						c.Msg <- fmt.Sprintf("-> No such name: %s", parts[1])
					}
				} else {
					c.Msg <- fmt.Sprintf("-> Missing $NAME from: /whois $NAME")
				}
			case "/list":
				names := c.Server.List(nil)
				c.Msg <- fmt.Sprintf("-> %d connected: %s", len(names), strings.Join(names, ", "))
			case "/ban":
				if !c.Server.IsOp(c) {
					c.Msg <- fmt.Sprintf("-> You're not an admin.")
				} else if len(parts) != 2 {
					c.Msg <- fmt.Sprintf("-> Missing $NAME from: /ban $NAME")
				} else {
					client := c.Server.Who(parts[1])
					if client == nil {
						c.Msg <- fmt.Sprintf("-> No such name: %s", parts[1])
					} else {
						fingerprint := client.Fingerprint()
						client.Write(fmt.Sprintf("-> Banned by %s.", c.ColoredName()))
						c.Server.Ban(fingerprint, nil)
						client.Conn.Close()
						c.Server.Broadcast(fmt.Sprintf("* %s was banned by %s", parts[1], c.ColoredName()), nil)
					}
				}
			case "/op":
				if !c.Server.IsOp(c) {
					c.Msg <- fmt.Sprintf("-> You're not an admin.")
				} else if len(parts) != 2 {
					c.Msg <- fmt.Sprintf("-> Missing $NAME from: /op $NAME")
				} else {
					client := c.Server.Who(parts[1])
					if client == nil {
						c.Msg <- fmt.Sprintf("-> No such name: %s", parts[1])
					} else {
						fingerprint := client.Fingerprint()
						client.Write(fmt.Sprintf("-> Made op by %s.", c.ColoredName()))
						c.Server.Op(fingerprint)
					}
				}
			case "/silence":
				if !c.Server.IsOp(c) {
					c.Msg <- fmt.Sprintf("-> You're not an admin.")
				} else if len(parts) < 2 {
					c.Msg <- fmt.Sprintf("-> Missing $NAME from: /silence $NAME")
				} else {
					duration := time.Duration(5) * time.Minute
					if len(parts) >= 3 {
						parsedDuration, err := time.ParseDuration(parts[2])
						if err == nil {
							duration = parsedDuration
						}
					}
					client := c.Server.Who(parts[1])
					if client == nil {
						c.Msg <- fmt.Sprintf("-> No such name: %s", parts[1])
					} else {
						client.Silence(duration)
						client.Write(fmt.Sprintf("-> Silenced for %s by %s.", duration, c.ColoredName()))
					}
				}
			default:
				c.Msg <- fmt.Sprintf("-> Invalid command: %s", line)
			}
			continue
		}

		msg := fmt.Sprintf("%s: %s", c.ColoredName(), line)
		if c.IsSilenced() || len(msg) > 1000 || len(line) < 1 {
			c.Msg <- fmt.Sprintf("-> Message rejected.")
			continue
		}
		c.Server.Broadcast(msg, c)
	}

}

func (c *Client) handleChannels(channels <-chan ssh.NewChannel) {
	prompt := fmt.Sprintf("[%s] ", c.ColoredName())

	hasShell := false

	for ch := range channels {
		if t := ch.ChannelType(); t != "session" {
			ch.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
			continue
		}

		channel, requests, err := ch.Accept()
		if err != nil {
			logger.Errorf("Could not accept channel: %v", err)
			continue
		}
		defer channel.Close()

		c.term = terminal.NewTerminal(channel, prompt)
		for req := range requests {
			var width, height int
			var ok bool

			switch req.Type {
			case "shell":
				if c.term != nil && !hasShell {
					go c.handleShell(channel)
					ok = true
					hasShell = true
				}
			case "pty-req":
				width, height, ok = parsePtyRequest(req.Payload)
				if ok {
					err := c.Resize(width, height)
					ok = err == nil
				}
			case "window-change":
				width, height, ok = parseWinchRequest(req.Payload)
				if ok {
					err := c.Resize(width, height)
					ok = err == nil
				}
			}

			if req.WantReply {
				req.Reply(ok, nil)
			}
		}
	}
}
<|endoftext|>


<commit_before>package TeleGogo

import (
	"encoding/json"
	"io"
	"net/http"
	"os"

	"github.com/wallnutkraken/TeleGogo/Requests"
)

type client struct {
	token      string
	httpClient *http.Client
}

func (c *client) getToken() string {
	return c.token
}

// GetUpdates receives incoming updates using long polling.
func (c *client) GetUpdates(options GetUpdatesOptions) ([]Update, error) {
	response, err := c.sendJSON(options)
	if err != nil {
		return nil, err
	}

	if response.StatusCode != http.StatusOK {
		return nil, responseToError(response)
	}

	upd := updateResponse{}
	decoder := json.NewDecoder(response.Body)
	if err = decoder.Decode(&upd); err != nil {
		return nil, err
	}
	return upd.Result, nil
}

// SetWebhook NOT TESTED. Use this method to specify a url and receive incoming updates via an outgoing
// webhook. Whenever there is an update for the bot, we will send an HTTPS POST request to the specified url,
// containing a JSON-serialized Update. In case of an unsuccessful request, we will give up after a
// reasonable amount of attempts.
func (c *client) SetWebhook(args SetWebhookArgs) error {
	response, err := c.sendFile(args)
	if err != nil {
		return err
	}

	if response.StatusCode != http.StatusOK {
		return responseToError(response)
	}
	response.Body.Close()

	return nil
}

// DownloadFile downloads the specified file
func (c *client) DownloadFile(file File, path string) error {
	get, err := Requests.CreateFileGet(c.getToken(), file.FilePath)
	if err != nil {
		return err
	}
	resp, err := c.httpClient.Do(get)
	if err != nil {
		return err
	}
	physicalFile, err := os.Create(path)
	if err != nil {
		return err
	}
	defer physicalFile.Close()
	defer resp.Body.Close()

	_, err = io.Copy(physicalFile, resp.Body)
	return err
}

// WhoAmI A simple method for testing your bot's auth token. Requires no parameters.
// Returns basic information about the bot in form of a User object.
func (c *client) WhoAmI() (User, error) {
	request, err := Requests.CreateBotGet(c.token, "getMe")

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

	response, err := c.httpClient.Do(request)
	if err != nil {
		return User{}, err
	}

	if response.StatusCode != http.StatusOK {
		return User{}, responseToError(response)
	}
	tgResp := userResponse{}
	decoder := json.NewDecoder(response.Body)

	err = decoder.Decode(&tgResp)
	response.Body.Close()

	return tgResp.Result, err
}

// SendMessage sends a message with the specified arguments. On success returns the sent Message.
func (c *client) SendMessage(args SendMessageArgs) (Message, error) {
	return c.sendJSONMessage(args)
}

func (c *client) ForwardMessage(args ForwardMessageArgs) (Message, error) {
	response, err := c.sendJSON(args)
	if err != nil {
		return Message{}, err
	}
	defer response.Body.Close()
	if response.StatusCode != http.StatusOK {
		return Message{}, responseToError(response)
	}

	return responseToMessage(response)
}

// SendPhoto Use this method to send photos. On success, the sent Message is returned.
func (c *client) SendPhoto(args SendPhotoArgs) (Message, error) {
	/* Decide whether this is a newly uploaded file or an old one. */
	if args.FileID == "" {
		return c.sendFileMessage(args)
	}
	return c.sendJSONMessage(args)
}

func (c *client) SendAudio(args SendAudioArgs) (Message, error) {
	/* Decide if it's a new or existing file, based on user intent */
	if args.AudioPath != "" {
		return c.sendFileMessage(args)
	}
	return c.sendJSONMessage(args)
}

// SendDocument Use this method to send general files. On success, the sent Message is returned.
// Bots can currently send files of any type of up to 50 MB in size, this limit may be changed in the future.
func (c *client) SendDocument(args SendDocumentArgs) (Message, error) {
	/* Decide if the document is a resend or a new file, based on args */
	if args.DocumentPath != "" {
		return c.sendFileMessage(args)
	}
	return c.sendJSONMessage(args)
}

// SendSticker Use this method to send .webp stickers. On success, the sent Message is returned.
func (c *client) SendSticker(args SendStickerArgs) (Message, error) {
	/* Decide if the sticker is a existing or a new sticker, based on args */
	if args.StickerPath != "" {
		return c.sendFileMessage(args)
	}
	return c.sendJSONMessage(args)
}

// SendVideo Use this method to send video files,
// Telegram clients support mp4 videos (other formats may be sent as Document).
// On success, the sent Message is returned.
// Bots can currently send video files of up to 50 MB in size, this limit may be changed in the future.
func (c *client) SendVideo(args SendVideoArgs) (Message, error) {
	/* Decide if the video is an existing or a new video, based on args */
	if args.VideoPath != "" {
		return c.sendFileMessage(args)
	}
	return c.sendJSONMessage(args)
}

func responseToMessage(response *http.Response) (Message, error) {
	msg := messageReply{}
	decoder := json.NewDecoder(response.Body)
	err := decoder.Decode(&msg)
	defer response.Body.Close()
	return msg.Result, err
}

// NewClient Creates a new Client
func NewClient(token string) (Client, error) {
	c := new(client)
	c.token = token
	c.httpClient = &http.Client{}
	return c, nil
}

// Client represents a bot in Telegram.
type Client interface {
	getToken() string
	DownloadFile(File, string) error
	WhoAmI() (User, error)
	GetUpdates(GetUpdatesOptions) ([]Update, error)
	SendMessage(SendMessageArgs) (Message, error)
	ForwardMessage(ForwardMessageArgs) (Message, error)
	SetWebhook(SetWebhookArgs) error
	SendPhoto(SendPhotoArgs) (Message, error)
	SendAudio(SendAudioArgs) (Message, error)
	SendDocument(SendDocumentArgs) (Message, error)
	SendSticker(SendStickerArgs) (Message, error)
	SendVideo(SendVideoArgs) (Message, error)
}
<commit_msg>removed getToken()<commit_after>package TeleGogo

import (
	"encoding/json"
	"io"
	"net/http"
	"os"

	"github.com/wallnutkraken/TeleGogo/Requests"
)

type client struct {
	token      string
	httpClient *http.Client
}

// GetUpdates receives incoming updates using long polling.
func (c *client) GetUpdates(options GetUpdatesOptions) ([]Update, error) {
	response, err := c.sendJSON(options)
	if err != nil {
		return nil, err
	}

	if response.StatusCode != http.StatusOK {
		return nil, responseToError(response)
	}

	upd := updateResponse{}
	decoder := json.NewDecoder(response.Body)
	if err = decoder.Decode(&upd); err != nil {
		return nil, err
	}
	return upd.Result, nil
}

// SetWebhook NOT TESTED. Use this method to specify a url and receive incoming updates via an outgoing
// webhook. Whenever there is an update for the bot, we will send an HTTPS POST request to the specified url,
// containing a JSON-serialized Update. In case of an unsuccessful request, we will give up after a
// reasonable amount of attempts.
func (c *client) SetWebhook(args SetWebhookArgs) error {
	response, err := c.sendFile(args)
	if err != nil {
		return err
	}

	if response.StatusCode != http.StatusOK {
		return responseToError(response)
	}
	response.Body.Close()

	return nil
}

// DownloadFile downloads the specified file
func (c *client) DownloadFile(file File, path string) error {
	get, err := Requests.CreateFileGet(c.token, file.FilePath)
	if err != nil {
		return err
	}
	resp, err := c.httpClient.Do(get)
	if err != nil {
		return err
	}
	physicalFile, err := os.Create(path)
	if err != nil {
		return err
	}
	defer physicalFile.Close()
	defer resp.Body.Close()

	_, err = io.Copy(physicalFile, resp.Body)
	return err
}

// WhoAmI A simple method for testing your bot's auth token. Requires no parameters.
// Returns basic information about the bot in form of a User object.
func (c *client) WhoAmI() (User, error) {
	request, err := Requests.CreateBotGet(c.token, "getMe")

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

	response, err := c.httpClient.Do(request)
	if err != nil {
		return User{}, err
	}

	if response.StatusCode != http.StatusOK {
		return User{}, responseToError(response)
	}
	tgResp := userResponse{}
	decoder := json.NewDecoder(response.Body)

	err = decoder.Decode(&tgResp)
	response.Body.Close()

	return tgResp.Result, err
}

// SendMessage sends a message with the specified arguments. On success returns the sent Message.
func (c *client) SendMessage(args SendMessageArgs) (Message, error) {
	return c.sendJSONMessage(args)
}

func (c *client) ForwardMessage(args ForwardMessageArgs) (Message, error) {
	response, err := c.sendJSON(args)
	if err != nil {
		return Message{}, err
	}
	defer response.Body.Close()
	if response.StatusCode != http.StatusOK {
		return Message{}, responseToError(response)
	}

	return responseToMessage(response)
}

// SendPhoto Use this method to send photos. On success, the sent Message is returned.
func (c *client) SendPhoto(args SendPhotoArgs) (Message, error) {
	/* Decide whether this is a newly uploaded file or an old one. */
	if args.FileID == "" {
		return c.sendFileMessage(args)
	}
	return c.sendJSONMessage(args)
}

func (c *client) SendAudio(args SendAudioArgs) (Message, error) {
	/* Decide if it's a new or existing file, based on user intent */
	if args.AudioPath != "" {
		return c.sendFileMessage(args)
	}
	return c.sendJSONMessage(args)
}

// SendDocument Use this method to send general files. On success, the sent Message is returned.
// Bots can currently send files of any type of up to 50 MB in size, this limit may be changed in the future.
func (c *client) SendDocument(args SendDocumentArgs) (Message, error) {
	/* Decide if the document is a resend or a new file, based on args */
	if args.DocumentPath != "" {
		return c.sendFileMessage(args)
	}
	return c.sendJSONMessage(args)
}

// SendSticker Use this method to send .webp stickers. On success, the sent Message is returned.
func (c *client) SendSticker(args SendStickerArgs) (Message, error) {
	/* Decide if the sticker is a existing or a new sticker, based on args */
	if args.StickerPath != "" {
		return c.sendFileMessage(args)
	}
	return c.sendJSONMessage(args)
}

// SendVideo Use this method to send video files,
// Telegram clients support mp4 videos (other formats may be sent as Document).
// On success, the sent Message is returned.
// Bots can currently send video files of up to 50 MB in size, this limit may be changed in the future.
func (c *client) SendVideo(args SendVideoArgs) (Message, error) {
	/* Decide if the video is an existing or a new video, based on args */
	if args.VideoPath != "" {
		return c.sendFileMessage(args)
	}
	return c.sendJSONMessage(args)
}

func responseToMessage(response *http.Response) (Message, error) {
	msg := messageReply{}
	decoder := json.NewDecoder(response.Body)
	err := decoder.Decode(&msg)
	defer response.Body.Close()
	return msg.Result, err
}

// NewClient Creates a new Client
func NewClient(token string) (Client, error) {
	c := new(client)
	c.token = token
	c.httpClient = &http.Client{}
	return c, nil
}

// Client represents a bot in Telegram.
type Client interface {
	DownloadFile(File, string) error
	WhoAmI() (User, error)
	GetUpdates(GetUpdatesOptions) ([]Update, error)
	SendMessage(SendMessageArgs) (Message, error)
	ForwardMessage(ForwardMessageArgs) (Message, error)
	SetWebhook(SetWebhookArgs) error
	SendPhoto(SendPhotoArgs) (Message, error)
	SendAudio(SendAudioArgs) (Message, error)
	SendDocument(SendDocumentArgs) (Message, error)
	SendSticker(SendStickerArgs) (Message, error)
	SendVideo(SendVideoArgs) (Message, error)
}
<|endoftext|>


<commit_before>package signalr

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"sync"

	"github.com/gorilla/websocket"
)

type negotiationResponse struct {
	Url                     string
	ConnectionToken         string
	ConnectionId            string
	KeepAliveTimeout        float32
	DisconnectTimeout       float32
	ConnectionTimeout       float32
	TryWebSockets           bool
	ProtocolVersion         string
	TransportConnectTimeout float32
	LogPollDelay            float32
}

type Client struct {
	OnMessageError func(err error)
	OnClientMethod func(hub, method string, arguments []json.RawMessage)
	params         negotiationResponse
	socket         *websocket.Conn
	nextId         int

	// Futures for server call responses and a guarding mutex.
	responseFutures map[string]chan *serverMessage
	mutex           sync.Mutex
	dispatchRunning bool
}

type serverMessage struct {
	Cursor     string            `json:"C"`
	Data       []json.RawMessage `json:"M"`
	Result     json.RawMessage   `json:"R"`
	Identifier string            `json:"I"`
	Error      string            `json:"E"`
}

func negotiate(scheme, address string) (negotiationResponse, error) {
	var response negotiationResponse

	var negotiationUrl = url.URL{Scheme: scheme, Host: address, Path: "/signalr/negotiate"}

	client := &http.Client{}

	reply, err := client.Get(negotiationUrl.String())
	if err != nil {
		return response, err
	}

	defer reply.Body.Close()

	if body, err := ioutil.ReadAll(reply.Body); err != nil {
		return response, err
	} else if err := json.Unmarshal(body, &response); err != nil {
		return response, err
	} else {
		return response, nil
	}
}

func connectWebsocket(address string, params negotiationResponse, hubs []string) (*websocket.Conn, error) {
	var connectionData = make([]struct {
		Name string `json:"Name"`
	}, len(hubs))
	for i, h := range hubs {
		connectionData[i].Name = h
	}
	connectionDataBytes, err := json.Marshal(connectionData)
	if err != nil {
		return nil, err
	}

	var connectionParameters = url.Values{}
	connectionParameters.Set("transport", "webSockets")
	connectionParameters.Set("clientProtocol", "1.5")
	connectionParameters.Set("connectionToken", params.ConnectionToken)
	connectionParameters.Set("connectionData", string(connectionDataBytes))

	var connectionUrl = url.URL{Scheme: "wss", Host: address, Path: "signalr/connect"}
	connectionUrl.RawQuery = connectionParameters.Encode()

	if conn, _, err := websocket.DefaultDialer.Dial(connectionUrl.String(), nil); err != nil {
		return nil, err
	} else {
		return conn, nil
	}
}

func (self *Client) routeResponse(response *serverMessage) {
	self.mutex.Lock()
	defer self.mutex.Unlock()

	if c, ok := self.responseFutures[response.Identifier]; ok {
		c <- response
		close(c)
		delete(self.responseFutures, response.Identifier)
	}
}

func (self *Client) createResponseFuture(identifier string) (chan *serverMessage, error) {
	self.mutex.Lock()
	defer self.mutex.Unlock()

	if !self.dispatchRunning {
		return nil, fmt.Errorf("Dispatch is not running")
	}

	var c = make(chan *serverMessage)
	self.responseFutures[identifier] = c

	return c, nil
}

func (self *Client) tryStartDispatch() error {
	self.mutex.Lock()
	defer self.mutex.Unlock()

	if self.dispatchRunning {
		return fmt.Errorf("Another Dispatch() is running")
	}
	self.dispatchRunning = true

	return nil
}

func (self *Client) endDispatch() {
	// Close all the waiting response futures.
	self.mutex.Lock()
	defer self.mutex.Unlock()
	self.dispatchRunning = false
	for _, c := range self.responseFutures {
		close(c)
	}
}

// Start dispatch loop. This function will return when error occurs. When this
// happens, all the connections are closed and user can run Connect()
// and Dispatch() again on the same client.
func (self *Client) Dispatch() error {
	if err := self.tryStartDispatch(); err != nil {
		return err
	}

	defer self.endDispatch()

	for {
		var message serverMessage

		var hubCall struct {
			HubName   string            `json:"H"`
			Method    string            `json:"M"`
			Arguments []json.RawMessage `json:"A"`
		}

		_, data, err := self.socket.ReadMessage()
		if err != nil {
			self.socket.Close()
			return err
		} else if err := json.Unmarshal(data, &message); err != nil {
			if self.OnMessageError != nil {
				self.OnMessageError(err)
			}
		} else {
			if len(message.Identifier) > 0 {
				// This is a response to a hub call.
				self.routeResponse(&message)
			} else if len(message.Data) == 1 {
				if err := json.Unmarshal(message.Data[0], &hubCall); err == nil && len(hubCall.HubName) > 0 && len(hubCall.Method) > 0 {
					// This is a client Hub method call from server.
					if self.OnClientMethod != nil {
						self.OnClientMethod(hubCall.HubName, hubCall.Method, hubCall.Arguments)
					}
				}
			}
		}
	}
}

// Call server hub method. Dispatch() function must be running, otherwise this method will never return.
func (self *Client) CallHub(hub, method string, params ...interface{}) (json.RawMessage, error) {
	var request = struct {
		Hub        string        `json:"H"`
		Method     string        `json:"M"`
		Arguments  []interface{} `json:"A"`
		Identifier int           `json:"I"`
	}{
		Hub:        hub,
		Method:     method,
		Arguments:  params,
		Identifier: self.nextId,
	}

	self.nextId++

	data, err := json.Marshal(request)
	if err != nil {
		return nil, err
	}

	responseChannel, err := self.createResponseFuture(fmt.Sprintf("%d", request.Identifier))
	if err != nil {
		return nil, err
	}

	if err := self.socket.WriteMessage(websocket.TextMessage, data); err != nil {
		return nil, err
	}

	if response, ok := <-responseChannel; !ok {
		return nil, fmt.Errorf("Call to server returned no result")
	} else if len(response.Error) > 0 {
		return nil, fmt.Errorf("%s", response.Error)
	} else {
		return response.Result, nil
	}
}

func (self *Client) Connect(scheme, host string, hubs []string) error {
	// Negotiate parameters.
	if params, err := negotiate(scheme, host); err != nil {
		return err
	} else {
		self.params = params
	}

	// Connect Websocket.
	if ws, err := connectWebsocket(host, self.params, hubs); err != nil {
		return err
	} else {
		self.socket = ws
	}

	return nil
}

func (self *Client) Close() {
	self.socket.Close()
}

func NewWebsocketClient() *Client {
	return &Client{
		nextId:          1,
		responseFutures: make(map[string]chan *serverMessage),
	}
}
<commit_msg>Fix channel deletion<commit_after>package signalr

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"sync"

	"github.com/gorilla/websocket"
)

type negotiationResponse struct {
	Url                     string
	ConnectionToken         string
	ConnectionId            string
	KeepAliveTimeout        float32
	DisconnectTimeout       float32
	ConnectionTimeout       float32
	TryWebSockets           bool
	ProtocolVersion         string
	TransportConnectTimeout float32
	LogPollDelay            float32
}

type Client struct {
	OnMessageError func(err error)
	OnClientMethod func(hub, method string, arguments []json.RawMessage)
	params         negotiationResponse
	socket         *websocket.Conn
	nextId         int

	// Futures for server call responses and a guarding mutex.
	responseFutures map[string]chan *serverMessage
	mutex           sync.Mutex
	dispatchRunning bool
}

type serverMessage struct {
	Cursor     string            `json:"C"`
	Data       []json.RawMessage `json:"M"`
	Result     json.RawMessage   `json:"R"`
	Identifier string            `json:"I"`
	Error      string            `json:"E"`
}

func negotiate(scheme, address string) (negotiationResponse, error) {
	var response negotiationResponse

	var negotiationUrl = url.URL{Scheme: scheme, Host: address, Path: "/signalr/negotiate"}

	client := &http.Client{}

	reply, err := client.Get(negotiationUrl.String())
	if err != nil {
		return response, err
	}

	defer reply.Body.Close()

	if body, err := ioutil.ReadAll(reply.Body); err != nil {
		return response, err
	} else if err := json.Unmarshal(body, &response); err != nil {
		return response, err
	} else {
		return response, nil
	}
}

func connectWebsocket(address string, params negotiationResponse, hubs []string) (*websocket.Conn, error) {
	var connectionData = make([]struct {
		Name string `json:"Name"`
	}, len(hubs))
	for i, h := range hubs {
		connectionData[i].Name = h
	}
	connectionDataBytes, err := json.Marshal(connectionData)
	if err != nil {
		return nil, err
	}

	var connectionParameters = url.Values{}
	connectionParameters.Set("transport", "webSockets")
	connectionParameters.Set("clientProtocol", "1.5")
	connectionParameters.Set("connectionToken", params.ConnectionToken)
	connectionParameters.Set("connectionData", string(connectionDataBytes))

	var connectionUrl = url.URL{Scheme: "wss", Host: address, Path: "signalr/connect"}
	connectionUrl.RawQuery = connectionParameters.Encode()

	if conn, _, err := websocket.DefaultDialer.Dial(connectionUrl.String(), nil); err != nil {
		return nil, err
	} else {
		return conn, nil
	}
}

func (self *Client) routeResponse(response *serverMessage) {
	self.mutex.Lock()
	defer self.mutex.Unlock()

	if c, ok := self.responseFutures[response.Identifier]; ok {
		c <- response
		close(c)
		delete(self.responseFutures, response.Identifier)
	}
}

func (self *Client) createResponseFuture(identifier string) (chan *serverMessage, error) {
	self.mutex.Lock()
	defer self.mutex.Unlock()

	if !self.dispatchRunning {
		return nil, fmt.Errorf("Dispatch is not running")
	}

	var c = make(chan *serverMessage)
	self.responseFutures[identifier] = c

	return c, nil
}

func (self *Client) deleteResponseFuture(identifier string) {
	self.mutex.Lock()
	defer self.mutex.Unlock()

	delete(self.responseFutures, identifier)
}

func (self *Client) tryStartDispatch() error {
	self.mutex.Lock()
	defer self.mutex.Unlock()

	if self.dispatchRunning {
		return fmt.Errorf("Another Dispatch() is running")
	}
	self.dispatchRunning = true

	return nil
}

func (self *Client) endDispatch() {
	// Close all the waiting response futures.
	self.mutex.Lock()
	defer self.mutex.Unlock()
	self.dispatchRunning = false
	for _, c := range self.responseFutures {
		close(c)
	}
	self.responseFutures = make(map[string]chan *serverMessage)
}

// Start dispatch loop. This function will return when error occurs. When this
// happens, all the connections are closed and user can run Connect()
// and Dispatch() again on the same client.
func (self *Client) Dispatch() error {
	if err := self.tryStartDispatch(); err != nil {
		return err
	}

	defer self.endDispatch()

	for {
		var message serverMessage

		var hubCall struct {
			HubName   string            `json:"H"`
			Method    string            `json:"M"`
			Arguments []json.RawMessage `json:"A"`
		}

		_, data, err := self.socket.ReadMessage()
		if err != nil {
			self.socket.Close()
			return err
		} else if err := json.Unmarshal(data, &message); err != nil {
			if self.OnMessageError != nil {
				self.OnMessageError(err)
			}
		} else {
			if len(message.Identifier) > 0 {
				// This is a response to a hub call.
				self.routeResponse(&message)
			} else if len(message.Data) == 1 {
				if err := json.Unmarshal(message.Data[0], &hubCall); err == nil && len(hubCall.HubName) > 0 && len(hubCall.Method) > 0 {
					// This is a client Hub method call from server.
					if self.OnClientMethod != nil {
						self.OnClientMethod(hubCall.HubName, hubCall.Method, hubCall.Arguments)
					}
				}
			}
		}
	}
}

// Call server hub method. Dispatch() function must be running, otherwise this method will never return.
func (self *Client) CallHub(hub, method string, params ...interface{}) (json.RawMessage, error) {
	var request = struct {
		Hub        string        `json:"H"`
		Method     string        `json:"M"`
		Arguments  []interface{} `json:"A"`
		Identifier int           `json:"I"`
	}{
		Hub:        hub,
		Method:     method,
		Arguments:  params,
		Identifier: self.nextId,
	}

	self.nextId++

	data, err := json.Marshal(request)
	if err != nil {
		return nil, err
	}

	var responseKey = fmt.Sprintf("%d", request.Identifier)
	responseChannel, err := self.createResponseFuture(responseKey)
	if err != nil {
		return nil, err
	}

	if err := self.socket.WriteMessage(websocket.TextMessage, data); err != nil {
		return nil, err
	}

	defer self.deleteResponseFuture(responseKey)

	if response, ok := <-responseChannel; !ok {
		return nil, fmt.Errorf("Call to server returned no result")
	} else if len(response.Error) > 0 {
		return nil, fmt.Errorf("%s", response.Error)
	} else {
		return response.Result, nil
	}
}

func (self *Client) Connect(scheme, host string, hubs []string) error {
	// Negotiate parameters.
	if params, err := negotiate(scheme, host); err != nil {
		return err
	} else {
		self.params = params
	}

	// Connect Websocket.
	if ws, err := connectWebsocket(host, self.params, hubs); err != nil {
		return err
	} else {
		self.socket = ws
	}

	return nil
}

func (self *Client) Close() {
	self.socket.Close()
}

func NewWebsocketClient() *Client {
	return &Client{
		nextId:          1,
		responseFutures: make(map[string]chan *serverMessage),
	}
}
<|endoftext|>


<commit_before>package bitbucket

import (
	"encoding/json"
	"fmt"
	"log"

	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"golang.org/x/net/context"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/bitbucket"
	"golang.org/x/oauth2/clientcredentials"
	"bytes"
	"mime/multipart"
	"io"
	"os"
)

const DEFAULT_PAGE_LENGTH = 10

type Client struct {
	Auth         *auth
	Users        users
	User         user
	Teams        teams
	Repositories *Repositories
	Pagelen      uint64

	HttpClient 	 *http.Client
}

type auth struct {
	appID, secret  string
	user, password string
	token          oauth2.Token
	bearerToken    string
}

// Uses the Client Credentials Grant oauth2 flow to authenticate to Bitbucket
func NewOAuthClientCredentials(i, s string) *Client {
	a := &auth{appID: i, secret: s}
	ctx := context.Background()
	conf := &clientcredentials.Config{
		ClientID:     i,
		ClientSecret: s,
		TokenURL:     bitbucket.Endpoint.TokenURL,
	}

	tok, err := conf.Token(ctx)
	if err != nil {
		log.Fatal(err)
	}
	a.token = *tok
	return injectClient(a)

}

func NewOAuth(i, s string) *Client {
	a := &auth{appID: i, secret: s}
	ctx := context.Background()
	conf := &oauth2.Config{
		ClientID:     i,
		ClientSecret: s,
		Endpoint:     bitbucket.Endpoint,
	}

	// Redirect user to consent page to ask for permission
	// for the scopes specified above.
	url := conf.AuthCodeURL("state", oauth2.AccessTypeOffline)
	fmt.Printf("Visit the URL for the auth dialog:\n%v", url)

	// Use the authorization code that is pushed to the redirect
	// URL. Exchange will do the handshake to retrieve the
	// initial access token. The HTTP Client returned by
	// conf.Client will refresh the token as necessary.
	var code string
	fmt.Printf("Enter the code in the return URL: ")
	if _, err := fmt.Scan(&code); err != nil {
		log.Fatal(err)
	}
	tok, err := conf.Exchange(ctx, code)
	if err != nil {
		log.Fatal(err)
	}
	a.token = *tok
	return injectClient(a)
}

// NewOAuthWithCode finishes the OAuth handshake with a given code
// and returns a *Client
func NewOAuthWithCode(i, s, c string) (*Client, string) {
	a := &auth{appID: i, secret: s}
	ctx := context.Background()
	conf := &oauth2.Config{
		ClientID:     i,
		ClientSecret: s,
		Endpoint:     bitbucket.Endpoint,
	}

	tok, err := conf.Exchange(ctx, c)
	if err != nil {
		log.Fatal(err)
	}
	a.token = *tok
	return injectClient(a), tok.AccessToken
}

func NewOAuthbearerToken(t string) *Client {
	a := &auth{bearerToken: t}
	return injectClient(a)
}

func NewBasicAuth(u, p string) *Client {
	a := &auth{user: u, password: p}
	return injectClient(a)
}

func injectClient(a *auth) *Client {
	c := &Client{Auth: a, Pagelen: DEFAULT_PAGE_LENGTH}
	c.Repositories = &Repositories{
		c:                  c,
		PullRequests:       &PullRequests{c: c},
		Repository:         &Repository{c: c},
		Commits:            &Commits{c: c},
		Diff:               &Diff{c: c},
		BranchRestrictions: &BranchRestrictions{c: c},
		Webhooks:           &Webhooks{c: c},
		Downloads:			&Downloads{c: c},
	}
	c.Users = &Users{c: c}
	c.User = &User{c: c}
	c.Teams = &Teams{c: c}
	c.HttpClient = new(http.Client)
	return c
}

func (c *Client) execute(method string, urlStr string, text string) (interface{}, error) {
	// Use pagination if changed from default value
	const DEC_RADIX = 10
	if strings.Contains(urlStr, "/repositories/") {
		if c.Pagelen != DEFAULT_PAGE_LENGTH {
			urlObj, err := url.Parse(urlStr)
			if err != nil {
				return nil, err
			}
			q := urlObj.Query()
			q.Set("pagelen", strconv.FormatUint(c.Pagelen, DEC_RADIX))
			urlObj.RawQuery = q.Encode()
			urlStr = urlObj.String()
		}
	}

	body := strings.NewReader(text)

	req, err := http.NewRequest(method, urlStr, body)
	if err != nil {
		return nil, err
	}
	if text != "" {
		req.Header.Set("Content-Type", "application/json")
	}

	c.authenticateRequest(req)
	result, err := c.doRequest(req, false)

	//autopaginate.
	resultMap, isMap := result.(map[string]interface{})
	if isMap {
		nextIn := resultMap["next"]
		valuesIn := resultMap["values"]
		if nextIn != nil && valuesIn != nil {
			nextUrl := nextIn.(string)
			if nextUrl != "" {
				valuesSlice := valuesIn.([]interface{})
				if valuesSlice != nil {
					nextResult, err := c.execute(method, nextUrl, text)
					if err != nil {
						return nil, err
					}
					nextResultMap, isNextMap := nextResult.(map[string]interface{})
					if !isNextMap {
						return nil, fmt.Errorf("next page result is not map, it's %T", nextResult)
					}
					nextValuesIn := nextResultMap["values"]
					if nextValuesIn == nil {
						return nil, fmt.Errorf("next page result has no values")
					}
					nextValuesSlice, isSlice := nextValuesIn.([]interface{})
					if !isSlice {
						return nil, fmt.Errorf("next page result 'values' is not slice")
					}
					valuesSlice = append(valuesSlice, nextValuesSlice...)
					resultMap["values"] = valuesSlice
					delete(resultMap, "page")
					delete(resultMap, "pagelen")
					delete(resultMap, "size")
					result = resultMap
				}
			}
		}
	}

	return result, nil
}

func (c *Client) executeFileUpload(method string, urlStr string, filePath string, fileName string) (interface{}, error) {
	fileReader, err := os.Open(filePath)
	if err != nil {
		return nil, err
	}
	defer fileReader.Close()

	// Prepare a form that you will submit to that URL.
	var b bytes.Buffer
	w := multipart.NewWriter(&b)

	var fw io.Writer
	if fw, err = w.CreateFormFile("files", fileName); err != nil {
		return nil , err
	}

	if _, err = io.Copy(fw, fileReader); err != nil {
		return nil, err
	}

	// Don't forget to close the multipart writer.
	// If you don't close it, your request will be missing the terminating boundary.
	w.Close()

	// Now that you have a form, you can submit it to your handler.
	req, err := http.NewRequest(method, urlStr, &b)
	if err != nil {
		return nil, err
	}
	// Don't forget to set the content type, this will contain the boundary.
	req.Header.Set("Content-Type", w.FormDataContentType())

	c.authenticateRequest(req)
	return c.doRequest(req, true)

}

func (c *Client) authenticateRequest(req *http.Request){
	if c.Auth.bearerToken != "" {
		req.Header.Set("Authorization", "Bearer "+c.Auth.bearerToken)
	}

	if c.Auth.user != "" && c.Auth.password != "" {
		req.SetBasicAuth(c.Auth.user, c.Auth.password)
	} else if c.Auth.token.Valid() {
		c.Auth.token.SetAuthHeader(req)
	}
	return
}

func (c *Client) doRequest(req *http.Request, emptyResponse bool) (interface{}, error){

	resp, err := c.HttpClient.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.Body != nil {
		defer resp.Body.Close()
	}

	if (resp.StatusCode != http.StatusOK) && (resp.StatusCode != http.StatusCreated) {
		return nil, fmt.Errorf(resp.Status)
	}

	if emptyResponse {
		return nil, nil
	}

	if resp.Body == nil {
		return nil, fmt.Errorf("response body is nil")
	}

	resBodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var result interface{}
	err = json.Unmarshal(resBodyBytes, &result)
	if err != nil {
		return nil, err
	}

	return result, nil
}

func (c *Client) requestUrl(template string, args ...interface{}) string {

	if len(args) == 1 && args[0] == "" {
		return GetApiBaseURL() + template
	}
	return GetApiBaseURL() + fmt.Sprintf(template, args...)
}
<commit_msg>fix #56, about missing error check.<commit_after>package bitbucket

import (
	"encoding/json"
	"fmt"
	"log"

	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"bytes"
	"io"
	"mime/multipart"
	"os"

	"golang.org/x/net/context"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/bitbucket"
	"golang.org/x/oauth2/clientcredentials"
)

const DEFAULT_PAGE_LENGTH = 10

type Client struct {
	Auth         *auth
	Users        users
	User         user
	Teams        teams
	Repositories *Repositories
	Pagelen      uint64

	HttpClient *http.Client
}

type auth struct {
	appID, secret  string
	user, password string
	token          oauth2.Token
	bearerToken    string
}

// Uses the Client Credentials Grant oauth2 flow to authenticate to Bitbucket
func NewOAuthClientCredentials(i, s string) *Client {
	a := &auth{appID: i, secret: s}
	ctx := context.Background()
	conf := &clientcredentials.Config{
		ClientID:     i,
		ClientSecret: s,
		TokenURL:     bitbucket.Endpoint.TokenURL,
	}

	tok, err := conf.Token(ctx)
	if err != nil {
		log.Fatal(err)
	}
	a.token = *tok
	return injectClient(a)

}

func NewOAuth(i, s string) *Client {
	a := &auth{appID: i, secret: s}
	ctx := context.Background()
	conf := &oauth2.Config{
		ClientID:     i,
		ClientSecret: s,
		Endpoint:     bitbucket.Endpoint,
	}

	// Redirect user to consent page to ask for permission
	// for the scopes specified above.
	url := conf.AuthCodeURL("state", oauth2.AccessTypeOffline)
	fmt.Printf("Visit the URL for the auth dialog:\n%v", url)

	// Use the authorization code that is pushed to the redirect
	// URL. Exchange will do the handshake to retrieve the
	// initial access token. The HTTP Client returned by
	// conf.Client will refresh the token as necessary.
	var code string
	fmt.Printf("Enter the code in the return URL: ")
	if _, err := fmt.Scan(&code); err != nil {
		log.Fatal(err)
	}
	tok, err := conf.Exchange(ctx, code)
	if err != nil {
		log.Fatal(err)
	}
	a.token = *tok
	return injectClient(a)
}

// NewOAuthWithCode finishes the OAuth handshake with a given code
// and returns a *Client
func NewOAuthWithCode(i, s, c string) (*Client, string) {
	a := &auth{appID: i, secret: s}
	ctx := context.Background()
	conf := &oauth2.Config{
		ClientID:     i,
		ClientSecret: s,
		Endpoint:     bitbucket.Endpoint,
	}

	tok, err := conf.Exchange(ctx, c)
	if err != nil {
		log.Fatal(err)
	}
	a.token = *tok
	return injectClient(a), tok.AccessToken
}

func NewOAuthbearerToken(t string) *Client {
	a := &auth{bearerToken: t}
	return injectClient(a)
}

func NewBasicAuth(u, p string) *Client {
	a := &auth{user: u, password: p}
	return injectClient(a)
}

func injectClient(a *auth) *Client {
	c := &Client{Auth: a, Pagelen: DEFAULT_PAGE_LENGTH}
	c.Repositories = &Repositories{
		c:                  c,
		PullRequests:       &PullRequests{c: c},
		Repository:         &Repository{c: c},
		Commits:            &Commits{c: c},
		Diff:               &Diff{c: c},
		BranchRestrictions: &BranchRestrictions{c: c},
		Webhooks:           &Webhooks{c: c},
		Downloads:          &Downloads{c: c},
	}
	c.Users = &Users{c: c}
	c.User = &User{c: c}
	c.Teams = &Teams{c: c}
	c.HttpClient = new(http.Client)
	return c
}

func (c *Client) execute(method string, urlStr string, text string) (interface{}, error) {
	// Use pagination if changed from default value
	const DEC_RADIX = 10
	if strings.Contains(urlStr, "/repositories/") {
		if c.Pagelen != DEFAULT_PAGE_LENGTH {
			urlObj, err := url.Parse(urlStr)
			if err != nil {
				return nil, err
			}
			q := urlObj.Query()
			q.Set("pagelen", strconv.FormatUint(c.Pagelen, DEC_RADIX))
			urlObj.RawQuery = q.Encode()
			urlStr = urlObj.String()
		}
	}

	body := strings.NewReader(text)

	req, err := http.NewRequest(method, urlStr, body)
	if err != nil {
		return nil, err
	}
	if text != "" {
		req.Header.Set("Content-Type", "application/json")
	}

	c.authenticateRequest(req)
	result, err := c.doRequest(req, false)
	if err != nil {
		return nil, err
	}

	//autopaginate.
	resultMap, isMap := result.(map[string]interface{})
	if isMap {
		nextIn := resultMap["next"]
		valuesIn := resultMap["values"]
		if nextIn != nil && valuesIn != nil {
			nextUrl := nextIn.(string)
			if nextUrl != "" {
				valuesSlice := valuesIn.([]interface{})
				if valuesSlice != nil {
					nextResult, err := c.execute(method, nextUrl, text)
					if err != nil {
						return nil, err
					}
					nextResultMap, isNextMap := nextResult.(map[string]interface{})
					if !isNextMap {
						return nil, fmt.Errorf("next page result is not map, it's %T", nextResult)
					}
					nextValuesIn := nextResultMap["values"]
					if nextValuesIn == nil {
						return nil, fmt.Errorf("next page result has no values")
					}
					nextValuesSlice, isSlice := nextValuesIn.([]interface{})
					if !isSlice {
						return nil, fmt.Errorf("next page result 'values' is not slice")
					}
					valuesSlice = append(valuesSlice, nextValuesSlice...)
					resultMap["values"] = valuesSlice
					delete(resultMap, "page")
					delete(resultMap, "pagelen")
					delete(resultMap, "size")
					result = resultMap
				}
			}
		}
	}

	return result, nil
}

func (c *Client) executeFileUpload(method string, urlStr string, filePath string, fileName string) (interface{}, error) {
	fileReader, err := os.Open(filePath)
	if err != nil {
		return nil, err
	}
	defer fileReader.Close()

	// Prepare a form that you will submit to that URL.
	var b bytes.Buffer
	w := multipart.NewWriter(&b)

	var fw io.Writer
	if fw, err = w.CreateFormFile("files", fileName); err != nil {
		return nil, err
	}

	if _, err = io.Copy(fw, fileReader); err != nil {
		return nil, err
	}

	// Don't forget to close the multipart writer.
	// If you don't close it, your request will be missing the terminating boundary.
	w.Close()

	// Now that you have a form, you can submit it to your handler.
	req, err := http.NewRequest(method, urlStr, &b)
	if err != nil {
		return nil, err
	}
	// Don't forget to set the content type, this will contain the boundary.
	req.Header.Set("Content-Type", w.FormDataContentType())

	c.authenticateRequest(req)
	return c.doRequest(req, true)

}

func (c *Client) authenticateRequest(req *http.Request) {
	if c.Auth.bearerToken != "" {
		req.Header.Set("Authorization", "Bearer "+c.Auth.bearerToken)
	}

	if c.Auth.user != "" && c.Auth.password != "" {
		req.SetBasicAuth(c.Auth.user, c.Auth.password)
	} else if c.Auth.token.Valid() {
		c.Auth.token.SetAuthHeader(req)
	}
	return
}

func (c *Client) doRequest(req *http.Request, emptyResponse bool) (interface{}, error) {

	resp, err := c.HttpClient.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.Body != nil {
		defer resp.Body.Close()
	}

	if (resp.StatusCode != http.StatusOK) && (resp.StatusCode != http.StatusCreated) {
		return nil, fmt.Errorf(resp.Status)
	}

	if emptyResponse {
		return nil, nil
	}

	if resp.Body == nil {
		return nil, fmt.Errorf("response body is nil")
	}

	resBodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var result interface{}
	err = json.Unmarshal(resBodyBytes, &result)
	if err != nil {
		return nil, err
	}

	return result, nil
}

func (c *Client) requestUrl(template string, args ...interface{}) string {

	if len(args) == 1 && args[0] == "" {
		return GetApiBaseURL() + template
	}
	return GetApiBaseURL() + fmt.Sprintf(template, args...)
}
<|endoftext|>