summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortjpcc <tjp@ctrl-c.club>2023-10-30 10:24:01 -0600
committertjpcc <tjp@ctrl-c.club>2023-10-30 10:24:01 -0600
commit96577f2367b7b02941f991f57125281a9a447c51 (patch)
treeccc74451ad378ce4be816cb4b55f7fef3bca3fbd
parent0a7e966d5a093e8c2d3b3834d25feb93f5fca156 (diff)
support uploads in sliderule.Client and sw-fetch tool
-rw-r--r--client.go19
-rw-r--r--tools/sw-fetch/main.go34
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
+}