116 lines
2.5 KiB
Go
116 lines
2.5 KiB
Go
package hibp
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
packageVersion = "0.0.1"
|
|
backendURL = "https://api.pwnedpasswords.com"
|
|
userAgent = "pwned-passwords-golang/" + packageVersion
|
|
)
|
|
|
|
// Client holds a connection to the HIBP API.
|
|
type Client struct {
|
|
client *http.Client
|
|
AppID string
|
|
UserAgent string
|
|
BackendURL *url.URL
|
|
|
|
// Services used for communicating with the API.
|
|
Pwned *PwnedService
|
|
Cache *CacheService
|
|
}
|
|
|
|
type service struct {
|
|
client *Client
|
|
}
|
|
|
|
// NewClient creates a new Client with the appropriate connection details and
|
|
// services used for communicating with the API.
|
|
func NewClient() *Client {
|
|
// Init new http.Client.
|
|
httpClient := http.DefaultClient
|
|
|
|
// Parse BE URL.
|
|
baseURL, _ := url.Parse(backendURL)
|
|
|
|
c := &Client{
|
|
client: httpClient,
|
|
BackendURL: baseURL,
|
|
UserAgent: userAgent,
|
|
}
|
|
|
|
c.Pwned = &PwnedService{client: c}
|
|
c.Cache = &CacheService{client: c}
|
|
return c
|
|
}
|
|
|
|
// NewRequest creates an API request. A relative URL can be provided in urlPath,
|
|
// which will be resolved to the BackendURL of the Client.
|
|
func (c *Client) NewRequest(method, urlPath string, body interface{}) (*http.Request, error) {
|
|
// Parse our URL.
|
|
rel, err := url.Parse(urlPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Resolve to absolute URI.
|
|
u := c.BackendURL.ResolveReference(rel)
|
|
|
|
buf := new(bytes.Buffer)
|
|
if body != nil {
|
|
err = json.NewEncoder(buf).Encode(body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Create the request.
|
|
req, err := http.NewRequest(method, u.String(), buf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Add our packages UA.
|
|
req.Header.Add("User-Agent", c.UserAgent)
|
|
|
|
return req, nil
|
|
}
|
|
|
|
// Do sends an API request and returns the API response.
|
|
func (c *Client) Do(req *http.Request) ([]string, error) {
|
|
resp, err := c.client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer func() {
|
|
if rerr := resp.Body.Close(); err == nil {
|
|
err = rerr
|
|
}
|
|
}()
|
|
|
|
// Error if anything else but 200.
|
|
// The API should always return a 200 (unless something is wrong) as per
|
|
// https://haveibeenpwned.com/API/v2#SearchingPwnedPasswordsByRange
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("Unexpected API response status: %v", resp.StatusCode)
|
|
}
|
|
|
|
// Parse our resp.Body.
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Response is returned as new-line'd string, split and return.
|
|
return strings.Split(string(body), "\r\n"), err
|
|
}
|