// Copyright (c) 2021 Proton Technologies AG // // This file is part of ProtonMail Bridge. // // ProtonMail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // ProtonMail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . package pmapi import ( "context" "net/http" "sync" "time" "github.com/ProtonMail/gopenpgp/v2/crypto" "github.com/go-resty/resty/v2" "github.com/pkg/errors" ) // client is a client of the protonmail API. It implements the Client interface. type client struct { req requester uid, acc, ref string authHandlers []AuthHandler authLocker sync.RWMutex user *User addresses AddressList userKeyRing *crypto.KeyRing addrKeyRing map[string]*crypto.KeyRing keyRingLock sync.Locker exp time.Time } func newClient(req requester, uid string) *client { return &client{ req: req, uid: uid, addrKeyRing: make(map[string]*crypto.KeyRing), keyRingLock: &sync.RWMutex{}, } } func (c *client) withAuth(acc, ref string, exp time.Time) *client { c.acc = acc c.ref = ref c.exp = exp return c } func (c *client) r(ctx context.Context) (*resty.Request, error) { r := c.req.r(ctx) if c.uid != "" { r.SetHeader("x-pm-uid", c.uid) } if time.Now().After(c.exp) { if err := c.authRefresh(ctx); err != nil { return nil, err } } c.authLocker.RLock() defer c.authLocker.RUnlock() if c.acc != "" { r.SetAuthToken(c.acc) } return r, nil } func (c *client) do(ctx context.Context, fn func(*resty.Request) (*resty.Response, error)) (*resty.Response, error) { r, err := c.r(ctx) if err != nil { return nil, err } res, err := wrapRestyError(fn(r)) if err != nil { if res.StatusCode() != http.StatusUnauthorized { return nil, err } if err := c.authRefresh(ctx); err != nil { return nil, err } return wrapRestyError(fn(r)) } return res, nil } func wrapRestyError(res *resty.Response, err error) (*resty.Response, error) { if err, ok := err.(*resty.ResponseError); ok { return res, err } if res.RawResponse != nil { return res, err } return res, errors.Wrap(ErrNoConnection, err.Error()) }