<commit_before>package pebbleclient

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"

	"golang.org/x/net/context"
	"golang.org/x/net/context/ctxhttp"
)

const maxPartialBody = 64 * 1024

type options Options

func (o options) Merge(other *options) options {
	op := Options(*other)
	return options(Options(o).Merge(&op))
}

// HTTPClient is a client for the Central API.
type HTTPClient struct {
	options
	hc *http.Client
}

// New constructs a new client.
func NewHTTPClient(opts Options) (*HTTPClient, error) {
	if err := opts.validate(); err != nil {
		return nil, err
	}
	var newOpts options = *(*options)(opts.applyDefaults())
	return &HTTPClient{
		options: newOpts,
		hc:      newOpts.HTTPClient,
	}, nil
}

// NewFromHTTPRequest constructs a new client that inherits the host name, protocol,
// session and request ID from an HTTP request. Any options specified will override inferred
// from the request.
func (client *HTTPClient) FromHTTPRequest(req *http.Request) (*HTTPClient, error) {
	opts := Options(client.options)

	var host string
	if hosts, ok := req.Header["X-Forwarded-Host"]; ok && len(hosts) > 0 {
		host = hosts[len(hosts)-1]
	} else {
		host = strings.SplitN(req.Host, ":", 2)[0]
	}
	opts.Host = host

	opts.Protocol = req.URL.Scheme

	if session := req.URL.Query().Get("session"); session != "" {
		opts.Session = session
	} else if cookie, err := req.Cookie("checkpoint.session"); err == nil {
		opts.Session = cookie.Value
	}

	if id := req.Header.Get("Request-Id"); id != "" {
		opts.RequestId = id
	}

	opts.HTTPClient = client.hc

	return NewHTTPClient(opts)
}

// GetOptions returns a copy of this client's options.
func (client *HTTPClient) GetOptions() Options {
	return Options(client.options)
}

func (client *HTTPClient) Options(opts Options) Client {
	newOpts := client.options.Merge((*options)(&opts))
	return &HTTPClient{
		options: newOpts,
		hc:      client.hc,
	}
}

func (client *HTTPClient) Get(path string, opts *RequestOptions, result interface{}) error {
	return client.do(opts, http.MethodGet, path, nil, result)
}

func (client *HTTPClient) Head(path string, opts *RequestOptions) error {
	return client.do(opts, http.MethodHead, path, nil, nil)
}

func (client *HTTPClient) Post(path string, opts *RequestOptions, body io.Reader, result interface{}) error {
	return client.do(opts, http.MethodPost, path, body, result)
}

func (client *HTTPClient) Put(path string, opts *RequestOptions, body io.Reader, result interface{}) error {
	return client.do(opts, http.MethodPut, path, body, result)
}

func (client *HTTPClient) Delete(path string, opts *RequestOptions, result interface{}) error {
	return client.do(opts, http.MethodDelete, path, nil, result)
}

func (client *HTTPClient) do(
	opts *RequestOptions,
	method string,
	path string,
	bodyIn io.Reader,
	result interface{}) error {
	if opts == nil {
		opts = &RequestOptions{}
	}

	req, err := http.NewRequest(method, client.formatEndpointUrl(path, opts.Params), bodyIn)
	if err != nil {
		return err
	}

	req.Header.Set("Content-Type", "application/json; charset=utf8")
	if client.options.RequestId != "" {
		req.Header.Set("Request-Id", client.options.RequestId)
	}

	ctx := client.Ctx
	if ctx == nil {
		ctx = context.Background()
	}

	resp, err := ctxhttp.Do(ctx, client.hc, req)
	if err != nil {
		return err
	}

	respBody := resp.Body
	defer func() {
		if respBody != nil {
			// Drain remaining body to work around bug in Go < 1.7
			_, _ = io.Copy(ioutil.Discard, respBody)

			_ = respBody.Close()
		}
	}()

	if isNonSuccessStatus(resp.StatusCode) {
		return client.buildError(&RequestError{}, opts, resp)
	}

	if doesStatusCodeYieldBody(resp.StatusCode) && result != nil {
		return decodeResponseAsJSON(resp, respBody, result)
	}

	return nil
}

func (client *HTTPClient) buildError(
	err *RequestError,
	opts *RequestOptions,
	resp *http.Response) error {
	var buf bytes.Buffer
	b := make([]byte, 1024)
	for buf.Len() < maxPartialBody {
		count, err := resp.Body.Read(b[:])
		if count == 0 {
			break
		}
		if err != nil && err != io.EOF {
			break
		}
		_, wErr := buf.Write(b[0:count])
		if err != nil || wErr != nil {
			break
		}
	}
	err.PartialBody = buf.Bytes()
	err.client = client
	err.Resp = resp
	err.Options = opts
	return err
}

func (client *HTTPClient) formatEndpointUrl(path string, params Params) string {
	if path[0:1] == "/" {
		path = path[1:]
	}
	result := url.URL{
		Scheme: client.Protocol,
		Host:   client.Host,
		Path: fmt.Sprintf("/api/%s/v%d/%s",
			client.ServiceName, client.ApiVersion, escapedPath(path)),
	}

	query := result.Query()
	if params != nil {
		for key, value := range params {
			query.Set(key, fmt.Sprintf("%s", value))
		}
	}
	if client.Session != "" {
		query.Set("session", client.Session)
	}
	result.RawQuery = query.Encode()

	return result.String()
}
<commit_msg>Use io.LimitedReader to read partial body on errors.<commit_after>package pebbleclient

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"

	"golang.org/x/net/context"
	"golang.org/x/net/context/ctxhttp"
)

const maxPartialBody = 64 * 1024

type options Options

func (o options) Merge(other *options) options {
	op := Options(*other)
	return options(Options(o).Merge(&op))
}

// HTTPClient is a client for the Central API.
type HTTPClient struct {
	options
	hc *http.Client
}

// New constructs a new client.
func NewHTTPClient(opts Options) (*HTTPClient, error) {
	if err := opts.validate(); err != nil {
		return nil, err
	}
	var newOpts options = *(*options)(opts.applyDefaults())
	return &HTTPClient{
		options: newOpts,
		hc:      newOpts.HTTPClient,
	}, nil
}

// NewFromHTTPRequest constructs a new client that inherits the host name, protocol,
// session and request ID from an HTTP request. Any options specified will override inferred
// from the request.
func (client *HTTPClient) FromHTTPRequest(req *http.Request) (*HTTPClient, error) {
	opts := Options(client.options)

	var host string
	if hosts, ok := req.Header["X-Forwarded-Host"]; ok && len(hosts) > 0 {
		host = hosts[len(hosts)-1]
	} else {
		host = strings.SplitN(req.Host, ":", 2)[0]
	}
	opts.Host = host

	opts.Protocol = req.URL.Scheme

	if session := req.URL.Query().Get("session"); session != "" {
		opts.Session = session
	} else if cookie, err := req.Cookie("checkpoint.session"); err == nil {
		opts.Session = cookie.Value
	}

	if id := req.Header.Get("Request-Id"); id != "" {
		opts.RequestId = id
	}

	opts.HTTPClient = client.hc

	return NewHTTPClient(opts)
}

// GetOptions returns a copy of this client's options.
func (client *HTTPClient) GetOptions() Options {
	return Options(client.options)
}

func (client *HTTPClient) Options(opts Options) Client {
	newOpts := client.options.Merge((*options)(&opts))
	return &HTTPClient{
		options: newOpts,
		hc:      client.hc,
	}
}

func (client *HTTPClient) Get(path string, opts *RequestOptions, result interface{}) error {
	return client.do(opts, http.MethodGet, path, nil, result)
}

func (client *HTTPClient) Head(path string, opts *RequestOptions) error {
	return client.do(opts, http.MethodHead, path, nil, nil)
}

func (client *HTTPClient) Post(path string, opts *RequestOptions, body io.Reader, result interface{}) error {
	return client.do(opts, http.MethodPost, path, body, result)
}

func (client *HTTPClient) Put(path string, opts *RequestOptions, body io.Reader, result interface{}) error {
	return client.do(opts, http.MethodPut, path, body, result)
}

func (client *HTTPClient) Delete(path string, opts *RequestOptions, result interface{}) error {
	return client.do(opts, http.MethodDelete, path, nil, result)
}

func (client *HTTPClient) do(
	opts *RequestOptions,
	method string,
	path string,
	bodyIn io.Reader,
	result interface{}) error {
	if opts == nil {
		opts = &RequestOptions{}
	}

	req, err := http.NewRequest(method, client.formatEndpointUrl(path, opts.Params), bodyIn)
	if err != nil {
		return err
	}

	req.Header.Set("Content-Type", "application/json; charset=utf8")
	if client.options.RequestId != "" {
		req.Header.Set("Request-Id", client.options.RequestId)
	}

	ctx := client.Ctx
	if ctx == nil {
		ctx = context.Background()
	}

	resp, err := ctxhttp.Do(ctx, client.hc, req)
	if err != nil {
		return err
	}

	respBody := resp.Body
	defer func() {
		if respBody != nil {
			// Drain remaining body to work around bug in Go < 1.7
			_, _ = io.Copy(ioutil.Discard, respBody)

			_ = respBody.Close()
		}
	}()

	if isNonSuccessStatus(resp.StatusCode) {
		return client.buildError(&RequestError{}, opts, resp)
	}

	if doesStatusCodeYieldBody(resp.StatusCode) && result != nil {
		return decodeResponseAsJSON(resp, respBody, result)
	}

	return nil
}

func (client *HTTPClient) buildError(
	error *RequestError,
	opts *RequestOptions,
	resp *http.Response) error {
	b, _ := ioutil.ReadAll(&io.LimitedReader{
		R: resp.Body,
		N: maxPartialBody,
	})
	error.PartialBody = b
	error.client = client
	error.Resp = resp
	error.Options = opts
	return error
}

func (client *HTTPClient) formatEndpointUrl(path string, params Params) string {
	if path[0:1] == "/" {
		path = path[1:]
	}
	result := url.URL{
		Scheme: client.Protocol,
		Host:   client.Host,
		Path: fmt.Sprintf("/api/%s/v%d/%s",
			client.ServiceName, client.ApiVersion, escapedPath(path)),
	}

	query := result.Query()
	if params != nil {
		for key, value := range params {
			query.Set(key, fmt.Sprintf("%s", value))
		}
	}
	if client.Session != "" {
		query.Set("session", client.Session)
	}
	result.RawQuery = query.Encode()

	return result.String()
}
<|endoftext|>


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

// Package runtime implements utility functions for runtime systems.
package runtime

import (
	"io/ioutil"
	"syscall"
)

func FDLimit() (uint64, error) {
	var rlimit syscall.Rlimit
	if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit); err != nil {
		return 0, err
	}
	return rlimit.Cur, nil
}

func FDUsage() (uint64, error) {
	fds, err := ioutil.ReadDir("/proc/self/fd")
	if err != nil {
		return 0, err
	}
	return uint64(len(fds)), nil
}
<commit_msg>pkg/runtime: optimize FDUsage by removing sort<commit_after>// Copyright 2015 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package runtime implements utility functions for runtime systems.
package runtime

import (
	"os"
	"syscall"
)

func FDLimit() (uint64, error) {
	var rlimit syscall.Rlimit
	if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit); err != nil {
		return 0, err
	}
	return rlimit.Cur, nil
}

func FDUsage() (uint64, error) {
	return countFiles("/proc/self/fd")
}

// countFiles reads the directory named by dirname and returns the count.
// This is same as stdlib "io/ioutil.ReadDir" but without sorting.
func countFiles(dirname string) (uint64, error) {
	f, err := os.Open(dirname)
	if err != nil {
		return 0, err
	}
	list, err := f.Readdir(-1)
	f.Close()
	if err != nil {
		return 0, err
	}
	return uint64(len(list)), nil
}
<|endoftext|>


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

package gomatrixserverlib

import (
	"bytes"
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"

	"github.com/matrix-org/gomatrix"
	"github.com/matrix-org/util"
	"github.com/sirupsen/logrus"
)

// Default HTTPS request timeout
const requestTimeout time.Duration = time.Duration(30) * time.Second

// A Client makes request to the federation listeners of matrix
// homeservers
type Client struct {
	client http.Client
}

// UserInfo represents information about a user.
type UserInfo struct {
	Sub string `json:"sub"`
}

// NewClient makes a new Client (with default timeout)
func NewClient(skipVerify bool) *Client {
	return NewClientWithTimeout(requestTimeout, newFederationTripper(skipVerify))
}

// NewClientWithTransport makes a new Client with an existing transport
func NewClientWithTransport(skipVerify bool, transport http.RoundTripper) *Client {
	return NewClientWithTimeout(requestTimeout, transport)
}

// NewClientWithTimeout makes a new Client with a specified request timeout
func NewClientWithTimeout(timeout time.Duration, transport http.RoundTripper) *Client {
	return &Client{
		client: http.Client{
			Transport: transport,
			Timeout:   timeout,
		},
	}
}

type federationTripper struct {
	// transports maps an TLS server name with an HTTP transport.
	transports      map[string]http.RoundTripper
	transportsMutex sync.Mutex
	skipVerify      bool
}

func newFederationTripper(skipVerify bool) *federationTripper {
	return &federationTripper{
		transports: make(map[string]http.RoundTripper),
		skipVerify: skipVerify,
	}
}

// getTransport returns a http.Transport instance with a TLS configuration using
// the given server name for SNI. It also creates the instance if there isn't
// any for this server name.
// We need to use one transport per TLS server name (instead of giving our round
// tripper a single transport) because there is no way to specify the TLS
// ServerName on a per-connection basis.
func (f *federationTripper) getTransport(tlsServerName string) (transport http.RoundTripper) {
	var ok bool

	f.transportsMutex.Lock()

	// Create the transport if we don't have any for this TLS server name.
	if transport, ok = f.transports[tlsServerName]; !ok {
		transport = &http.Transport{
			TLSClientConfig: &tls.Config{
				ServerName:         tlsServerName,
				InsecureSkipVerify: f.skipVerify,
			},
		}

		f.transports[tlsServerName] = transport
	}

	f.transportsMutex.Unlock()

	return transport
}

func makeHTTPSURL(u *url.URL, addr string) (httpsURL url.URL) {
	httpsURL = *u
	httpsURL.Scheme = "https"
	httpsURL.Host = addr
	return
}

func (f *federationTripper) RoundTrip(r *http.Request) (*http.Response, error) {
	serverName := ServerName(r.URL.Host)
	resolutionResults, err := ResolveServer(serverName)
	if err != nil {
		return nil, err
	}

	if len(resolutionResults) == 0 {
		return nil, fmt.Errorf("no address found for matrix host %v", serverName)
	}

	var resp *http.Response
	// TODO: respect the priority and weight fields from the SRV record
	for _, result := range resolutionResults {
		u := makeHTTPSURL(r.URL, result.Destination)
		r.URL = &u
		r.Host = string(result.Host)
		resp, err = f.getTransport(result.TLSServerName).RoundTrip(r)
		if err == nil {
			return resp, nil
		}
		util.GetLogger(r.Context()).Warnf("Error sending request to %s: %v",
			u.String(), err)
	}

	// just return the most recent error
	return nil, err
}

// LookupUserInfo gets information about a user from a given matrix homeserver
// using a bearer access token.
func (fc *Client) LookupUserInfo(
	ctx context.Context, matrixServer ServerName, token string,
) (u UserInfo, err error) {
	url := url.URL{
		Scheme:   "matrix",
		Host:     string(matrixServer),
		Path:     "/_matrix/federation/v1/openid/userinfo",
		RawQuery: url.Values{"access_token": []string{token}}.Encode(),
	}

	req, err := http.NewRequest("GET", url.String(), nil)
	if err != nil {
		return
	}

	var response *http.Response
	response, err = fc.DoHTTPRequest(ctx, req)
	if response != nil {
		defer response.Body.Close() // nolint: errcheck
	}
	if err != nil {
		return
	}
	if response.StatusCode < 200 || response.StatusCode >= 300 {
		var errorOutput []byte
		errorOutput, err = ioutil.ReadAll(response.Body)
		if err != nil {
			return
		}
		err = fmt.Errorf("HTTP %d : %s", response.StatusCode, errorOutput)
		return
	}

	err = json.NewDecoder(response.Body).Decode(&u)
	if err != nil {
		return
	}

	userParts := strings.SplitN(u.Sub, ":", 2)
	if len(userParts) != 2 || userParts[1] != string(matrixServer) {
		err = fmt.Errorf("userID doesn't match server name '%v' != '%v'", u.Sub, matrixServer)
		return
	}

	return
}

// GetServerKeys asks a matrix server for its signing keys and TLS cert
func (fc *Client) GetServerKeys(
	ctx context.Context, matrixServer ServerName,
) (ServerKeys, error) {
	url := url.URL{
		Scheme: "matrix",
		Host:   string(matrixServer),
		Path:   "/_matrix/key/v2/server",
	}

	var body ServerKeys
	req, err := http.NewRequest("GET", url.String(), nil)
	if err != nil {
		return body, err
	}

	err = fc.DoRequestAndParseResponse(
		ctx, req, &body,
	)
	return body, err
}

// GetVersion gets the version information of a homeserver.
// See https://matrix.org/docs/spec/server_server/r0.1.1.html#get-matrix-federation-v1-version
func (fc *Client) GetVersion(
	ctx context.Context, s ServerName,
) (res Version, err error) {
	// Construct a request for version information
	url := url.URL{
		Scheme: "matrix",
		Host:   string(s),
		Path:   "/_matrix/federation/v1/version",
	}
	req, err := http.NewRequest("GET", url.String(), nil)
	if err != nil {
		return
	}

	// Make the request and parse the response
	err = fc.DoRequestAndParseResponse(ctx, req, &res)
	return
}

// LookupServerKeys looks up the keys for a matrix server from a matrix server.
// The first argument is the name of the matrix server to download the keys from.
// The second argument is a map from (server name, key ID) pairs to timestamps.
// The (server name, key ID) pair identifies the key to download.
// The timestamps tell the server when the keys need to be valid until.
// Perspective servers can use that timestamp to determine whether they can
// return a cached copy of the keys or whether they will need to retrieve a fresh
// copy of the keys.
// Returns the keys returned by the server, or an error if there was a problem talking to the server.
func (fc *Client) LookupServerKeys(
	ctx context.Context, matrixServer ServerName, keyRequests map[PublicKeyLookupRequest]Timestamp,
) ([]ServerKeys, error) {
	url := url.URL{
		Scheme: "matrix",
		Host:   string(matrixServer),
		Path:   "/_matrix/key/v2/query",
	}

	// The request format is:
	// { "server_keys": { "<server_name>": { "<key_id>": { "minimum_valid_until_ts": <ts> }}}
	type keyreq struct {
		MinimumValidUntilTS Timestamp `json:"minimum_valid_until_ts"`
	}
	request := struct {
		ServerKeyMap map[ServerName]map[KeyID]keyreq `json:"server_keys"`
	}{map[ServerName]map[KeyID]keyreq{}}
	for k, ts := range keyRequests {
		server := request.ServerKeyMap[k.ServerName]
		if server == nil {
			server = map[KeyID]keyreq{}
			request.ServerKeyMap[k.ServerName] = server
		}
		if k.KeyID != "" {
			server[k.KeyID] = keyreq{ts}
		}
	}

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

	var body struct {
		ServerKeyList []json.RawMessage `json:"server_keys"`
	}

	var res struct {
		ServerKeyList []ServerKeys
	}

	req, err := http.NewRequest("POST", url.String(), bytes.NewBuffer(requestBytes))
	if err != nil {
		return nil, err
	}
	req.Header.Add("Content-Type", "application/json")

	err = fc.DoRequestAndParseResponse(
		ctx, req, &body,
	)
	if err != nil {
		return nil, err
	}

	for _, field := range body.ServerKeyList {
		var keys ServerKeys
		if err := json.Unmarshal(field, &keys); err == nil {
			res.ServerKeyList = append(res.ServerKeyList, keys)
		}
	}

	return res.ServerKeyList, nil
}

// CreateMediaDownloadRequest creates a request for media on a homeserver and returns the http.Response or an error
func (fc *Client) CreateMediaDownloadRequest(
	ctx context.Context, matrixServer ServerName, mediaID string,
) (*http.Response, error) {
	// Set allow_remote=false here so that we avoid loops:
	// https://github.com/matrix-org/synapse/pull/1992
	requestURL := "matrix://" + string(matrixServer) + "/_matrix/media/v1/download/" + string(matrixServer) + "/" + mediaID + "?allow_remote=false"
	req, err := http.NewRequest("GET", requestURL, nil)
	if err != nil {
		return nil, err
	}

	return fc.DoHTTPRequest(ctx, req)
}

// DoRequestAndParseResponse calls DoHTTPRequest and then decodes the response.
//
// If the HTTP response is not a 200, an attempt is made to parse the response
// body into a gomatrix.RespError. In any case, a non-200 response will result
// in a gomatrix.HTTPError.
//
func (fc *Client) DoRequestAndParseResponse(
	ctx context.Context,
	req *http.Request,
	result interface{},
) error {
	response, err := fc.DoHTTPRequest(ctx, req)
	if response != nil {
		defer response.Body.Close() // nolint: errcheck
	}
	if err != nil {
		return err
	}

	if response.StatusCode/100 != 2 { // not 2xx
		// Adapted from https://github.com/matrix-org/gomatrix/blob/master/client.go
		var contents []byte
		contents, err = ioutil.ReadAll(response.Body)
		if err != nil {
			return err
		}

		var wrap error
		var respErr gomatrix.RespError
		if _ = json.Unmarshal(contents, &respErr); respErr.ErrCode != "" {
			wrap = respErr
		}

		// If we failed to decode as RespError, don't just drop the HTTP body, include it in the
		// HTTP error instead (e.g proxy errors which return HTML).
		msg := fmt.Sprintf("Failed to %s JSON (hostname %q path %q)", req.Method, req.Host, req.URL.Path)
		if wrap == nil {
			msg += ": " + string(contents)
		}

		return gomatrix.HTTPError{
			Code:         response.StatusCode,
			Message:      msg,
			WrappedError: wrap,
			Contents:     contents,
		}
	}

	if err = json.NewDecoder(response.Body).Decode(result); err != nil {
		return err
	}

	return nil
}

// DoHTTPRequest creates an outgoing request ID and adds it to the context
// before sending off the request and awaiting a response.
//
// If the returned error is nil, the Response will contain a non-nil
// Body which the caller is expected to close.
//
func (fc *Client) DoHTTPRequest(ctx context.Context, req *http.Request) (*http.Response, error) {
	reqID := util.RandomString(12)
	logger := util.GetLogger(ctx).WithFields(logrus.Fields{
		"out.req.ID":     reqID,
		"out.req.method": req.Method,
		"out.req.uri":    req.URL,
	})
	logger.Trace("Outgoing request")
	newCtx := util.ContextWithLogger(ctx, logger)

	start := time.Now()
	resp, err := fc.client.Do(req.WithContext(newCtx))
	if err != nil {
		logger.WithField("error", err).Warn("Outgoing request failed")
		return nil, err
	}

	// we haven't yet read the body, so this is slightly premature, but it's the easiest place.
	logger.WithFields(logrus.Fields{
		"out.req.code":        resp.StatusCode,
		"out.req.duration_ms": int(time.Since(start) / time.Millisecond),
	}).Trace("Outgoing request returned")

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

package gomatrixserverlib

import (
	"bytes"
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"

	"github.com/matrix-org/gomatrix"
	"github.com/matrix-org/util"
	"github.com/sirupsen/logrus"
)

// Default HTTPS request timeout
const requestTimeout time.Duration = time.Duration(30) * time.Second

// A Client makes request to the federation listeners of matrix
// homeservers
type Client struct {
	client    http.Client
	userAgent string
}

// UserInfo represents information about a user.
type UserInfo struct {
	Sub string `json:"sub"`
}

// NewClient makes a new Client (with default timeout)
func NewClient(skipVerify bool) *Client {
	return NewClientWithTimeout(requestTimeout, newFederationTripper(skipVerify))
}

// NewClientWithTransport makes a new Client with an existing transport
func NewClientWithTransport(skipVerify bool, transport http.RoundTripper) *Client {
	return NewClientWithTimeout(requestTimeout, transport)
}

// NewClientWithTimeout makes a new Client with a specified request timeout
func NewClientWithTimeout(timeout time.Duration, transport http.RoundTripper) *Client {
	return &Client{
		client: http.Client{
			Transport: transport,
			Timeout:   timeout,
		},
	}
}

type federationTripper struct {
	// transports maps an TLS server name with an HTTP transport.
	transports      map[string]http.RoundTripper
	transportsMutex sync.Mutex
	skipVerify      bool
}

func newFederationTripper(skipVerify bool) *federationTripper {
	return &federationTripper{
		transports: make(map[string]http.RoundTripper),
		skipVerify: skipVerify,
	}
}

// getTransport returns a http.Transport instance with a TLS configuration using
// the given server name for SNI. It also creates the instance if there isn't
// any for this server name.
// We need to use one transport per TLS server name (instead of giving our round
// tripper a single transport) because there is no way to specify the TLS
// ServerName on a per-connection basis.
func (f *federationTripper) getTransport(tlsServerName string) (transport http.RoundTripper) {
	var ok bool

	f.transportsMutex.Lock()

	// Create the transport if we don't have any for this TLS server name.
	if transport, ok = f.transports[tlsServerName]; !ok {
		transport = &http.Transport{
			TLSClientConfig: &tls.Config{
				ServerName:         tlsServerName,
				InsecureSkipVerify: f.skipVerify,
			},
		}

		f.transports[tlsServerName] = transport
	}

	f.transportsMutex.Unlock()

	return transport
}

func makeHTTPSURL(u *url.URL, addr string) (httpsURL url.URL) {
	httpsURL = *u
	httpsURL.Scheme = "https"
	httpsURL.Host = addr
	return
}

func (f *federationTripper) RoundTrip(r *http.Request) (*http.Response, error) {
	serverName := ServerName(r.URL.Host)
	resolutionResults, err := ResolveServer(serverName)
	if err != nil {
		return nil, err
	}

	if len(resolutionResults) == 0 {
		return nil, fmt.Errorf("no address found for matrix host %v", serverName)
	}

	var resp *http.Response
	// TODO: respect the priority and weight fields from the SRV record
	for _, result := range resolutionResults {
		u := makeHTTPSURL(r.URL, result.Destination)
		r.URL = &u
		r.Host = string(result.Host)
		resp, err = f.getTransport(result.TLSServerName).RoundTrip(r)
		if err == nil {
			return resp, nil
		}
		util.GetLogger(r.Context()).Warnf("Error sending request to %s: %v",
			u.String(), err)
	}

	// just return the most recent error
	return nil, err
}

// SetUserAgent sets the user agent string that is sent in the headers of
// outbound HTTP requests.
func (fc *Client) SetUserAgent(ua string) {
	fc.userAgent = ua
}

// LookupUserInfo gets information about a user from a given matrix homeserver
// using a bearer access token.
func (fc *Client) LookupUserInfo(
	ctx context.Context, matrixServer ServerName, token string,
) (u UserInfo, err error) {
	url := url.URL{
		Scheme:   "matrix",
		Host:     string(matrixServer),
		Path:     "/_matrix/federation/v1/openid/userinfo",
		RawQuery: url.Values{"access_token": []string{token}}.Encode(),
	}

	req, err := http.NewRequest("GET", url.String(), nil)
	if err != nil {
		return
	}

	var response *http.Response
	response, err = fc.DoHTTPRequest(ctx, req)
	if response != nil {
		defer response.Body.Close() // nolint: errcheck
	}
	if err != nil {
		return
	}
	if response.StatusCode < 200 || response.StatusCode >= 300 {
		var errorOutput []byte
		errorOutput, err = ioutil.ReadAll(response.Body)
		if err != nil {
			return
		}
		err = fmt.Errorf("HTTP %d : %s", response.StatusCode, errorOutput)
		return
	}

	err = json.NewDecoder(response.Body).Decode(&u)
	if err != nil {
		return
	}

	userParts := strings.SplitN(u.Sub, ":", 2)
	if len(userParts) != 2 || userParts[1] != string(matrixServer) {
		err = fmt.Errorf("userID doesn't match server name '%v' != '%v'", u.Sub, matrixServer)
		return
	}

	return
}

// GetServerKeys asks a matrix server for its signing keys and TLS cert
func (fc *Client) GetServerKeys(
	ctx context.Context, matrixServer ServerName,
) (ServerKeys, error) {
	url := url.URL{
		Scheme: "matrix",
		Host:   string(matrixServer),
		Path:   "/_matrix/key/v2/server",
	}

	var body ServerKeys
	req, err := http.NewRequest("GET", url.String(), nil)
	if err != nil {
		return body, err
	}

	err = fc.DoRequestAndParseResponse(
		ctx, req, &body,
	)
	return body, err
}

// GetVersion gets the version information of a homeserver.
// See https://matrix.org/docs/spec/server_server/r0.1.1.html#get-matrix-federation-v1-version
func (fc *Client) GetVersion(
	ctx context.Context, s ServerName,
) (res Version, err error) {
	// Construct a request for version information
	url := url.URL{
		Scheme: "matrix",
		Host:   string(s),
		Path:   "/_matrix/federation/v1/version",
	}
	req, err := http.NewRequest("GET", url.String(), nil)
	if err != nil {
		return
	}

	// Make the request and parse the response
	err = fc.DoRequestAndParseResponse(ctx, req, &res)
	return
}

// LookupServerKeys looks up the keys for a matrix server from a matrix server.
// The first argument is the name of the matrix server to download the keys from.
// The second argument is a map from (server name, key ID) pairs to timestamps.
// The (server name, key ID) pair identifies the key to download.
// The timestamps tell the server when the keys need to be valid until.
// Perspective servers can use that timestamp to determine whether they can
// return a cached copy of the keys or whether they will need to retrieve a fresh
// copy of the keys.
// Returns the keys returned by the server, or an error if there was a problem talking to the server.
func (fc *Client) LookupServerKeys(
	ctx context.Context, matrixServer ServerName, keyRequests map[PublicKeyLookupRequest]Timestamp,
) ([]ServerKeys, error) {
	url := url.URL{
		Scheme: "matrix",
		Host:   string(matrixServer),
		Path:   "/_matrix/key/v2/query",
	}

	// The request format is:
	// { "server_keys": { "<server_name>": { "<key_id>": { "minimum_valid_until_ts": <ts> }}}
	type keyreq struct {
		MinimumValidUntilTS Timestamp `json:"minimum_valid_until_ts"`
	}
	request := struct {
		ServerKeyMap map[ServerName]map[KeyID]keyreq `json:"server_keys"`
	}{map[ServerName]map[KeyID]keyreq{}}
	for k, ts := range keyRequests {
		server := request.ServerKeyMap[k.ServerName]
		if server == nil {
			server = map[KeyID]keyreq{}
			request.ServerKeyMap[k.ServerName] = server
		}
		if k.KeyID != "" {
			server[k.KeyID] = keyreq{ts}
		}
	}

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

	var body struct {
		ServerKeyList []json.RawMessage `json:"server_keys"`
	}

	var res struct {
		ServerKeyList []ServerKeys
	}

	req, err := http.NewRequest("POST", url.String(), bytes.NewBuffer(requestBytes))
	if err != nil {
		return nil, err
	}
	req.Header.Add("Content-Type", "application/json")

	err = fc.DoRequestAndParseResponse(
		ctx, req, &body,
	)
	if err != nil {
		return nil, err
	}

	for _, field := range body.ServerKeyList {
		var keys ServerKeys
		if err := json.Unmarshal(field, &keys); err == nil {
			res.ServerKeyList = append(res.ServerKeyList, keys)
		}
	}

	return res.ServerKeyList, nil
}

// CreateMediaDownloadRequest creates a request for media on a homeserver and returns the http.Response or an error
func (fc *Client) CreateMediaDownloadRequest(
	ctx context.Context, matrixServer ServerName, mediaID string,
) (*http.Response, error) {
	// Set allow_remote=false here so that we avoid loops:
	// https://github.com/matrix-org/synapse/pull/1992
	requestURL := "matrix://" + string(matrixServer) + "/_matrix/media/v1/download/" + string(matrixServer) + "/" + mediaID + "?allow_remote=false"
	req, err := http.NewRequest("GET", requestURL, nil)
	if err != nil {
		return nil, err
	}

	return fc.DoHTTPRequest(ctx, req)
}

// DoRequestAndParseResponse calls DoHTTPRequest and then decodes the response.
//
// If the HTTP response is not a 200, an attempt is made to parse the response
// body into a gomatrix.RespError. In any case, a non-200 response will result
// in a gomatrix.HTTPError.
//
func (fc *Client) DoRequestAndParseResponse(
	ctx context.Context,
	req *http.Request,
	result interface{},
) error {
	response, err := fc.DoHTTPRequest(ctx, req)
	if response != nil {
		defer response.Body.Close() // nolint: errcheck
	}
	if err != nil {
		return err
	}

	if response.StatusCode/100 != 2 { // not 2xx
		// Adapted from https://github.com/matrix-org/gomatrix/blob/master/client.go
		var contents []byte
		contents, err = ioutil.ReadAll(response.Body)
		if err != nil {
			return err
		}

		var wrap error
		var respErr gomatrix.RespError
		if _ = json.Unmarshal(contents, &respErr); respErr.ErrCode != "" {
			wrap = respErr
		}

		// If we failed to decode as RespError, don't just drop the HTTP body, include it in the
		// HTTP error instead (e.g proxy errors which return HTML).
		msg := fmt.Sprintf("Failed to %s JSON (hostname %q path %q)", req.Method, req.Host, req.URL.Path)
		if wrap == nil {
			msg += ": " + string(contents)
		}

		return gomatrix.HTTPError{
			Code:         response.StatusCode,
			Message:      msg,
			WrappedError: wrap,
			Contents:     contents,
		}
	}

	if err = json.NewDecoder(response.Body).Decode(result); err != nil {
		return err
	}

	return nil
}

// DoHTTPRequest creates an outgoing request ID and adds it to the context
// before sending off the request and awaiting a response.
//
// If the returned error is nil, the Response will contain a non-nil
// Body which the caller is expected to close.
//
func (fc *Client) DoHTTPRequest(ctx context.Context, req *http.Request) (*http.Response, error) {
	reqID := util.RandomString(12)
	logger := util.GetLogger(ctx).WithFields(logrus.Fields{
		"out.req.ID":     reqID,
		"out.req.method": req.Method,
		"out.req.uri":    req.URL,
	})
	logger.Trace("Outgoing request")
	newCtx := util.ContextWithLogger(ctx, logger)
	if fc.userAgent != "" {
		req.Header.Set("User-Agent", fc.userAgent)
	}

	start := time.Now()
	resp, err := fc.client.Do(req.WithContext(newCtx))
	if err != nil {
		logger.WithField("error", err).Warn("Outgoing request failed")
		return nil, err
	}

	// we haven't yet read the body, so this is slightly premature, but it's the easiest place.
	logger.WithFields(logrus.Fields{
		"out.req.code":        resp.StatusCode,
		"out.req.duration_ms": int(time.Since(start) / time.Millisecond),
	}).Trace("Outgoing request returned")

	return resp, nil
}
<|endoftext|>


<commit_before>package bitbucket

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"

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

const DEFAULT_PAGE_LENGTH = 10
const DEFAULT_MAX_DEPTH = 1
const DEFAULT_BITBUCKET_API_BASE_URL = "https://api.bitbucket.org/2.0"

func apiBaseUrl() (*url.URL, error) {
	ev := os.Getenv("BITBUCKET_API_BASE_URL")
	if ev == "" {
		ev = DEFAULT_BITBUCKET_API_BASE_URL
	}

	return url.Parse(ev)
}

type Client struct {
	Auth         *auth
	Users        users
	User         user
	Teams        teams
	Repositories *Repositories
	Workspaces   *Workspace
	Pagelen      uint64
	MaxDepth     uint64
	apiBaseURL   *url.URL

	HttpClient *http.Client
}

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

type Response struct {
	Size     int           `json:"size"`
	Page     int           `json:"page"`
	Pagelen  int           `json:"pagelen"`
	Next     string        `json:"next"`
	Previous string        `json:"previous"`
	Values   []interface{} `json:"values"`
}

// 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 {
	bitbucketUrl, err := apiBaseUrl()
	if err != nil {
		log.Fatalf("invalid bitbucket url")
	}
	c := &Client{Auth: a, Pagelen: DEFAULT_PAGE_LENGTH, MaxDepth: DEFAULT_MAX_DEPTH, apiBaseURL: bitbucketUrl}
	c.Repositories = &Repositories{
		c:                  c,
		PullRequests:       &PullRequests{c: c},
		Pipelines:          &Pipelines{c: c},
		Repository:         &Repository{c: c},
		Issues:             &Issues{c: c},
		Commits:            &Commits{c: c},
		Diff:               &Diff{c: c},
		BranchRestrictions: &BranchRestrictions{c: c},
		Webhooks:           &Webhooks{c: c},
		Downloads:          &Downloads{c: c},
		DeployKeys:         &DeployKeys{c: c},
	}
	c.Users = &Users{c: c}
	c.User = &User{c: c}
	c.Teams = &Teams{c: c}
	c.Workspaces = &Workspace{c: c, Repositories: c.Repositories, Permissions: &Permission{c: c}}
	c.HttpClient = new(http.Client)
	return c
}

func (c *Client) GetOAuthToken() oauth2.Token {
	return c.Auth.token
}

func (c *Client) GetApiBaseURL() string {
	return fmt.Sprintf("%s%s", c.GetApiHostnameURL(), c.apiBaseURL.Path)
}

func (c *Client) GetApiHostnameURL() string {
	return fmt.Sprintf("%s://%s", c.apiBaseURL.Scheme, c.apiBaseURL.Host)
}

func (c *Client) SetApiBaseURL(urlStr url.URL) {
	c.apiBaseURL = &urlStr
}

func (c *Client) executeRaw(method string, urlStr string, text string) (io.ReadCloser, error) {
	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)
	return c.doRawRequest(req, false)
}

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()
		}

		if c.MaxDepth != DEFAULT_MAX_DEPTH {
			urlObj, err := url.Parse(urlStr)
			if err != nil {
				return nil, err
			}
			q := urlObj.Query()
			q.Set("max_depth", strconv.FormatUint(c.MaxDepth, 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
	}

	return result, nil
}

func (c *Client) executeFileUpload(method string, urlStr string, filePath string, fileName string, fieldname string, params map[string]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(fieldname, fileName); err != nil {
		return nil, err
	}

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

	for key, value := range params {
		err = w.WriteField(key, value)
		if 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)
	}
}

func (c *Client) doRequest(req *http.Request, emptyResponse bool) (interface{}, error) {
	resBody, err := c.doRawRequest(req, emptyResponse)
	if err != nil {
		return nil, err
	}
	if emptyResponse || resBody == nil {
		return nil, nil
	}

	defer resBody.Close()

	responseBytes, err := ioutil.ReadAll(resBody)
	if err != nil {
		return resBody, err
	}

	responsePaginated := &Response{}
	err = json.Unmarshal(responseBytes, responsePaginated)
	if err == nil && len(responsePaginated.Values) > 0 {
		var values []interface{}
		for {
			values = append(values, responsePaginated.Values...)
			if responsePaginated.Pagelen == 0 || responsePaginated.Size/responsePaginated.Pagelen <= responsePaginated.Page {
				break
			}
			newReq, err := http.NewRequest(req.Method, responsePaginated.Next, nil)
			if err != nil {
				return resBody, err
			}
			c.authenticateRequest(newReq)
			resp, err := c.doRawRequest(newReq, false)
			if err != nil {
				return resBody, err
			}
			json.NewDecoder(resp).Decode(responsePaginated)
		}
		responsePaginated.Values = values
		responseBytes, err = json.Marshal(responsePaginated)
		if err != nil {
			return resBody, err
		}
	}

	var result interface{}
	if err := json.Unmarshal(responseBytes, &result); err != nil {
		log.Println("Could not unmarshal JSON payload, returning raw response")
		return resBody, err
	}
	return result, nil
}

func (c *Client) doRawRequest(req *http.Request, emptyResponse bool) (io.ReadCloser, error) {
	resp, err := c.HttpClient.Do(req)
	if err != nil {
		return nil, err
	}

	if unexpectedHttpStatusCode(resp.StatusCode) {
		defer resp.Body.Close()

		out := &UnexpectedResponseStatusError{Status: resp.Status}

		body, err := io.ReadAll(resp.Body)
		if err != nil {
			out.Body = []byte(fmt.Sprintf("could not read the response body: %v", err))
		} else {
			out.Body = body
		}

		return nil, out
	}

	if emptyResponse || resp.StatusCode == http.StatusNoContent {
		resp.Body.Close()
		return nil, nil
	}

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

	return resp.Body, nil
}

func unexpectedHttpStatusCode(statusCode int) bool {
	switch statusCode {
	case http.StatusOK,
		http.StatusCreated,
		http.StatusNoContent,
		http.StatusAccepted:
		return false
	default:
		return true
	}
}

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

	if len(args) == 1 && args[0] == "" {
		return c.GetApiBaseURL() + template
	}
	return c.GetApiBaseURL() + fmt.Sprintf(template, args...)
}
<commit_msg>Don't reuse response struct for multiple requests (#191)<commit_after>package bitbucket

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"

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

const DEFAULT_PAGE_LENGTH = 10
const DEFAULT_MAX_DEPTH = 1
const DEFAULT_BITBUCKET_API_BASE_URL = "https://api.bitbucket.org/2.0"

func apiBaseUrl() (*url.URL, error) {
	ev := os.Getenv("BITBUCKET_API_BASE_URL")
	if ev == "" {
		ev = DEFAULT_BITBUCKET_API_BASE_URL
	}

	return url.Parse(ev)
}

type Client struct {
	Auth         *auth
	Users        users
	User         user
	Teams        teams
	Repositories *Repositories
	Workspaces   *Workspace
	Pagelen      uint64
	MaxDepth     uint64
	apiBaseURL   *url.URL

	HttpClient *http.Client
}

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

type Response struct {
	Size     int           `json:"size"`
	Page     int           `json:"page"`
	Pagelen  int           `json:"pagelen"`
	Next     string        `json:"next"`
	Previous string        `json:"previous"`
	Values   []interface{} `json:"values"`
}

// 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 {
	bitbucketUrl, err := apiBaseUrl()
	if err != nil {
		log.Fatalf("invalid bitbucket url")
	}
	c := &Client{Auth: a, Pagelen: DEFAULT_PAGE_LENGTH, MaxDepth: DEFAULT_MAX_DEPTH, apiBaseURL: bitbucketUrl}
	c.Repositories = &Repositories{
		c:                  c,
		PullRequests:       &PullRequests{c: c},
		Pipelines:          &Pipelines{c: c},
		Repository:         &Repository{c: c},
		Issues:             &Issues{c: c},
		Commits:            &Commits{c: c},
		Diff:               &Diff{c: c},
		BranchRestrictions: &BranchRestrictions{c: c},
		Webhooks:           &Webhooks{c: c},
		Downloads:          &Downloads{c: c},
		DeployKeys:         &DeployKeys{c: c},
	}
	c.Users = &Users{c: c}
	c.User = &User{c: c}
	c.Teams = &Teams{c: c}
	c.Workspaces = &Workspace{c: c, Repositories: c.Repositories, Permissions: &Permission{c: c}}
	c.HttpClient = new(http.Client)
	return c
}

func (c *Client) GetOAuthToken() oauth2.Token {
	return c.Auth.token
}

func (c *Client) GetApiBaseURL() string {
	return fmt.Sprintf("%s%s", c.GetApiHostnameURL(), c.apiBaseURL.Path)
}

func (c *Client) GetApiHostnameURL() string {
	return fmt.Sprintf("%s://%s", c.apiBaseURL.Scheme, c.apiBaseURL.Host)
}

func (c *Client) SetApiBaseURL(urlStr url.URL) {
	c.apiBaseURL = &urlStr
}

func (c *Client) executeRaw(method string, urlStr string, text string) (io.ReadCloser, error) {
	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)
	return c.doRawRequest(req, false)
}

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()
		}

		if c.MaxDepth != DEFAULT_MAX_DEPTH {
			urlObj, err := url.Parse(urlStr)
			if err != nil {
				return nil, err
			}
			q := urlObj.Query()
			q.Set("max_depth", strconv.FormatUint(c.MaxDepth, 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
	}

	return result, nil
}

func (c *Client) executeFileUpload(method string, urlStr string, filePath string, fileName string, fieldname string, params map[string]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(fieldname, fileName); err != nil {
		return nil, err
	}

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

	for key, value := range params {
		err = w.WriteField(key, value)
		if 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)
	}
}

func (c *Client) doRequest(req *http.Request, emptyResponse bool) (interface{}, error) {
	resBody, err := c.doRawRequest(req, emptyResponse)
	if err != nil {
		return nil, err
	}
	if emptyResponse || resBody == nil {
		return nil, nil
	}

	defer resBody.Close()

	responseBytes, err := ioutil.ReadAll(resBody)
	if err != nil {
		return resBody, err
	}

	responsePaginated := &Response{}
	err = json.Unmarshal(responseBytes, responsePaginated)
	if err == nil && len(responsePaginated.Values) > 0 {
		var values []interface{}
		for {
			values = append(values, responsePaginated.Values...)
			if len(responsePaginated.Next) == 0 {
				break
			}
			newReq, err := http.NewRequest(req.Method, responsePaginated.Next, nil)
			if err != nil {
				return resBody, err
			}
			c.authenticateRequest(newReq)
			resp, err := c.doRawRequest(newReq, false)
			if err != nil {
				return resBody, err
			}

			responsePaginated = &Response{}
			json.NewDecoder(resp).Decode(responsePaginated)
		}
		responsePaginated.Values = values
		responseBytes, err = json.Marshal(responsePaginated)
		if err != nil {
			return resBody, err
		}
	}

	var result interface{}
	if err := json.Unmarshal(responseBytes, &result); err != nil {
		log.Println("Could not unmarshal JSON payload, returning raw response")
		return resBody, err
	}
	return result, nil
}

func (c *Client) doRawRequest(req *http.Request, emptyResponse bool) (io.ReadCloser, error) {
	resp, err := c.HttpClient.Do(req)
	if err != nil {
		return nil, err
	}

	if unexpectedHttpStatusCode(resp.StatusCode) {
		defer resp.Body.Close()

		out := &UnexpectedResponseStatusError{Status: resp.Status}

		body, err := io.ReadAll(resp.Body)
		if err != nil {
			out.Body = []byte(fmt.Sprintf("could not read the response body: %v", err))
		} else {
			out.Body = body
		}

		return nil, out
	}

	if emptyResponse || resp.StatusCode == http.StatusNoContent {
		resp.Body.Close()
		return nil, nil
	}

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

	return resp.Body, nil
}

func unexpectedHttpStatusCode(statusCode int) bool {
	switch statusCode {
	case http.StatusOK,
		http.StatusCreated,
		http.StatusNoContent,
		http.StatusAccepted:
		return false
	default:
		return true
	}
}

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

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


<commit_before>package httptesting

import (
	"bytes"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"strings"
	"testing"

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

type Client struct {
	Client       *http.Client
	Response     *http.Response
	ResponseBody []byte

	t     *testing.T
	host  string
	https bool
}

// NewClient returns an initialized Client ready for using
func New(host string, isHttps bool) *Client {
	jar, _ := cookiejar.New(nil)

	// adjust host
	if strings.HasPrefix(host, "http://") || strings.HasPrefix(host, "https://") {
		u, err := url.Parse(host)
		if err == nil {
			host = u.Host
		}
	}

	return &Client{
		Client: &http.Client{Jar: jar},
		host:   host,
		https:  isHttps,
	}
}

// Host returns the host and port of the server, e.g. "127.0.0.1:9090"
func (test *Client) Host() string {
	if test.host[0] == ':' {
		return "127.0.0.1" + test.host
	}

	return test.host
}

// Url returns the abs http/https URL of the resource, e.g. "http://127.0.0.1:9090/status".
// The scheme is set to https if http.ssl is set to true in the configuration.
func (test *Client) Url(path string) string {
	if test.https {
		return "https://" + test.Host() + path
	}

	return "http://" + test.Host() + path
}

// WebsocketUrl returns the abs websocket URL of the resource, e.g. "ws://127.0.0.1:9090/status"
func (test *Client) WebsocketUrl(path string) string {
	return "ws://" + test.Host() + path
}

// Cookies returns cookies related with the host
func (test *Client) Cookies() []*http.Cookie {
	u, _ := url.Parse(test.Url("/"))

	return test.Client.Jar.Cookies(u)
}

// SetCookie sets cookies with the host
func (test *Client) SetCookies(cookies []*http.Cookie) {
	u, _ := url.Parse(test.Url("/"))

	test.Client.Jar.SetCookies(u, cookies)
}

// New returns a RequestClient which has more customlization!
func (test *Client) New(t *testing.T) *RequestClient {
	client := NewRequestClient(test)
	client.t = t

	return client
}

// NewRequest issues any request and read the response.
// If successful, the caller may examine the Response and ResponseBody properties.
// NOTE: You have to manage session / cookie data manually.
func (test *Client) NewRequest(t *testing.T, request *http.Request) {
	test.t = t

	var err error

	test.Response, err = test.Client.Do(request)
	if err != nil {
		t.Fatalf("[REQUEST] %s %s: %#v\n", request.Method, request.URL.Path, err.Error())
	}
	defer test.Response.Body.Close()

	// Read response body if not empty
	test.ResponseBody = []byte{}
	if test.Response.ContentLength > 0 {
		test.ResponseBody, err = ioutil.ReadAll(test.Response.Body)
		if err != nil {
			t.Fatalf("[RESPONSE] %s %s: %#v\n", request.Method, request.URL.Path, err)
		}
	}
}

// NewSessionRequest issues any request with session / cookie and read the response.
// If successful, the caller may examine the Response and ResponseBody properties.
// NOTE: Session data will be added to the request cookies for you.
func (test *Client) NewSessionRequest(t *testing.T, request *http.Request) {
	for _, cookie := range test.Client.Jar.Cookies(request.URL) {
		request.AddCookie(cookie)
	}

	test.NewRequest(t, request)
}

// NewFilterRequest issues any request with TransportFiler and read the response.
// If successful, the caller may examine the Response and ResponseBody properties.
// NOTE: It returns error without apply HTTP request when transport filter returned an error.
func (test *Client) NewFilterRequest(t *testing.T, request *http.Request, filter TransportFilter) {
	test.t = t

	var err error

	client := &http.Client{
		Transport: newTransport(filter),
	}

	test.Response, err = client.Do(request)
	if err != nil {
		t.Fatalf("[REQUEST] %s %s: %#v\n", request.Method, request.URL.Path, err.Error())
	}

	// Read response body
	test.ResponseBody, err = ioutil.ReadAll(test.Response.Body)
	if err != nil {
		t.Fatalf("[RESPONSE] %s %s: %#v\n", request.Method, request.URL.Path, err)
	}
	test.Response.Body.Close()
}

// NewMultipartRequest issues a multipart request for the method & fields given and read the response.
// If successful, the caller may examine the Response and ResponseBody properties.
func (test *Client) NewMultipartRequest(t *testing.T, method, path, filename string, file interface{}, fields ...map[string]string) {
	test.t = t

	var buf bytes.Buffer

	mw := multipart.NewWriter(&buf)

	fw, ferr := mw.CreateFormFile("filename", filename)
	if ferr != nil {
		t.Fatalf("%s %s: %#v\n", method, path, ferr)
	}

	// apply file
	var (
		reader io.Reader
		err    error
	)
	switch file.(type) {
	case io.Reader:
		reader, _ = file.(io.Reader)

	case *os.File:
		reader, _ = file.(*os.File)

	case string:
		filepath, _ := file.(string)

		reader, err = os.Open(filepath)
		if err != nil {
			t.Fatalf("%s %s: %#v\n", method, path, err)
		}

	}

	if _, err := io.Copy(fw, reader); err != nil {
		t.Fatalf("%s %s: %#v\n", method, path, err)
	}

	// apply fields
	if len(fields) > 0 {
		for key, value := range fields[0] {
			mw.WriteField(key, value)
		}
	}

	// adds the terminating boundary
	mw.Close()

	request, err := http.NewRequest(method, test.Url(path), &buf)
	if err != nil {
		t.Fatalf("%s %s: %#v\n", method, path, err)
	}
	request.Header.Set("Content-Type", mw.FormDataContentType())

	test.NewRequest(t, request)
}

// NewWebsocket creates a websocket connection to the given path and returns the connection
func (test *Client) NewWebsocket(t *testing.T, path string) *websocket.Conn {
	origin := test.WebsocketUrl("/")
	target := test.WebsocketUrl(path)

	ws, err := websocket.Dial(target, "", origin)
	if err != nil {
		t.Fatalf("WS %s: %#v\n", path, err)
	}

	return ws
}
<commit_msg>fix the bug when transferring data by chunked model<commit_after>package httptesting

import (
	"bytes"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"strings"
	"testing"

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

type Client struct {
	Client       *http.Client
	Response     *http.Response
	ResponseBody []byte

	t     *testing.T
	host  string
	https bool
}

// NewClient returns an initialized Client ready for using
func New(host string, isHttps bool) *Client {
	jar, _ := cookiejar.New(nil)

	// adjust host
	if strings.HasPrefix(host, "http://") || strings.HasPrefix(host, "https://") {
		u, err := url.Parse(host)
		if err == nil {
			host = u.Host
		}
	}

	return &Client{
		Client: &http.Client{Jar: jar},
		host:   host,
		https:  isHttps,
	}
}

// Host returns the host and port of the server, e.g. "127.0.0.1:9090"
func (test *Client) Host() string {
	if test.host[0] == ':' {
		return "127.0.0.1" + test.host
	}

	return test.host
}

// Url returns the abs http/https URL of the resource, e.g. "http://127.0.0.1:9090/status".
// The scheme is set to https if http.ssl is set to true in the configuration.
func (test *Client) Url(path string) string {
	if test.https {
		return "https://" + test.Host() + path
	}

	return "http://" + test.Host() + path
}

// WebsocketUrl returns the abs websocket URL of the resource, e.g. "ws://127.0.0.1:9090/status"
func (test *Client) WebsocketUrl(path string) string {
	return "ws://" + test.Host() + path
}

// Cookies returns cookies related with the host
func (test *Client) Cookies() []*http.Cookie {
	u, _ := url.Parse(test.Url("/"))

	return test.Client.Jar.Cookies(u)
}

// SetCookie sets cookies with the host
func (test *Client) SetCookies(cookies []*http.Cookie) {
	u, _ := url.Parse(test.Url("/"))

	test.Client.Jar.SetCookies(u, cookies)
}

// New returns a RequestClient which has more customlization!
func (test *Client) New(t *testing.T) *RequestClient {
	client := NewRequestClient(test)
	client.t = t

	return client
}

// NewRequest issues any request and read the response.
// If successful, the caller may examine the Response and ResponseBody properties.
// NOTE: You have to manage session / cookie data manually.
func (test *Client) NewRequest(t *testing.T, request *http.Request) {
	test.t = t

	var err error

	test.Response, err = test.Client.Do(request)
	if err != nil {
		t.Fatalf("[REQUEST] %s %s: %#v\n", request.Method, request.URL.Path, err.Error())
	}
	defer test.Response.Body.Close()

	// Read response body if not empty
	test.ResponseBody = []byte{}

	switch test.Response.StatusCode {
	case http.StatusNoContent:
		// ignore

	default:
		if test.ResponseBody, err = ioutil.ReadAll(test.Response.Body); err != nil {
			if err != io.EOF {
				t.Fatalf("[RESPONSE] %s %s: %#v\n", request.Method, request.URL.Path, err)
			}

			// unexpected EOF with content-length
			if test.Response.ContentLength > 0 && int64(len(test.ResponseBody)) != test.Response.ContentLength {
				t.Fatalf("[RESPONSE] %s %s: %#v\n", request.Method, request.URL.Path, err)
			}

			t.Logf("[RESPONSE] %s %s: Unexptected response body with io.EOF error.", request.Method, request.URL.Path)
		}
	}
}

// NewSessionRequest issues any request with session / cookie and read the response.
// If successful, the caller may examine the Response and ResponseBody properties.
// NOTE: Session data will be added to the request cookies for you.
func (test *Client) NewSessionRequest(t *testing.T, request *http.Request) {
	for _, cookie := range test.Client.Jar.Cookies(request.URL) {
		request.AddCookie(cookie)
	}

	test.NewRequest(t, request)
}

// NewFilterRequest issues any request with TransportFiler and read the response.
// If successful, the caller may examine the Response and ResponseBody properties.
// NOTE: It returns error without apply HTTP request when transport filter returned an error.
func (test *Client) NewFilterRequest(t *testing.T, request *http.Request, filter TransportFilter) {
	test.t = t

	var err error

	client := &http.Client{
		Transport: newTransport(filter),
	}

	test.Response, err = client.Do(request)
	if err != nil {
		t.Fatalf("[REQUEST] %s %s: %#v\n", request.Method, request.URL.Path, err.Error())
	}

	// Read response body
	test.ResponseBody, err = ioutil.ReadAll(test.Response.Body)
	if err != nil {
		t.Fatalf("[RESPONSE] %s %s: %#v\n", request.Method, request.URL.Path, err)
	}
	test.Response.Body.Close()
}

// NewMultipartRequest issues a multipart request for the method & fields given and read the response.
// If successful, the caller may examine the Response and ResponseBody properties.
func (test *Client) NewMultipartRequest(t *testing.T, method, path, filename string, file interface{}, fields ...map[string]string) {
	test.t = t

	var buf bytes.Buffer

	mw := multipart.NewWriter(&buf)

	fw, ferr := mw.CreateFormFile("filename", filename)
	if ferr != nil {
		t.Fatalf("%s %s: %#v\n", method, path, ferr)
	}

	// apply file
	var (
		reader io.Reader
		err    error
	)
	switch file.(type) {
	case io.Reader:
		reader, _ = file.(io.Reader)

	case *os.File:
		reader, _ = file.(*os.File)

	case string:
		filepath, _ := file.(string)

		reader, err = os.Open(filepath)
		if err != nil {
			t.Fatalf("%s %s: %#v\n", method, path, err)
		}

	}

	if _, err := io.Copy(fw, reader); err != nil {
		t.Fatalf("%s %s: %#v\n", method, path, err)
	}

	// apply fields
	if len(fields) > 0 {
		for key, value := range fields[0] {
			mw.WriteField(key, value)
		}
	}

	// adds the terminating boundary
	mw.Close()

	request, err := http.NewRequest(method, test.Url(path), &buf)
	if err != nil {
		t.Fatalf("%s %s: %#v\n", method, path, err)
	}
	request.Header.Set("Content-Type", mw.FormDataContentType())

	test.NewRequest(t, request)
}

// NewWebsocket creates a websocket connection to the given path and returns the connection
func (test *Client) NewWebsocket(t *testing.T, path string) *websocket.Conn {
	origin := test.WebsocketUrl("/")
	target := test.WebsocketUrl(path)

	ws, err := websocket.Dial(target, "", origin)
	if err != nil {
		t.Fatalf("WS %s: %#v\n", path, err)
	}

	return ws
}
<|endoftext|>


<commit_before>package paranoidhttp

import (
	"context"
	"errors"
	"fmt"
	"log"
	"net"
	"net/http"
	"regexp"
	"time"
)

// DefaultClient is the default Client whose setting is the same as http.DefaultClient.
var DefaultClient *http.Client

func mustParseCIDR(addr string) *net.IPNet {
	_, ipnet, err := net.ParseCIDR(addr)
	if err != nil {
		log.Fatalf("%s must be parsed", addr)
	}
	return ipnet
}

var (
	netPrivateClassA = mustParseCIDR("10.0.0.0/8")
	netPrivateClassB = mustParseCIDR("172.16.0.0/12")
	netPrivateClassC = mustParseCIDR("192.168.0.0/16")
	netTestNet       = mustParseCIDR("192.0.2.0/24")
	net6To4Relay     = mustParseCIDR("192.88.99.0/24")
)

func init() {
	DefaultClient, _, _ = NewClient()
}

func safeAddr(ctx context.Context, resolver *net.Resolver, hostport string) (string, error) {
	host, port, err := net.SplitHostPort(hostport)
	if err != nil {
		return "", err
	}

	ip := net.ParseIP(host)
	if ip != nil {
		if ip.To4() != nil && isBadIPv4(ip) {
			return "", fmt.Errorf("bad ip is detected: %v", ip)
		}
		return net.JoinHostPort(ip.String(), port), nil
	}

	if isBadHost(host) {
		return "", fmt.Errorf("bad host is detected: %v", host)
	}

	r := resolver
	if r == nil {
		r = net.DefaultResolver
	}
	addrs, err := r.LookupIPAddr(ctx, host)
	if err != nil || len(addrs) <= 0 {
		return "", err
	}
	safeAddrs := make([]net.IPAddr, 0, len(addrs))
	for _, addr := range addrs {
		// only support IPv4 address
		if addr.IP.To4() == nil {
			continue
		}
		if isBadIPv4(addr.IP) {
			return "", fmt.Errorf("bad ip is detected: %v", addr.IP)
		}
		safeAddrs = append(safeAddrs, addr)
	}
	if len(safeAddrs) == 0 {
		return "", fmt.Errorf("fail to lookup ip addr: %v", host)
	}
	return net.JoinHostPort(safeAddrs[0].IP.String(), port), nil
}

// NewDialer returns a dialer function which only allows IPv4 connections.
//
// This is used to create a new paranoid http.Client,
// because I'm not sure about a paranoid behavior for IPv6 connections :(
func NewDialer(dialer *net.Dialer) func(ctx context.Context, network, addr string) (net.Conn, error) {
	return func(ctx context.Context, network, hostport string) (net.Conn, error) {
		switch network {
		case "tcp", "tcp4":
			addr, err := safeAddr(ctx, dialer.Resolver, hostport)
			if err != nil {
				return nil, err
			}
			return dialer.DialContext(ctx, "tcp4", addr)
		default:
			return nil, errors.New("does not support any networks except tcp4")
		}
	}
}

// NewClient returns a new http.Client configured to be paranoid for attackers.
//
// This also returns http.Tranport and net.Dialer so that you can customize those behavior.
func NewClient() (*http.Client, *http.Transport, *net.Dialer) {
	dialer := &net.Dialer{
		Timeout:   30 * time.Second,
		KeepAlive: 30 * time.Second,
	}
	transport := &http.Transport{
		Proxy:               http.ProxyFromEnvironment,
		DialContext:         NewDialer(dialer),
		TLSHandshakeTimeout: 10 * time.Second,
	}
	return &http.Client{
		Timeout:   30 * time.Second,
		Transport: transport,
	}, transport, dialer
}

var regLocalhost = regexp.MustCompile("(?i)^localhost$")
var regHasSpace = regexp.MustCompile("(?i)\\s+")

func isBadHost(host string) bool {
	if regLocalhost.MatchString(host) {
		return true
	}
	if regHasSpace.MatchString(host) {
		return true
	}

	return false
}

func isBadIPv4(ip net.IP) bool {
	if ip.To4() == nil {
		panic("cannot be called for IPv6")
	}

	if ip.Equal(net.IPv4bcast) || !ip.IsGlobalUnicast() ||
		netPrivateClassA.Contains(ip) || netPrivateClassB.Contains(ip) || netPrivateClassC.Contains(ip) ||
		netTestNet.Contains(ip) || net6To4Relay.Contains(ip) {
		return true
	}

	return false
}
<commit_msg>introduce structure BadIPError and BadHostError<commit_after>package paranoidhttp

import (
	"context"
	"errors"
	"fmt"
	"log"
	"net"
	"net/http"
	"regexp"
	"time"
)

// DefaultClient is the default Client whose setting is the same as http.DefaultClient.
var DefaultClient *http.Client

// BadIPError is returned when requested address is not permitted by paranoidhttp.
type BadIPError struct {
	IP net.IP
}

func (e *BadIPError) Error() string {
	return fmt.Sprintf("bad ip is detected: %v", e.IP)
}

// BadHostError is returned when requested hostname is not permitted by paranoidhttp.
type BadHostError struct {
	hostname string
}

func (e *BadHostError) Error() string {
	return fmt.Sprintf("bad host is detected: %v", e.hostname)
}

func newBadIPError(ip net.IP) error {
	return &BadIPError{ip}
}

func newBadHostError(hostname string) error {
	return &BadHostError{hostname}
}

func mustParseCIDR(addr string) *net.IPNet {
	_, ipnet, err := net.ParseCIDR(addr)
	if err != nil {
		log.Fatalf("%s must be parsed", addr)
	}
	return ipnet
}

var (
	netPrivateClassA = mustParseCIDR("10.0.0.0/8")
	netPrivateClassB = mustParseCIDR("172.16.0.0/12")
	netPrivateClassC = mustParseCIDR("192.168.0.0/16")
	netTestNet       = mustParseCIDR("192.0.2.0/24")
	net6To4Relay     = mustParseCIDR("192.88.99.0/24")
)

func init() {
	DefaultClient, _, _ = NewClient()
}

func safeAddr(ctx context.Context, resolver *net.Resolver, hostport string) (string, error) {
	host, port, err := net.SplitHostPort(hostport)
	if err != nil {
		return "", err
	}

	ip := net.ParseIP(host)
	if ip != nil {
		if ip.To4() != nil && isBadIPv4(ip) {
			return "", newBadIPError(ip)
		}
		return net.JoinHostPort(ip.String(), port), nil
	}

	if isBadHost(host) {
		return "", newBadHostError(host)
	}

	r := resolver
	if r == nil {
		r = net.DefaultResolver
	}
	addrs, err := r.LookupIPAddr(ctx, host)
	if err != nil || len(addrs) <= 0 {
		return "", err
	}
	safeAddrs := make([]net.IPAddr, 0, len(addrs))
	for _, addr := range addrs {
		// only support IPv4 address
		if addr.IP.To4() == nil {
			continue
		}
		if isBadIPv4(addr.IP) {
			return "", newBadIPError(addr.IP)
		}
		safeAddrs = append(safeAddrs, addr)
	}
	if len(safeAddrs) == 0 {
		return "", fmt.Errorf("fail to lookup ip addr: %v", host)
	}
	return net.JoinHostPort(safeAddrs[0].IP.String(), port), nil
}

// NewDialer returns a dialer function which only allows IPv4 connections.
//
// This is used to create a new paranoid http.Client,
// because I'm not sure about a paranoid behavior for IPv6 connections :(
func NewDialer(dialer *net.Dialer) func(ctx context.Context, network, addr string) (net.Conn, error) {
	return func(ctx context.Context, network, hostport string) (net.Conn, error) {
		switch network {
		case "tcp", "tcp4":
			addr, err := safeAddr(ctx, dialer.Resolver, hostport)
			if err != nil {
				return nil, err
			}
			return dialer.DialContext(ctx, "tcp4", addr)
		default:
			return nil, errors.New("does not support any networks except tcp4")
		}
	}
}

// NewClient returns a new http.Client configured to be paranoid for attackers.
//
// This also returns http.Tranport and net.Dialer so that you can customize those behavior.
func NewClient() (*http.Client, *http.Transport, *net.Dialer) {
	dialer := &net.Dialer{
		Timeout:   30 * time.Second,
		KeepAlive: 30 * time.Second,
	}
	transport := &http.Transport{
		Proxy:               http.ProxyFromEnvironment,
		DialContext:         NewDialer(dialer),
		TLSHandshakeTimeout: 10 * time.Second,
	}
	return &http.Client{
		Timeout:   30 * time.Second,
		Transport: transport,
	}, transport, dialer
}

var regLocalhost = regexp.MustCompile("(?i)^localhost$")
var regHasSpace = regexp.MustCompile("(?i)\\s+")

func isBadHost(host string) bool {
	if regLocalhost.MatchString(host) {
		return true
	}
	if regHasSpace.MatchString(host) {
		return true
	}

	return false
}

func isBadIPv4(ip net.IP) bool {
	if ip.To4() == nil {
		panic("cannot be called for IPv6")
	}

	if ip.Equal(net.IPv4bcast) || !ip.IsGlobalUnicast() ||
		netPrivateClassA.Contains(ip) || netPrivateClassB.Contains(ip) || netPrivateClassC.Contains(ip) ||
		netTestNet.Contains(ip) || net6To4Relay.Contains(ip) {
		return true
	}

	return false
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"gnd.la/log"
	"gnd.la/util/stringutil"
	"io"
	"net"
	"os"
	"os/signal"
	"os/user"
	"path/filepath"
	"strings"

	"gopkgs.com/dl.v1"
)

const help = `available commands are:
    start <service|all>   : starts a service or all services, in priority order
    stop <service|all>    : stops a service or all services, in priority order
    restart <service|all> : restart a service or all services, in priority order
    list                  : list registered services
    exit                  : close the shell
    help                  : show help`

func sendCommand(args []string) (bool, error) {
	conn, err := net.Dial("unix", SocketPath)
	if err != nil {
		return false, err
	}
	defer conn.Close()
	if err := encodeArgs(conn, args); err != nil {
		return false, err
	}
	log.Debugf("sent command %s", args)
	closed := false
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, os.Interrupt)
	defer signal.Stop(ch)
	done := make(chan struct{}, 1)
	defer func() {
		done <- struct{}{}
	}()
	go func() {
		select {
		case <-ch:
			closed = true
			conn.Close()
		case <-done:
		}
	}()
	ok := true
	for {
		r, s, err := decodeResponse(conn)
		if err != nil {
			if closed {
				return ok, nil
			}
			return ok, err
		}
		log.Debugf("received response %d", r)
		switch r {
		case respEnd:
			return ok, nil
		case respOk:
			fmt.Print(s)
		case respErr:
			ok = false
			fmt.Fprint(os.Stderr, s)
		default:
			return false, fmt.Errorf("invalid response type %d", r)
		}
	}
	return ok, nil
}

func evalCommand(args []string) (bool, error) {
	if len(args) > 0 {
		switch strings.ToLower(args[0]) {
		case "quit", "exit":
			os.Exit(0)
		case "help":
			fmt.Fprintf(os.Stderr, "%s\n", help)
			return true, nil
		}
	}
	return sendCommand(args)
}

func clientMain(args []string) (bool, error) {
	createGovernatorUserDir()
	if len(args) > 0 {
		return evalCommand(args)
	}
	r := newLineReader()
	fmt.Printf("%s interactive shell\nType exit or press control+d to end\nType help to show available commands\n\n", AppName)
	sendCommand([]string{"list"})
	for {
		s, err := r.ReadLine()
		if err == io.EOF {
			fmt.Print("exit\n")
			break
		}
		s = strings.TrimSpace(s)
		if s != "" {
			r.AddHistory(s)
			fields, err := stringutil.SplitFields(s, " ")
			if err != nil {
				fmt.Fprintf(os.Stderr, "error reading input: %s\n", err)
				continue
			}
			if _, err := evalCommand(fields); err != nil {
				fmt.Fprintf(os.Stderr, "error executing command: %s\n", err)
			}
		}
	}
	return true, nil
}

func governatorUserDir() (string, error) {
	usr, err := user.Current()
	if err != nil {
		return "", err
	}
	return filepath.Join(usr.HomeDir, "."+AppName), nil
}

func createGovernatorUserDir() error {
	dir, err := governatorUserDir()
	if err != nil {
		return err
	}
	return os.Mkdir(dir, 0755)
}

var (
	readline      func(string) *string
	add_history   func(string)
	read_history  func(string)
	write_history func(string)
)

type lineReader interface {
	ReadLine() (string, error)
	AddHistory(s string)
}

type bufLineReader struct {
	r *bufio.Reader
}

func (r *bufLineReader) ReadLine() (string, error) {
	fmt.Printf("%s> ", AppName)
	return r.r.ReadString('\n')
}

func (r *bufLineReader) AddHistory(_ string) {
}

func newLineReader() lineReader {
	if readline != nil {
		r := &readlineLineReader{}
		r.readHistory()
		return r
	}
	return &bufLineReader{
		r: bufio.NewReader(os.Stdin),
	}
}

type readlineLineReader struct {
}

func (r *readlineLineReader) historyFile() (string, error) {
	dir, err := governatorUserDir()
	if err != nil {
		return "", err
	}
	return filepath.Join(dir, "history"), nil
}

func (r *readlineLineReader) readHistory() error {
	if read_history != nil {
		file, err := r.historyFile()
		if err != nil {
			return err
		}
		read_history(file)
	}
	return nil
}

func (r *readlineLineReader) ReadLine() (string, error) {
	s := readline(fmt.Sprintf("%s> ", AppName))
	if s == nil {
		return "", io.EOF
	}
	return *s, nil
}

func (r *readlineLineReader) AddHistory(s string) {
	if add_history != nil {
		add_history(s)
		if write_history != nil {
			file, err := r.historyFile()
			if err != nil {
				return
			}
			write_history(file)
		}
	}
}

func init() {
	lib, _ := dl.Open("libreadline", 0)
	if lib != nil {
		lib.Sym("readline", &readline)
		lib.Sym("add_history", &add_history)
		lib.Sym("read_history", &read_history)
		lib.Sym("write_history", &write_history)
	}
}
<commit_msg>Try more library names when opening libreadline<commit_after>package main

import (
	"bufio"
	"fmt"
	"io"
	"net"
	"os"
	"os/signal"
	"os/user"
	"path/filepath"
	"strings"

	"gnd.la/log"
	"gnd.la/util/stringutil"

	"gopkgs.com/dl.v1"
)

const help = `available commands are:
    start <service|all>   : starts a service or all services, in priority order
    stop <service|all>    : stops a service or all services, in priority order
    restart <service|all> : restart a service or all services, in priority order
    list                  : list registered services
    exit                  : close the shell
    help                  : show help`

func sendCommand(args []string) (bool, error) {
	conn, err := net.Dial("unix", SocketPath)
	if err != nil {
		return false, err
	}
	defer conn.Close()
	if err := encodeArgs(conn, args); err != nil {
		return false, err
	}
	log.Debugf("sent command %s", args)
	closed := false
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, os.Interrupt)
	defer signal.Stop(ch)
	done := make(chan struct{}, 1)
	defer func() {
		done <- struct{}{}
	}()
	go func() {
		select {
		case <-ch:
			closed = true
			conn.Close()
		case <-done:
		}
	}()
	ok := true
	for {
		r, s, err := decodeResponse(conn)
		if err != nil {
			if closed {
				return ok, nil
			}
			return ok, err
		}
		log.Debugf("received response %d", r)
		switch r {
		case respEnd:
			return ok, nil
		case respOk:
			fmt.Print(s)
		case respErr:
			ok = false
			fmt.Fprint(os.Stderr, s)
		default:
			return false, fmt.Errorf("invalid response type %d", r)
		}
	}
	return ok, nil
}

func evalCommand(args []string) (bool, error) {
	if len(args) > 0 {
		switch strings.ToLower(args[0]) {
		case "quit", "exit":
			os.Exit(0)
		case "help":
			fmt.Fprintf(os.Stderr, "%s\n", help)
			return true, nil
		}
	}
	return sendCommand(args)
}

func clientMain(args []string) (bool, error) {
	createGovernatorUserDir()
	if len(args) > 0 {
		return evalCommand(args)
	}
	r := newLineReader()
	fmt.Printf("%s interactive shell\nType exit or press control+d to end\nType help to show available commands\n\n", AppName)
	sendCommand([]string{"list"})
	for {
		s, err := r.ReadLine()
		if err == io.EOF {
			fmt.Print("exit\n")
			break
		}
		s = strings.TrimSpace(s)
		if s != "" {
			r.AddHistory(s)
			fields, err := stringutil.SplitFields(s, " ")
			if err != nil {
				fmt.Fprintf(os.Stderr, "error reading input: %s\n", err)
				continue
			}
			if _, err := evalCommand(fields); err != nil {
				fmt.Fprintf(os.Stderr, "error executing command: %s\n", err)
			}
		}
	}
	return true, nil
}

func governatorUserDir() (string, error) {
	usr, err := user.Current()
	if err != nil {
		return "", err
	}
	return filepath.Join(usr.HomeDir, "."+AppName), nil
}

func createGovernatorUserDir() error {
	dir, err := governatorUserDir()
	if err != nil {
		return err
	}
	return os.Mkdir(dir, 0755)
}

var (
	readline      func(string) *string
	add_history   func(string)
	read_history  func(string)
	write_history func(string)
)

type lineReader interface {
	ReadLine() (string, error)
	AddHistory(s string)
}

type bufLineReader struct {
	r *bufio.Reader
}

func (r *bufLineReader) ReadLine() (string, error) {
	fmt.Printf("%s> ", AppName)
	return r.r.ReadString('\n')
}

func (r *bufLineReader) AddHistory(_ string) {
}

func newLineReader() lineReader {
	if readline != nil {
		r := &readlineLineReader{}
		r.readHistory()
		return r
	}
	return &bufLineReader{
		r: bufio.NewReader(os.Stdin),
	}
}

type readlineLineReader struct {
}

func (r *readlineLineReader) historyFile() (string, error) {
	dir, err := governatorUserDir()
	if err != nil {
		return "", err
	}
	return filepath.Join(dir, "history"), nil
}

func (r *readlineLineReader) readHistory() error {
	if read_history != nil {
		file, err := r.historyFile()
		if err != nil {
			return err
		}
		read_history(file)
	}
	return nil
}

func (r *readlineLineReader) ReadLine() (string, error) {
	s := readline(fmt.Sprintf("%s> ", AppName))
	if s == nil {
		return "", io.EOF
	}
	return *s, nil
}

func (r *readlineLineReader) AddHistory(s string) {
	if add_history != nil {
		add_history(s)
		if write_history != nil {
			file, err := r.historyFile()
			if err != nil {
				return
			}
			write_history(file)
		}
	}
}

func init() {
	var lib *dl.DL
	for _, v := range []string{"", dl.LibExt + ".5", dl.LibExt + ".6"} {
		lib, _ = dl.Open("libreadline"+v, 0)
		if lib != nil {
			break
		}
	}
	if lib != nil {
		lib.Sym("readline", &readline)
		lib.Sym("add_history", &add_history)
		lib.Sym("read_history", &read_history)
		lib.Sym("write_history", &write_history)
	}
}
<|endoftext|>


<commit_before>package growthforecast

/*

GrowthForecast (http://kazeburo.github.io/GrowthForecast/) is a data
visualization tool. This library gives you an easy way to interact
with the GrowthForecast server

*/

import (
  "bytes"
  "encoding/json"
  "errors"
  "fmt"
  "net/http"
  "net/url"
  "strings"
)

type Client struct {
  BaseURL string
}

/*

NewClient() creates a new growthforecast.Client struct. You just need
to pass it a base URL where all API endpoints will automatically be
generated from.

    client := growthforecast.NewClient("http://gf.mycompany.com")
    g, err := client.GetGraph("service/section/graph")
    if err != nil {
        log.Fatalf("Error while fetching graph: %s", err)
    }

*/
func NewClient(base string) *Client {
  return &Client { base }
}

func (self *Client) createURL(path string) string {
  if strings.HasPrefix(path, "/") {
    path = path[1:len(path)]
  }
  return fmt.Sprintf("%s/%s", self.BaseURL, path)
}

func (self *Client) getJSON(path string) (*http.Response, error) {
  url := self.createURL(path)
  res, err := http.Get(url)
  if err != nil {
    return nil, err
  }

  if res.StatusCode != 200 {
    return nil, errors.New(
      fmt.Sprintf(
        "HTTP request to %s failed",
        url,
      ),
    )
  }

  return res, nil
}

func (self *Client) getDecoder(path string) (*json.Decoder, error) {
  res, err := self.getJSON(path)
  if err != nil {
    return nil, err
  }

  return json.NewDecoder(res.Body), nil
}

// Note that when you create a graph, you can only specify the basic
// parameter
func (self *Client) CreateGraph(graph *Graph) (*Graph, error) {
  values := url.Values{
    "number": {fmt.Sprintf("%d",graph.Number)},
  }
  if graph.Mode != "" {
    values.Add("mode", graph.Mode)
  }
  if graph.Color != "" {
    values.Add("color", graph.Color)
  }
  url := self.createURL(fmt.Sprintf("api/%s", graph.GetPath()))
  res, err := http.PostForm(url, values)
  if err != nil {
    return nil, err
  }

  if res.StatusCode != 200 {
    return nil, errors.New(
      fmt.Sprintf(
        "HTTP request to %s failed",
        url,
      ),
    )
  }

  var jres struct {
    Data Graph      `json:"data"`
    Error int       `json:"error"`
  }
  dec := json.NewDecoder(res.Body)
  err = dec.Decode(&jres)
  if err != nil {
    return nil, errors.New(
      fmt.Sprintf(
        "Failed to decode JSON: %s",
        err,
      ),
    )
  }

  if jres.Error != 0 {
    return nil, errors.New(
      fmt.Sprintf(
        "Error response: %s",
        jres.Error,
      ),
    )
  }

  return &jres.Data, nil
}

func (self *Client) CreateComplex(graph *ComplexGraph) (*ComplexGraph, error) {
  payload, err := json.Marshal(graph)
  if err != nil {
    return nil, errors.New(
      fmt.Sprintf(
        "Failed to encode json data: %s", err,
      ),
    )
  }

  url := self.createURL("json/create/complex")
  res, err := http.Post(
    url,
    "application/json",
    bytes.NewReader(payload),
  )
  if err != nil {
    return nil, errors.New(
      fmt.Sprintf(
        "Failed to post to %s: %s", url, err,
      ),
    )
  }

  if res.StatusCode != 200 {
    return nil, errors.New(
      fmt.Sprintf(
        "HTTP request to %s failed with %s",
        url,
        res.Status,
      ),
    )
  }

  var jres struct {
    Location string `json:"location"`
    Error int
  }
  dec := json.NewDecoder(res.Body)
  err = dec.Decode(&jres)
  if err != nil {
    return nil, errors.New(
      fmt.Sprintf(
        "Failed to decode JSON: %s",
        err,
      ),
    )
  }

  if jres.Error != 0 {
    return nil, errors.New(
      fmt.Sprintf(
        "Error response: %s",
        jres.Error,
      ),
    )
  }

  return self.GetComplexByPath(graph.GetPath())
}

func (self *Client) GetGraph(id int) (*Graph, error) {
  // It's actually exactly the same as GetGraphByPath, but we just
  // implement this conversion for easy of use
  return self.GetGraphByPath(fmt.Sprintf("%d", id))
}

func (self *Client) GetGraphByPath(path string) (*Graph, error) {
  dec, err := self.getDecoder(fmt.Sprintf("/json/graph/%s", path))
  if err != nil {
    return nil, err
  }

  var e Graph
  err = dec.Decode(&e)
  if err != nil {
    return nil, err
  }

  return &e, nil
}

func (self *Client) GetComplex(id int) (*ComplexGraph, error){
  return self.GetComplexByPath(fmt.Sprintf("%d", id))
}

func (self *Client) GetComplexByPath(path string) (*ComplexGraph, error){
  dec, err := self.getDecoder(fmt.Sprintf("/json/complex/%s", path))
  if err != nil {
    return nil, err
  }

  var e ComplexGraph
  err = dec.Decode(&e)
  if err != nil {
    return nil, err
  }

  return &e, nil
}

func (self *Client) GetGraphList() (GraphList, error) {
  dec, err := self.getDecoder("/json/list/graph")
  if err != nil {
    return nil, err
  }

  var e GraphList
  err = dec.Decode(&e)
  if err != nil {
    return nil, err
  }

  return e, nil
}

/*

Fetches the list of ComplexGraphs registered in the GrowthForecast instance.

*/
func (self *Client) GetComplexList() (ComplexList, error) {
  dec, err := self.getDecoder("/json/list/complex")
  if err != nil {
    return nil, err
  }

  var e ComplexList
  err = dec.Decode(&e)
  if err != nil {
    return nil, err
  }

  return e, nil
}

<commit_msg>tweak docs<commit_after>package growthforecast

/*

GrowthForecast (http://kazeburo.github.io/GrowthForecast/) is a data
visualization tool. This library gives you an easy way to interact
with the GrowthForecast server

*/

import (
  "bytes"
  "encoding/json"
  "errors"
  "fmt"
  "net/http"
  "net/url"
  "strings"
)

type Client struct {
  BaseURL string
}

/*

NewClient() creates a new growthforecast.Client struct. You just need
to pass it a base URL where all API endpoints will automatically be
generated from.

    client := growthforecast.NewClient("http://gf.mycompany.com")
    g, err := client.GetGraphByPath("service/section/graph")
    if err != nil {
        log.Fatalf("Error while fetching graph: %s", err)
    }

*/
func NewClient(base string) *Client {
  return &Client { base }
}

func (self *Client) createURL(path string) string {
  if strings.HasPrefix(path, "/") {
    path = path[1:len(path)]
  }
  return fmt.Sprintf("%s/%s", self.BaseURL, path)
}

func (self *Client) getJSON(path string) (*http.Response, error) {
  url := self.createURL(path)
  res, err := http.Get(url)
  if err != nil {
    return nil, err
  }

  if res.StatusCode != 200 {
    return nil, errors.New(
      fmt.Sprintf(
        "HTTP request to %s failed",
        url,
      ),
    )
  }

  return res, nil
}

func (self *Client) getDecoder(path string) (*json.Decoder, error) {
  res, err := self.getJSON(path)
  if err != nil {
    return nil, err
  }

  return json.NewDecoder(res.Body), nil
}

/*

Sends a request to the GrowthForecast server to create a graph.
Returns the new Graph created by this operation. This return value is
useful to grab server generated parameters such as the grah ID.

*/
func (self *Client) CreateGraph(graph *Graph) (*Graph, error) {
  values := url.Values{
    "number": {fmt.Sprintf("%d",graph.Number)},
  }
  if graph.Mode != "" {
    values.Add("mode", graph.Mode)
  }
  if graph.Color != "" {
    values.Add("color", graph.Color)
  }
  url := self.createURL(fmt.Sprintf("api/%s", graph.GetPath()))
  res, err := http.PostForm(url, values)
  if err != nil {
    return nil, err
  }

  if res.StatusCode != 200 {
    return nil, errors.New(
      fmt.Sprintf(
        "HTTP request to %s failed",
        url,
      ),
    )
  }

  var jres struct {
    Data Graph      `json:"data"`
    Error int       `json:"error"`
  }
  dec := json.NewDecoder(res.Body)
  err = dec.Decode(&jres)
  if err != nil {
    return nil, errors.New(
      fmt.Sprintf(
        "Failed to decode JSON: %s",
        err,
      ),
    )
  }

  if jres.Error != 0 {
    return nil, errors.New(
      fmt.Sprintf(
        "Error response: %s",
        jres.Error,
      ),
    )
  }

  return &jres.Data, nil
}

func (self *Client) CreateComplex(graph *ComplexGraph) (*ComplexGraph, error) {
  payload, err := json.Marshal(graph)
  if err != nil {
    return nil, errors.New(
      fmt.Sprintf(
        "Failed to encode json data: %s", err,
      ),
    )
  }

  url := self.createURL("json/create/complex")
  res, err := http.Post(
    url,
    "application/json",
    bytes.NewReader(payload),
  )
  if err != nil {
    return nil, errors.New(
      fmt.Sprintf(
        "Failed to post to %s: %s", url, err,
      ),
    )
  }

  if res.StatusCode != 200 {
    return nil, errors.New(
      fmt.Sprintf(
        "HTTP request to %s failed with %s",
        url,
        res.Status,
      ),
    )
  }

  var jres struct {
    Location string `json:"location"`
    Error int
  }
  dec := json.NewDecoder(res.Body)
  err = dec.Decode(&jres)
  if err != nil {
    return nil, errors.New(
      fmt.Sprintf(
        "Failed to decode JSON: %s",
        err,
      ),
    )
  }

  if jres.Error != 0 {
    return nil, errors.New(
      fmt.Sprintf(
        "Error response: %s",
        jres.Error,
      ),
    )
  }

  return self.GetComplexByPath(graph.GetPath())
}

func (self *Client) GetGraph(id int) (*Graph, error) {
  // It's actually exactly the same as GetGraphByPath, but we just
  // implement this conversion for easy of use
  return self.GetGraphByPath(fmt.Sprintf("%d", id))
}

func (self *Client) GetGraphByPath(path string) (*Graph, error) {
  dec, err := self.getDecoder(fmt.Sprintf("/json/graph/%s", path))
  if err != nil {
    return nil, err
  }

  var e Graph
  err = dec.Decode(&e)
  if err != nil {
    return nil, err
  }

  return &e, nil
}

func (self *Client) GetComplex(id int) (*ComplexGraph, error){
  return self.GetComplexByPath(fmt.Sprintf("%d", id))
}

func (self *Client) GetComplexByPath(path string) (*ComplexGraph, error){
  dec, err := self.getDecoder(fmt.Sprintf("/json/complex/%s", path))
  if err != nil {
    return nil, err
  }

  var e ComplexGraph
  err = dec.Decode(&e)
  if err != nil {
    return nil, err
  }

  return &e, nil
}

func (self *Client) GetGraphList() (GraphList, error) {
  dec, err := self.getDecoder("/json/list/graph")
  if err != nil {
    return nil, err
  }

  var e GraphList
  err = dec.Decode(&e)
  if err != nil {
    return nil, err
  }

  return e, nil
}

/*

Fetches the list of ComplexGraphs registered in the GrowthForecast instance.

*/
func (self *Client) GetComplexList() (ComplexList, error) {
  dec, err := self.getDecoder("/json/list/complex")
  if err != nil {
    return nil, err
  }

  var e ComplexList
  err = dec.Decode(&e)
  if err != nil {
    return nil, err
  }

  return e, nil
}

<|endoftext|>