Something went wrong. Try again.
Maps Linux distribution source packages to their upstream repositories, and through them to each other
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118package fetch
import ( "bytes" "context" "crypto/sha256" "encoding/json" "fmt" "io" "net/http" neturl "net/url" "strconv" "strings" "time")
// Some openSUSE hosts (code.opensuse.org, and the web routes of src.opensuse.org) answer every// request with a 403 page until the client pays a small proof-of-work, as their page script// does in a browser: fetch a challenge {t, r, s} from powPath; for t=1 find the smallest n// such that hex(sha256(r+n)) starts with "0000", for t=0 wait three seconds; POST "r-s-n"// back. The answer sets a cookie (for the whole opensuse.org domain) that admits later// requests, so the cost is paid about once per process.
// powPath is the challenge endpoint; its presence in a 403 body identifies the check.const powPath = "/cdn-cgi/challenge-platform/challenge"
// powMaxTries bounds the search; the expected cost of a 16-bit prefix is 65536 hashes.const powMaxTries = 1 << 26
func isPoWChallenge(status int, body []byte) bool { return status == http.StatusForbidden && bytes.Contains(body, []byte(powPath))}
// solvePoWNonce returns the smallest n such that sha256(r+n) begins with two zero bytes// (the hex prefix "0000").func solvePoWNonce(r string) (int, bool) { for n := 0; n < powMaxTries; n++ { sum := sha256.Sum256([]byte(r + strconv.Itoa(n))) if sum[0] == 0 && sum[1] == 0 { return n, true } } return 0, false}
// payPoW answers the challenge of origin (scheme://host). The cookie lands in c.HTTP.Jar.func (c *Client) payPoW(ctx context.Context, origin string) error { c.powMu.Lock() defer c.powMu.Unlock() if c.HTTP.Jar == nil { return fmt.Errorf("no cookie jar to keep the answer in") } req, err := http.NewRequestWithContext(ctx, http.MethodGet, origin+powPath, nil) if err != nil { return err } req.Header.Set("User-Agent", c.UserAgent) resp, err := c.HTTP.Do(req) if err != nil { return err } b, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) resp.Body.Close() if err != nil { return err } var ch struct { T int `json:"t"` R string `json:"r"` S string `json:"s"` } if err := json.Unmarshal(b, &ch); err != nil { return fmt.Errorf("challenge: %w", err) } n := 0 switch ch.T { case 0: select { case <-ctx.Done(): return ctx.Err() case <-time.After(3 * time.Second): } case 1: var ok bool if n, ok = solvePoWNonce(ch.R); !ok { return fmt.Errorf("no nonce within %d tries", powMaxTries) } default: return fmt.Errorf("unknown challenge type %d", ch.T) } answer := ch.R + "-" + ch.S + "-" + strconv.Itoa(n) req, err = http.NewRequestWithContext(ctx, http.MethodPost, origin+powPath, strings.NewReader(answer)) if err != nil { return err } req.Header.Set("User-Agent", c.UserAgent) req.Header.Set("Content-Type", "text/plain") resp, err = c.HTTP.Do(req) if err != nil { return err } io.Copy(io.Discard, resp.Body) resp.Body.Close() if resp.StatusCode/100 != 2 { return fmt.Errorf("answer rejected: HTTP %d", resp.StatusCode) } c.logf("fetch: paid the proof-of-work challenge of %s (%d hashes)", origin, n+1) return nil}
func origin(rawURL string) string { u, err := neturl.Parse(rawURL) if err != nil { return "" } return u.Scheme + "://" + u.Host}