package main import ( "bytes" "crypto/tls" "fmt" "io" "net/url" "os" "tildegit.org/tjp/sliderule" "tildegit.org/tjp/sliderule/gemini" ) 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 ] [ -u | --upload ] URL Options: -h --help Show this screen. -v --verbose Display more diagnostic information on standard error. -o --output PATH Send the fetched resource to PATH instead of standard out. -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) 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") } defer func() { _ = response.Close() _ = conf.output.Close() }() _, _ = io.Copy(conf.output, response.Body) } type config struct { verbose bool upload bool output io.WriteCloser url *url.URL clientTLS *tls.Config } func configure() config { if len(os.Args) == 1 { fail(usage) } conf := config{output: os.Stdout} key := "" cert := "" verify := true for i := 1; i <= len(os.Args)-1; i += 1 { switch os.Args[i] { case "-h", "--help": os.Stdout.WriteString(usage) os.Exit(0) case "-v", "--verbose": conf.verbose = true case "-o", "--output": if i+1 == len(os.Args)-1 { fail(usage) } out := os.Args[i+1] if out != "-" { output, err := os.OpenFile(out, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) if err != nil { fmt.Println(err.Error()) failf("'%s' is not a valid path\n", out) } conf.output = output } i += 1 case "-k", "--keyfile": if i+1 == len(os.Args)-1 { fail(usage) } i += 1 key = os.Args[i] case "-c", "--certfile": if i+1 == len(os.Args)-1 { fail(usage) } i += 1 cert = os.Args[i] case "-s", "--skip-verify": verify = false case "-u", "--upload": conf.upload = true } } conf.clientTLS = &tls.Config{} if key != "" || cert != "" { if key == "" || cert == "" { fail("-k|--keyfile and -c|--certfile must both be present, or neither\n") } tlsConf, err := gemini.FileTLS(cert, key) if err != nil { failf("failed to load TLS key pair: %s", err.Error()) } conf.clientTLS = tlsConf } conf.clientTLS.InsecureSkipVerify = !verify u, err := url.Parse(os.Args[len(os.Args)-1]) if err != nil || u.Scheme == "" { fail(usage) } conf.url = u return conf } func fail(msg string) { os.Stderr.WriteString(msg) os.Exit(1) } 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 }