hydroxide-push/protonmail/protonmail.go

170 lines
3.4 KiB
Go
Raw Normal View History

2017-08-22 01:04:16 +03:00
// Package protonmail implements a ProtonMail API client.
package protonmail
import (
"bytes"
"encoding/json"
"fmt"
2017-08-22 01:04:16 +03:00
"io"
2017-09-26 19:29:13 +03:00
"io/ioutil"
2017-08-22 01:04:16 +03:00
"net/http"
"strconv"
2017-08-22 11:36:43 +03:00
"golang.org/x/crypto/openpgp"
2019-08-31 17:34:26 +03:00
"log"
2017-08-22 01:04:16 +03:00
)
2017-08-22 11:16:20 +03:00
const Version = 3
2017-08-22 01:04:16 +03:00
const headerAPIVersion = "X-Pm-Apiversion"
type resp struct {
Code int
*RawAPIError
2017-08-22 01:04:16 +03:00
}
func (r *resp) Err() error {
if err := r.RawAPIError; err != nil {
return &APIError{
2018-10-21 13:15:20 +03:00
Code: r.Code,
2018-01-09 23:58:42 +02:00
Message: err.Message,
}
2017-08-22 01:04:16 +03:00
}
return nil
}
type maybeError interface {
Err() error
}
type RawAPIError struct {
2017-08-22 01:04:16 +03:00
Message string `json:"Error"`
}
type APIError struct {
2018-10-21 13:15:20 +03:00
Code int
2018-01-09 23:58:42 +02:00
Message string
}
func (err *APIError) Error() string {
2018-01-09 23:58:42 +02:00
return fmt.Sprintf("[%v] %v", err.Code, err.Message)
2017-08-22 01:04:16 +03:00
}
// Client is a ProtonMail API client.
type Client struct {
2017-08-22 11:16:20 +03:00
RootURL string
2017-08-22 01:04:16 +03:00
AppVersion string
2017-08-22 11:16:20 +03:00
HTTPClient *http.Client
2017-09-26 19:29:13 +03:00
ReAuth func() error
2017-08-22 11:36:43 +03:00
2017-08-22 14:22:27 +03:00
uid string
2017-08-22 11:36:43 +03:00
accessToken string
2017-08-22 14:22:27 +03:00
keyRing openpgp.EntityList
2017-08-22 01:04:16 +03:00
}
2017-09-26 19:29:13 +03:00
func (c *Client) setRequestAuthorization(req *http.Request) {
if c.uid != "" && c.accessToken != "" {
req.Header.Set("X-Pm-Uid", c.uid)
req.Header.Set("Authorization", "Bearer "+c.accessToken)
}
}
2017-08-22 01:04:16 +03:00
func (c *Client) newRequest(method, path string, body io.Reader) (*http.Request, error) {
2017-08-22 11:16:20 +03:00
req, err := http.NewRequest(method, c.RootURL+path, body)
2017-08-22 01:04:16 +03:00
if err != nil {
return nil, err
}
2019-08-31 17:34:26 +03:00
//log.Printf(">> %v %v\n", method, path)
2017-08-22 01:04:16 +03:00
req.Header.Set("X-Pm-Appversion", c.AppVersion)
req.Header.Set(headerAPIVersion, strconv.Itoa(Version))
2017-09-26 19:29:13 +03:00
c.setRequestAuthorization(req)
2017-08-22 01:04:16 +03:00
return req, nil
}
func (c *Client) newJSONRequest(method, path string, body interface{}) (*http.Request, error) {
2017-09-26 19:29:13 +03:00
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(body); err != nil {
2017-08-22 01:04:16 +03:00
return nil, err
}
2017-09-26 19:29:13 +03:00
b := buf.Bytes()
2017-12-02 17:23:06 +02:00
//log.Printf(">> %v %v\n%v", method, path, string(b))
2017-09-26 19:29:13 +03:00
req, err := c.newRequest(method, path, bytes.NewReader(b))
2017-08-22 10:41:47 +03:00
if err != nil {
return nil, err
}
2017-09-26 19:29:13 +03:00
2017-08-22 10:41:47 +03:00
req.Header.Set("Content-Type", "application/json")
2017-09-26 19:29:13 +03:00
req.GetBody = func() (io.ReadCloser, error) {
return ioutil.NopCloser(bytes.NewReader(b)), nil
}
2017-08-22 10:41:47 +03:00
return req, nil
2017-08-22 01:04:16 +03:00
}
func (c *Client) do(req *http.Request) (*http.Response, error) {
httpClient := c.HTTPClient
if httpClient == nil {
httpClient = http.DefaultClient
}
2017-09-26 19:29:13 +03:00
resp, err := httpClient.Do(req)
if err != nil {
return resp, err
}
// Check if access token has expired
_, hasAuth := req.Header["Authorization"]
canRetry := req.Body == nil || req.GetBody != nil
if resp.StatusCode == http.StatusUnauthorized && hasAuth && c.ReAuth != nil && canRetry {
resp.Body.Close()
c.accessToken = ""
if err := c.ReAuth(); err != nil {
return resp, err
}
c.setRequestAuthorization(req) // Access token has changed
if req.Body != nil {
body, err := req.GetBody()
if err != nil {
return resp, err
}
req.Body = body
}
return c.do(req)
}
return resp, nil
2017-08-22 01:04:16 +03:00
}
func (c *Client) doJSON(req *http.Request, respData interface{}) error {
2017-08-22 10:41:47 +03:00
req.Header.Set("Accept", "application/json")
2017-08-22 12:19:21 +03:00
if respData == nil {
respData = new(resp)
}
2017-08-22 01:04:16 +03:00
resp, err := c.do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if err := json.NewDecoder(resp.Body).Decode(respData); err != nil {
return err
}
//log.Printf("<< %v %v\n%#v", req.Method, req.URL.Path, respData)
2017-08-22 01:04:16 +03:00
if maybeError, ok := respData.(maybeError); ok {
if err := maybeError.Err(); err != nil {
2019-08-31 17:34:26 +03:00
log.Printf("request failed: %v %v: %v", req.Method, req.URL.String(), err)
2017-08-22 01:04:16 +03:00
return err
}
}
return nil
}