package fetchguard_test import ( "context" "errors" "fmt" "io" "net/http" "net/netip" "strings" "time" "git.golem15.com/golem15/summercms/modules/fetchguard" ) func ExampleFetch() { ctx := context.Background() // Only the application's image host, at most 5 MiB within 5 seconds. images := fetchguard.Policy{ Mode: fetchguard.AllowHostsMode, AllowHosts: []string{"images.example.com"}, MaxBytes: 5 << 20, Timeout: 5 * time.Second, } // Any public host, for a URL a user pasted. public := fetchguard.Policy{Mode: fetchguard.PublicOnlyMode} for _, c := range []struct { url string policy fetchguard.Policy }{ {"http://images.example.com/cover.jpg", images}, {"https://cdn.attacker.example/cover.jpg", images}, {"https://127.0.0.1/admin", public}, {"https://169.254.169.254/latest/meta-data/", public}, {"https://[::ffff:10.0.0.1]/", public}, {"https://%zz", public}, } { // The last argument is the application's config (app.Config), for // limits the policy leaves at zero; nil uses the framework defaults. _, err := fetchguard.Fetch(ctx, c.url, c.policy, nil) var fe *fetchguard.Error if errors.As(err, &fe) { fmt.Println(fe.Reason, c.url) } } fmt.Println(fetchguard.Defaults()) // Output: // scheme http://images.example.com/cover.jpg // invalid_url https://cdn.attacker.example/cover.jpg // private_ip https://127.0.0.1/admin // private_ip https://169.254.169.254/latest/meta-data/ // private_ip https://[::ffff:10.0.0.1]/ // invalid_url https://%zz // 10485760 10s } // stubVendor stands in for a vendor API in the example; tests use the tide // upstream fake the same way. type stubVendor struct{} func (stubVendor) RoundTrip(r *http.Request) (*http.Response, error) { body := `{"id":42}` if r.Header.Get("Authorization") != "Bearer example-token" { body = `{"error":"unauthorized"}` } return &http.Response{ StatusCode: http.StatusCreated, Header: http.Header{"Content-Type": {"application/json"}}, Body: io.NopCloser(strings.NewReader(body)), Request: r, }, nil } func ExampleClient_PostJSON() { client, err := fetchguard.NewClient(fetchguard.Policy{ Mode: fetchguard.AllowHostsMode, AllowHosts: []string{"api.example.com"}, Timeout: 10 * time.Second, }, nil) if err != nil { panic(err) } header := http.Header{} header.Set("Authorization", fetchguard.Bearer("example-token")) // Production code passes its own context; the example routes the call to // a stub instead of the network. ctx := fetchguard.WithTransport(context.Background(), stubVendor{}) res, err := client.PostJSON(ctx, "https://api.example.com/v1/items", header, map[string]string{"name": "widget"}) if err != nil { panic(err) } fmt.Println(res.StatusCode, string(res.Body)) // Output: // 201 {"id":42} } func ExampleIsPrivateAddr() { for _, ip := range []string{"8.8.8.8", "10.0.0.7", "::ffff:127.0.0.1"} { fmt.Println(ip, fetchguard.IsPrivateAddr(netip.MustParseAddr(ip))) } // Output: // 8.8.8.8 false // 10.0.0.7 true // ::ffff:127.0.0.1 true }