1
0
mirror of https://github.com/taigrr/wtf synced 2025-01-18 04:03:14 -08:00
wtf/modules/hibp/client.go
Chris Cummer cde904ff08
Use errcheck to find unhandled errors (#795)
Signed-off-by: Chris Cummer <chriscummer@me.com>
2019-12-17 08:26:16 -08:00

144 lines
3.0 KiB
Go

package hibp
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"time"
)
const (
apiURL = "https://haveibeenpwned.com/api/v3/breachedaccount/"
clientTimeoutSecs = 2
userAgent = "WTFUtil"
)
type hibpError struct {
StatusCode int `json:"statusCode"`
Message string `json:"message"`
}
func (widget *Widget) fullURL(account string, truncated bool) string {
truncStr := "false"
if truncated {
truncStr = "true"
}
return apiURL + account + fmt.Sprintf("?truncateResponse=%s", truncStr)
}
func (widget *Widget) fetchForAccount(account string, since string) (*Status, error) {
if account == "" {
return nil, nil
}
hibpClient := http.Client{
Timeout: time.Second * clientTimeoutSecs,
}
asTruncated := true
if since != "" {
asTruncated = false
}
request, err := http.NewRequest(http.MethodGet, widget.fullURL(account, asTruncated), nil)
if err != nil {
return nil, err
}
request.Header.Set("User-Agent", userAgent)
request.Header.Set("hibp-api-key", widget.settings.apiKey)
response, getErr := hibpClient.Do(request)
if getErr != nil {
return nil, err
}
body, readErr := ioutil.ReadAll(response.Body)
if readErr != nil {
return nil, err
}
hibpErr := widget.validateHTTPResponse(response.StatusCode, body)
if hibpErr != nil {
return nil, errors.New(hibpErr.Message)
}
stat, err := widget.parseResponseBody(account, body)
if err != nil {
return nil, err
}
return stat, nil
}
func (widget *Widget) parseResponseBody(account string, body []byte) (*Status, error) {
breaches := []Breach{}
stat := NewStatus(account, breaches)
if len(body) == 0 {
// If the body is empty then there's no breaches
return stat, nil
}
jsonErr := json.Unmarshal(body, &breaches)
if jsonErr != nil {
return stat, jsonErr
}
breaches = widget.filterBreaches(breaches)
stat.Breaches = breaches
return stat, nil
}
func (widget *Widget) filterBreaches(breaches []Breach) []Breach {
// If there's no valid since value in the settings, there's no point in trying to filter
// the breaches on that value, they'll all pass
if !widget.settings.HasSince() {
return breaches
}
sinceDate, err := widget.settings.SinceDate()
if err != nil {
return breaches
}
latestBreaches := []Breach{}
for _, breach := range breaches {
breachDate, err := breach.BreachDate()
if err != nil {
// Append the erring breach here because a failing breach date doesn't mean that
// the breach itself isn't applicable. The date could be missing or malformed,
// in which case we err on the side of caution and assume that the breach is valid
latestBreaches = append(latestBreaches, breach)
continue
}
if breachDate.After(sinceDate) {
latestBreaches = append(latestBreaches, breach)
}
}
return latestBreaches
}
func (widget *Widget) validateHTTPResponse(responseCode int, body []byte) *hibpError {
hibpErr := &hibpError{}
switch responseCode {
case 401, 402:
err := json.Unmarshal(body, hibpErr)
if err != nil {
return nil
}
default:
hibpErr = nil
}
return hibpErr
}