Files
summercms/modules/fetchguard/example_test.go
Jakub Zych e6a67134d1 feat(14-01): fetchguard client covers PUT, multipart, bearer and a trusted mode
- TrustedMode (declared after PublicOnlyMode) lifts the scheme, host and dial checks for Client only
- PutJSON, PostMultipart with FormField/FormFile, Bearer
- tests for modes, redirects, multipart order, body cap and the scheme guard
- README, root modules row and outbound HTTP docs describe the client and its test seam
2026-10-03 19:42:37 +02:00

97 lines
2.8 KiB
Go

package fetchguard_test
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"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}
}