From 96577f2367b7b02941f991f57125281a9a447c51 Mon Sep 17 00:00:00 2001 From: tjpcc Date: Mon, 30 Oct 2023 10:24:01 -0600 Subject: support uploads in sliderule.Client and sw-fetch tool --- client.go | 19 +++++++++++++++++-- tools/sw-fetch/main.go | 34 +++++++++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/client.go b/client.go index 0a25110..145b2f8 100644 --- a/client.go +++ b/client.go @@ -4,6 +4,7 @@ import ( "crypto/tls" "errors" "fmt" + "io" "net/http" neturl "net/url" @@ -93,6 +94,20 @@ func (c Client) Fetch(url string) (*Response, error) { return nil, ExceededMaxRedirects } +// Upload sends a request with a body and returns any redirect response. +func (c Client) Upload(url string, contents *io.LimitedReader) (*Response, error) { + u, err := neturl.Parse(url) + if err != nil { + return nil, err + } + switch u.Scheme { + case "spartan", "http", "https": + return c.RoundTrip(&types.Request{URL: u, Meta: contents}) + default: + return nil, fmt.Errorf("upload not supported on %s", u.Scheme) + } +} + func getRedirectLocation(proto string, meta any) string { switch proto { case "gemini", "spartan": @@ -103,12 +118,12 @@ func getRedirectLocation(proto string, meta any) string { return "" } -type httpClient struct{ +type httpClient struct { tp *http.Transport } func (hc httpClient) RoundTrip(request *Request) (*Response, error) { - hreq, err := http.NewRequest("GET", request.URL.String(), nil) + hreq, err := http.NewRequest("GET", request.URL.String(), request.Meta.(*io.LimitedReader)) if err != nil { return nil, err } diff --git a/tools/sw-fetch/main.go b/tools/sw-fetch/main.go index ccf8ac8..cf37e50 100644 --- a/tools/sw-fetch/main.go +++ b/tools/sw-fetch/main.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "crypto/tls" "fmt" "io" @@ -15,7 +16,7 @@ const usage = `Resource fetcher for the small web. Usage: sw-fetch (-h | --help) - sw-fetch [-v | --verbose] [-o PATH | --output PATH] [-k | --keyfile PATH] [ -c | --certfile PATH ] [ -s | --skip-verify ] URL + sw-fetch [-v | --verbose] [-o PATH | --output PATH] [-k | --keyfile PATH] [ -c | --certfile PATH ] [ -s | --skip-verify ] [ -u | --upload ] URL Options: -h --help Show this screen. @@ -24,13 +25,26 @@ Options: -k --keyfile PATH Path to the TLS key file to use. -c --certfile PATH Path to the TLS certificate file to use. -s --skip-verify Don't verify server TLS certificates. + -u --upload Use stdin as the request body on supported protocols and don't follow redirects. ` func main() { conf := configure() cl := sliderule.NewClient(conf.clientTLS) - response, err := cl.Fetch(conf.url.String()) + var response *sliderule.Response + var err error + + if conf.upload { + body, er := stdinContents() + if er != nil { + err = er + } else { + response, err = cl.Upload(conf.url.String(), body) + } + } else { + response, err = cl.Fetch(conf.url.String()) + } if err != nil { fail(err.Error() + "\n") } @@ -44,6 +58,7 @@ func main() { type config struct { verbose bool + upload bool output io.WriteCloser url *url.URL clientTLS *tls.Config @@ -98,6 +113,8 @@ func configure() config { cert = os.Args[i] case "-s", "--skip-verify": verify = false + case "-u", "--upload": + conf.upload = true } } @@ -108,7 +125,7 @@ func configure() config { } tlsConf, err := gemini.FileTLS(cert, key) if err != nil { - failf("failed to load TLS key pair") + failf("failed to load TLS key pair: %s", err.Error()) } conf.clientTLS = tlsConf } @@ -132,3 +149,14 @@ func failf(msg string, args ...any) { fmt.Fprintf(os.Stderr, msg, args...) os.Exit(1) } + +func stdinContents() (*io.LimitedReader, error) { + contents, err := io.ReadAll(os.Stdin) + if err != nil { + return nil, err + } + return &io.LimitedReader{ + R: bytes.NewBuffer(contents), + N: int64(len(contents)), + }, nil +} -- cgit v1.2.3