File: get.go
   1 /*
   2 The MIT License (MIT)
   3 
   4 Copyright (c) 2026 pacman64
   5 
   6 Permission is hereby granted, free of charge, to any person obtaining a copy of
   7 this software and associated documentation files (the "Software"), to deal
   8 in the Software without restriction, including without limitation the rights to
   9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  10 of the Software, and to permit persons to whom the Software is furnished to do
  11 so, subject to the following conditions:
  12 
  13 The above copyright notice and this permission notice shall be included in all
  14 copies or substantial portions of the Software.
  15 
  16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22 SOFTWARE.
  23 */
  24 
  25 /*
  26 To compile a smaller-sized command-line app, you can use the `go` command as
  27 follows:
  28 
  29 go build -ldflags "-s -w" -trimpath get.go
  30 */
  31 
  32 package main
  33 
  34 import (
  35     "bufio"
  36     "encoding/base64"
  37     "errors"
  38     "io"
  39     "net/http"
  40     "os"
  41     "strings"
  42 )
  43 
  44 const info = `
  45 get [files/URIs...]
  46 
  47 Load all files and/or web-resources given.
  48 `
  49 
  50 func main() {
  51     if len(os.Args) > 1 {
  52         switch os.Args[1] {
  53         case `-h`, `--h`, `-help`, `--help`:
  54             os.Stdout.WriteString(info[1:])
  55             return
  56         }
  57     }
  58 
  59     if err := run(os.Args[1:]); err != nil {
  60         os.Stderr.WriteString(err.Error())
  61         os.Stderr.WriteString("\n")
  62         os.Exit(1)
  63     }
  64 }
  65 
  66 func run(names []string) error {
  67     w := bufio.NewWriter(os.Stdout)
  68     defer w.Flush()
  69 
  70     dashes := 0
  71     for _, s := range names {
  72         if s == `-` {
  73             dashes++
  74         }
  75     }
  76 
  77     var stdin []byte
  78     gotStdin := false
  79 
  80     for _, name := range names {
  81         w.Flush()
  82 
  83         // handle reading from stdin, keeping its data for reuse if needed
  84         if name == `-` {
  85             if dashes > 1 {
  86                 if !gotStdin {
  87                     stdin, _ = io.ReadAll(os.Stdin)
  88                     gotStdin = true
  89                 }
  90 
  91                 n, err := w.Write(stdin)
  92                 if n < len(stdin) || err != nil {
  93                     return nil
  94                 }
  95                 continue
  96             }
  97 
  98             _, err := io.Copy(os.Stdout, os.Stdin)
  99             if err != nil {
 100                 return nil
 101             }
 102             continue
 103         }
 104 
 105         // handle data-URIs
 106         if seemsDataURI(name) {
 107             if err := handleDataURI(w, name); err != nil {
 108                 return err
 109             }
 110             continue
 111         }
 112 
 113         // handle web-requests and actual files
 114         handle := handleFile
 115         if seemsURI(name) {
 116             handle = handleURI
 117         }
 118         if err := handle(w, name); err != nil {
 119             return err
 120         }
 121     }
 122 
 123     // use stdin when no filenames were given
 124     if len(names) == 0 {
 125         io.Copy(os.Stdout, os.Stdin)
 126     }
 127     return nil
 128 }
 129 
 130 // handleDataURI decodes base64 explicitly, so decoding errors can be told
 131 // apart from output-writing ones
 132 func handleDataURI(w *bufio.Writer, data string) error {
 133     i := strings.Index(data, `;base64,`)
 134     if i < 0 {
 135         return errors.New(`invalid data URI`)
 136     }
 137     r := strings.NewReader(data[i+len(`;base64,`):])
 138     dec := base64.NewDecoder(base64.StdEncoding, r)
 139 
 140     var buf [32 * 1024]byte
 141 
 142     for {
 143         n, err := dec.Read(buf[:])
 144         if n < 1 && err == io.EOF {
 145             return nil
 146         }
 147         if err != nil {
 148             return err
 149         }
 150 
 151         _, err = w.Write(buf[:n])
 152         if err != nil {
 153             return io.EOF
 154         }
 155     }
 156 }
 157 
 158 func handleFile(w *bufio.Writer, path string) error {
 159     f, err := os.Open(path)
 160     if err != nil {
 161         return err
 162     }
 163     defer f.Close()
 164 
 165     _, err = io.Copy(w, f)
 166     if err != nil {
 167         return io.EOF
 168     }
 169     return nil
 170 }
 171 
 172 func handleURI(w *bufio.Writer, uri string) error {
 173     r, err := http.Get(uri)
 174     if err != nil {
 175         return err
 176     }
 177     defer r.Body.Close()
 178 
 179     _, err = io.Copy(w, r.Body)
 180     if err != nil {
 181         return io.EOF
 182     }
 183     return nil
 184 }
 185 
 186 func seemsDataURI(s string) bool {
 187     start := s
 188     if len(s) > 64 {
 189         start = s[:64]
 190     }
 191     return strings.HasPrefix(s, `data:`) && strings.Contains(start, `;base64,`)
 192 }
 193 
 194 func seemsURI(s string) bool {
 195     return false ||
 196         strings.HasPrefix(s, `https://`) ||
 197         strings.HasPrefix(s, `http://`)
 198 }