func relativeURL(base, new string) (*url.URL, error) {
	if new == "" {
		return nil, errgo.Newf("empty URL")
	}
	baseURL, err := url.Parse(base)
	if err != nil {
		return nil, errgo.Notef(err, "cannot parse URL")
	}
	newURL, err := url.Parse(new)
	if err != nil {
		return nil, errgo.Notef(err, "cannot parse URL")
	}
	return baseURL.ResolveReference(newURL), nil
}
relativeURL returns newPath relative to an original URL.
package httpbakery
import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"strings"
	"time"
	"golang.org/x/net/context"
	"golang.org/x/net/context/ctxhttp"
	"golang.org/x/net/publicsuffix"
	"gopkg.in/errgo.v1"
	"gopkg.in/httprequest.v1"
	"gopkg.in/macaroon.v2"
	"gopkg.in/macaroon-bakery.v2/bakery"
	"gopkg.in/macaroon-bakery.v2/bakery/checkers"
)
var unmarshalError = httprequest.ErrorUnmarshaler(&Error{})
const maxDischargeRetries = 3
type DischargeError struct {
	Reason *Error
}
func (e *DischargeError) Error() string {
	return fmt.Sprintf("third party refused discharge: %v", e.Reason)
}
func IsDischargeError(err error) bool {
	_, ok := err.(*DischargeError)
	return ok
}
type InteractionError struct {
	Reason error
}
func (e *InteractionError) Error() string {
	return fmt.Sprintf("cannot start interactive session: %v", e.Reason)
}
func IsInteractionError(err error) bool {
	_, ok := err.(*InteractionError)
	return ok
}
func NewHTTPClient() *http.Client {
	c := *http.DefaultClient
	c.CheckRedirect = func(req *http.Request, via []*http.Request) error {
		if len(via) >= 10 {
			return fmt.Errorf("too many redirects")
		}
		if len(via) == 0 {
			return nil
		}
		for attr, val := range via[0].Header {
			if attr == "Cookie" {
				continue
			}
			if _, ok := req.Header[attr]; !ok {
				req.Header[attr] = val
			}
		}
		return nil
	}
	jar, err := cookiejar.New(&cookiejar.Options{
		PublicSuffixList: publicsuffix.List,
	})
	if err != nil {
		panic(err)
	}
	c.Jar = jar
	return &c
}
type Client struct {
	*http.Client
	InteractionMethods []Interactor
	Key *bakery.KeyPair
	Logger bakery.Logger
}
type Interactor interface {
	Kind() string
	Interact(ctx context.Context, client *Client, location string, interactionRequiredErr *Error) (*DischargeToken, error)
}
type DischargeToken struct {
	Kind string `json:"kind"`
	Value []byte `json:"value"`
}
type LegacyInteractor interface {
	LegacyInteract(ctx context.Context, client *Client, location string, visitURL *url.URL) error
}
func NewClient() *Client {
	return &Client{
		Client: NewHTTPClient(),
	}
}
func (c *Client) AddInteractor(i Interactor) {
	c.InteractionMethods = append(c.InteractionMethods, i)
}
func (c *Client) DischargeAll(ctx context.Context, m *bakery.Macaroon) (macaroon.Slice, error) {
	return bakery.DischargeAllWithKey(ctx, m, c.AcquireDischarge, c.Key)
}
func (c *Client) DischargeAllUnbound(ctx context.Context, ms bakery.Slice) (bakery.Slice, error) {
	return ms.DischargeAll(ctx, c.AcquireDischarge, c.Key)
}
func (c *Client) Do(req *http.Request) (*http.Response, error) {
	return c.do(contextFromRequest(req), req, nil)
}
func (c *Client) DoWithContext(ctx context.Context, req *http.Request) (*http.Response, error) {
	return c.do(ctx, req, nil)
}
func (c *Client) DoWithCustomError(req *http.Request, getError func(resp *http.Response) error) (*http.Response, error) {
	return c.do(contextFromRequest(req), req, getError)
}
func (c *Client) do(ctx context.Context, req *http.Request, getError func(resp *http.Response) error) (*http.Response, error) {
	c.logDebugf(ctx, "client do %s %s {", req.Method, req.URL)
	resp, err := c.do1(ctx, req, getError)
	c.logDebugf(ctx, "} -> error %#v", err)
	return resp, err
}
func (c *Client) do1(ctx context.Context, req *http.Request, getError func(resp *http.Response) error) (*http.Response, error) {
	if getError == nil {
		getError = DefaultGetError
	}
	if c.Client.Jar == nil {
		return nil, errgo.New("no cookie jar supplied in HTTP client")
	}
	rreq, ok := newRetryableRequest(c.Client, req)
	if !ok {
		return nil, fmt.Errorf("request body is not seekable")
	}
	defer rreq.close()
	req.Header.Set(BakeryProtocolHeader, fmt.Sprint(bakery.LatestVersion))
	retry := 0
	for {
		resp, err := c.do2(ctx, rreq, getError)
		if err == nil || !isDischargeRequiredError(err) {
			return resp, errgo.Mask(err, errgo.Any)
		}
		if retry++; retry > maxDischargeRetries {
			return nil, errgo.NoteMask(err, fmt.Sprintf("too many (%d) discharge requests", retry-1), errgo.Any)
		}
		if err1 := c.HandleError(ctx, req.URL, err); err1 != nil {
			return nil, errgo.Mask(err1, errgo.Any)
		}
		c.logDebugf(ctx, "discharge succeeded; retry %d", retry)
	}
}
func (c *Client) do2(ctx context.Context, rreq *retryableRequest, getError func(resp *http.Response) error) (*http.Response, error) {
	httpResp, err := rreq.do(ctx)
	if err != nil {
		return nil, errgo.Mask(err, errgo.Any)
	}
	err = getError(httpResp)
	if err == nil {
		c.logInfof(ctx, "HTTP response OK (status %v)", httpResp.Status)
		return httpResp, nil
	}
	httpResp.Body.Close()
	return nil, errgo.Mask(err, errgo.Any)
}
func (c *Client) HandleError(ctx context.Context, reqURL *url.URL, err error) error {
	respErr, ok := errgo.Cause(err).(*Error)
	if !ok {
		return err
	}
	if respErr.Code != ErrDischargeRequired {
		return respErr
	}
	if respErr.Info == nil || respErr.Info.Macaroon == nil {
		return errgo.New("no macaroon found in discharge-required response")
	}
	mac := respErr.Info.Macaroon
	macaroons, err := bakery.DischargeAllWithKey(ctx, mac, c.AcquireDischarge, c.Key)
	if err != nil {
		return errgo.Mask(err, errgo.Any)
	}
	var cookiePath string
	if path := respErr.Info.MacaroonPath; path != "" {
		relURL, err := parseURLPath(path)
		if err != nil {
			c.logInfof(ctx, "ignoring invalid path in discharge-required response: %v", err)
		} else {
			cookiePath = reqURL.ResolveReference(relURL).Path
		}
	}
	cookie, err := NewCookie(nil, macaroons)
	if err != nil {
		return errgo.Notef(err, "cannot make cookie")
	}
	cookie.Path = cookiePath
	if name := respErr.Info.CookieNameSuffix; name != "" {
		cookie.Name = "macaroon-" + name
	}
	c.Jar.SetCookies(reqURL, []*http.Cookie{cookie})
	return nil
}
func DefaultGetError(httpResp *http.Response) error {
	if httpResp.StatusCode != http.StatusProxyAuthRequired && httpResp.StatusCode != http.StatusUnauthorized {
		return nil
	}
	if httpResp.StatusCode == http.StatusUnauthorized && httpResp.Header.Get("WWW-Authenticate") != "Macaroon" {
		return nil
	}
	if httpResp.Header.Get("Content-Type") != "application/json" {
		return nil
	}
	var resp Error
	if err := json.NewDecoder(httpResp.Body).Decode(&resp); err != nil {
		return fmt.Errorf("cannot unmarshal error response: %v", err)
	}
	return &resp
}
func parseURLPath(path string) (*url.URL, error) {
	u, err := url.Parse(path)
	if err != nil {
		return nil, errgo.Mask(err)
	}
	if u.Scheme != "" ||
		u.Opaque != "" ||
		u.User != nil ||
		u.Host != "" ||
		u.RawQuery != "" ||
		u.Fragment != "" {
		return nil, errgo.Newf("URL path %q is not clean", path)
	}
	return u, nil
}
const PermanentExpiryDuration = 100 * 365 * 24 * time.Hour
func NewCookie(ns *checkers.Namespace, ms macaroon.Slice) (*http.Cookie, error) {
	if len(ms) == 0 {
		return nil, errgo.New("no macaroons in cookie")
	}
	data, err := json.Marshal(ms)
	if err != nil {
		return nil, errgo.Notef(err, "cannot marshal macaroons")
	}
	cookie := &http.Cookie{
		Name:  fmt.Sprintf("macaroon-%x", ms[0].Signature()),
		Value: base64.StdEncoding.EncodeToString(data),
	}
	expires, found := checkers.MacaroonsExpiryTime(ns, ms)
	if !found {
		expires = time.Now().Add(PermanentExpiryDuration)
	} else if expires.Sub(time.Now()) < time.Minute {
		expires = time.Time{}
	}
	cookie.Expires = expires
	return cookie, nil
}
func SetCookie(jar http.CookieJar, url *url.URL, ns *checkers.Namespace, ms macaroon.Slice) error {
	cookie, err := NewCookie(ns, ms)
	if err != nil {
		return errgo.Mask(err)
	}
	jar.SetCookies(url, []*http.Cookie{cookie})
	return nil
}
func MacaroonsForURL(jar http.CookieJar, u *url.URL) []macaroon.Slice {
	return cookiesToMacaroons(jar.Cookies(u))
}
func appendURLElem(u, elem string) string {
	if strings.HasSuffix(u, "/") {
		return u + elem
	}
	return u + "/" + elem
}
func (c *Client) AcquireDischarge(ctx context.Context, cav macaroon.Caveat, payload []byte) (*bakery.Macaroon, error) {
	m, err := c.acquireDischarge(ctx, cav, payload, nil)
	if err == nil {
		return m, nil
	}
	cause, ok := errgo.Cause(err).(*Error)
	if !ok {
		return nil, errgo.NoteMask(err, "cannot acquire discharge", IsInteractionError)
	}
	if cause.Code != ErrInteractionRequired {
		return nil, &DischargeError{
			Reason: cause,
		}
	}
	if cause.Info == nil {
		return nil, errgo.Notef(err, "interaction-required response with no info")
	}
	loc := appendURLElem(cav.Location, "")
	token, m, err := c.interact(ctx, loc, cause, payload)
	if err != nil {
		return nil, errgo.Mask(err, IsDischargeError, IsInteractionError)
	}
	if m != nil {
		return m, nil
	}
	m, err = c.acquireDischarge(ctx, cav, payload, token)
	if err != nil {
		return nil, errgo.Mask(err, IsDischargeError, IsInteractionError)
	}
	return m, nil
}
func (c *Client) acquireDischarge(
	ctx context.Context,
	cav macaroon.Caveat,
	payload []byte,
	token *DischargeToken,
) (*bakery.Macaroon, error) {
	dclient := newDischargeClient(cav.Location, c)
	var req dischargeRequest
	req.Id, req.Id64 = maybeBase64Encode(cav.Id)
	if token != nil {
		req.Token, req.Token64 = maybeBase64Encode(token.Value)
		req.TokenKind = token.Kind
	}
	req.Caveat = base64.RawURLEncoding.EncodeToString(payload)
	resp, err := dclient.Discharge(ctx, &req)
	if err == nil {
		return resp.Macaroon, nil
	}
	return nil, errgo.Mask(err, errgo.Any)
}
func (c *Client) interact(ctx context.Context, location string, irErr *Error, payload []byte) (*DischargeToken, *bakery.Macaroon, error) {
	if len(c.InteractionMethods) == 0 {
		return nil, nil, &InteractionError{
			Reason: errgo.New("interaction required but not possible"),
		}
	}
	if irErr.Info.InteractionMethods == nil && irErr.Info.LegacyVisitURL != "" {
		m, err := c.legacyInteract(ctx, location, irErr)
		if err != nil {
			return nil, nil, errgo.Mask(err, IsDischargeError, IsInteractionError)
		}
		return nil, m, nil
	}
	for _, interactor := range c.InteractionMethods {
		c.logDebugf(ctx, "checking interaction method %q", interactor.Kind())
		if _, ok := irErr.Info.InteractionMethods[interactor.Kind()]; ok {
			c.logDebugf(ctx, "found possible interaction method %q", interactor.Kind())
			token, err := interactor.Interact(ctx, c, location, irErr)
			if err != nil {
				if errgo.Cause(err) == ErrInteractionMethodNotFound {
					continue
				}
				return nil, nil, errgo.Mask(err, IsDischargeError, IsInteractionError)
			}
			if token == nil {
				return nil, nil, errgo.New("interaction method returned an empty token")
			}
			return token, nil, nil
		} else {
			c.logDebugf(ctx, "interaction method %q not found in %#v", interactor.Kind(), irErr.Info.InteractionMethods)
		}
	}
	return nil, nil, &InteractionError{
		Reason: errgo.Newf("no supported interaction method"),
	}
}
func (c *Client) legacyInteract(ctx context.Context, location string, irErr *Error) (*bakery.Macaroon, error) {
	visitURL, err := relativeURL(location, irErr.Info.LegacyVisitURL)
	if err != nil {
		return nil, errgo.Mask(err)
	}
	waitURL, err := relativeURL(location, irErr.Info.LegacyWaitURL)
	if err != nil {
		return nil, errgo.Mask(err)
	}
	methodURLs := map[string]*url.URL{
		"interactive": visitURL,
	}
	if len(c.InteractionMethods) > 1 || c.InteractionMethods[0].Kind() != WebBrowserInteractionKind {
		methodURLs = legacyGetInteractionMethods(ctx, c.logger(), c, visitURL)
	}
	for _, interactor := range c.InteractionMethods {
		kind := interactor.Kind()
		if kind == WebBrowserInteractionKind {
			kind = "interactive"
		}
		interactor, ok := interactor.(LegacyInteractor)
		if !ok {
			continue
		}
		visitURL, ok := methodURLs[kind]
		if !ok {
			continue
		}
		visitURL, err := relativeURL(location, visitURL.String())
		if err != nil {
			return nil, errgo.Mask(err)
		}
		if err := interactor.LegacyInteract(ctx, c, location, visitURL); err != nil {
			return nil, &InteractionError{
				Reason: errgo.Mask(err, errgo.Any),
			}
		}
		return waitForMacaroon(ctx, c, waitURL)
	}
	return nil, &InteractionError{
		Reason: errgo.Newf("no methods supported"),
	}
}
func (c *Client) logDebugf(ctx context.Context, f string, a ...interface{}) {
	c.logger().Debugf(ctx, f, a...)
}
func (c *Client) logInfof(ctx context.Context, f string, a ...interface{}) {
	c.logger().Infof(ctx, f, a...)
}
func (c *Client) logger() bakery.Logger {
	if c.Logger != nil {
		return c.Logger
	}
	return bakery.DefaultLogger("httpbakery")
}
func waitForMacaroon(ctx context.Context, client *Client, waitURL *url.URL) (*bakery.Macaroon, error) {
	httpResp, err := ctxhttp.Get(ctx, client.Client, waitURL.String())
	if err != nil {
		return nil, errgo.Notef(err, "cannot get %q", waitURL)
	}
	defer httpResp.Body.Close()
	if httpResp.StatusCode != http.StatusOK {
		err := unmarshalError(httpResp)
		if err1, ok := err.(*Error); ok {
			err = &DischargeError{
				Reason: err1,
			}
		}
		return nil, errgo.NoteMask(err, "failed to acquire macaroon after waiting", errgo.Any)
	}
	var resp WaitResponse
	if err := httprequest.UnmarshalJSONResponse(httpResp, &resp); err != nil {
		return nil, errgo.Notef(err, "cannot unmarshal wait response")
	}
	return resp.Macaroon, nil
}