forked from gopasspw/gopass
/
client.go
78 lines (64 loc) · 1.45 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package api
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"strconv"
"strings"
"time"
"github.com/gopasspw/gopass/pkg/out"
"github.com/cenkalti/backoff"
"github.com/pkg/errors"
)
// URL is the HIBPv2 API URL
var URL = "https://api.pwnedpasswords.com"
// Lookup performs a lookup against the HIBP v2 API
func Lookup(ctx context.Context, shaSum string) (uint64, error) {
if len(shaSum) != 40 {
return 0, errors.Errorf("invalid shasum")
}
shaSum = strings.ToUpper(shaSum)
prefix := shaSum[:5]
suffix := shaSum[5:]
var count uint64
url := fmt.Sprintf("%s/range/%s", URL, prefix)
op := func() error {
out.Debug(ctx, "[%s] HTTP Request: %s", shaSum, url)
resp, err := http.Get(url)
if err != nil {
return err
}
defer func() {
_ = resp.Body.Close()
}()
if resp.StatusCode == http.StatusNotFound {
return nil
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("HTTP request failed: %s %s", resp.Status, body)
}
for _, line := range strings.Split(string(body), "\n") {
line = strings.TrimSpace(line)
if len(line) < 37 {
continue
}
if line[:35] != suffix {
continue
}
if iv, err := strconv.ParseUint(line[36:], 10, 64); err == nil {
count = iv
return nil
}
}
return nil
}
bo := backoff.NewExponentialBackOff()
bo.MaxElapsedTime = 10 * time.Second
err := backoff.Retry(op, bo)
return count, err
}