]> Sergey Matveev's repositories - paster.git/commitdiff
Netstrings are simpler than bencode v2.0.0
authorSergey Matveev <stargrave@stargrave.org>
Wed, 15 Feb 2023 10:31:11 +0000 (13:31 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Wed, 15 Feb 2023 10:31:11 +0000 (13:31 +0300)
13 files changed:
contrib/clean.sh
contrib/paster-tls-gnutls.sh [moved from contrib/paster-tls-gnutls with 85% similarity]
contrib/paster-tls-ucspi.sh [moved from contrib/paster-tls-ucspi with 90% similarity]
contrib/paster-ucspi.sh [moved from contrib/paster-ucspi with 87% similarity]
contrib/paster.sh [moved from contrib/paster with 51% similarity]
contrib/paster.tcl
contrib/paster.zsh
doc/features.texi
doc/install.texi
doc/protocol.texi
go.mod
go.sum [new file with mode: 0644]
main.go

index a9148a9ae7c187bbbe66a2241474fd7326f41918..452c04063a6022d3cadaeb6b6603087778556cc7 100755 (executable)
@@ -1,6 +1,6 @@
-#!/bin/sh
+#!/bin/sh -e
 
-find . -type f -mtime +1 \
+exec find . -type f -mtime +1 \
     -and -not -name index.html \
     -and -not -name "asciinema-player-*" \
     -delete
similarity index 85%
rename from contrib/paster-tls-gnutls
rename to contrib/paster-tls-gnutls.sh
index e091bfd734e12cf5daf39910df5b8b7670eb0674..b916998a9e11baa00a2e72c3a79be5c9cc1f4c26 100755 (executable)
@@ -1,2 +1,2 @@
-#!/bin/sh
+#!/bin/sh -e
 DST="gnutls-cli --logfile=/dev/null -p 2021 paster.example.com" paster $@
similarity index 90%
rename from contrib/paster-tls-ucspi
rename to contrib/paster-tls-ucspi.sh
index 0ee162a790d7e3e32bc5a72b16f4202aed7fd768..fdf7a80fd6825d1a1cc19dbcfba2bbd5e5250e52 100755 (executable)
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/sh -e
 HOST=paster.example.com
 DST=cat paster $@ |
 tcpclient -DHR -l 0 $HOST 2021 tlsc -name $HOST sh -c "cat >&7 ; cat <&6"
similarity index 87%
rename from contrib/paster-ucspi
rename to contrib/paster-ucspi.sh
index 8ab1070a623af8b90f105d4d7adc310b87d2e7be..cd5d12d8b4e230695b4b7afe52a523c28f4042ee 100755 (executable)
@@ -1,3 +1,3 @@
-#!/bin/sh
+#!/bin/sh -e
 DST=cat paster $@ |
 tcpclient -DHR -l 0 paster.example.com 2020 sh -c "cat >&7 ; cat <&6"
similarity index 51%
rename from contrib/paster
rename to contrib/paster.sh
index 64f8c79aee9dc2617c17b3b8611525b8ab05bcb9..bac1926b0da2c728f4c66be573e6b9b1cb51b4d4 100755 (executable)
@@ -1,15 +1,16 @@
 #!/bin/sh -e
 DST=${DST:-nc paster.example.com 2020}
+ext="0:,"
 if [ -s "$1" ]; then
     src="$1"
     bn="${1##*/}"
     _ext="${bn##*.}"
-    [ "$bn" = "$_ext" ] || ext="1:e${#_ext}:$_ext"
+    [ "$bn" = "$_ext" ] || ext="${#_ext}:$_ext,"
 else
     src=`mktemp`
     trap "rm -f $src" HUP PIPE INT QUIT TERM EXIT
     cat > $src
-    [ $# -eq 0 ] || ext="1:e${#1}:$1"
+    [ $# -eq 0 ] || ext="${#1}:$1,"
 fi
-size=`perl -e 'print -s $ARGV[0]' $src`
-( echo -n "d${ext}1:v${size}:" ; cat $src ; echo -n e ) | $DST
+size=$(perl -e 'print -s $ARGV[0]' "$src")
+( echo -n "${ext}${size}:" ; cat "$src" ; echo -n , ) | $DST
index 7ef8f0b159b03064c3389b34c33124aa9d560d4a..ad7c4eff984fa1d5e9b8f6d26dcca44d1670ab31 100755 (executable)
@@ -4,10 +4,11 @@ set Host paster.example.com
 set Port 2021
 set GnuTLS 1
 
+set ext ""
 set fn [lindex $argv 0]
 set size 0
 if {($argc > 0) && [file exists $fn]} {
-    set ext [file extension $fn]
+    set ext [string trimleft [file extension $fn] .]
     set fd [open $fn {RDONLY BINARY}]
     set size [file size $fn]
 } else {
@@ -23,19 +24,16 @@ if $GnuTLS {
     set sock [socket $Host $Port]
     chan configure $sock -encoding binary -translation binary
 }
-puts -nonewline $sock "d"
-if {[info exists ext]} {
-    set ext [string trimleft $ext .]
-    puts -nonewline $sock [string cat "1:e" [string length $ext] ":$ext"]
-}
-puts -nonewline $sock "1:v$size:"
+
+puts -nonewline $sock [string cat [string length $ext] ":$ext,"]
+puts -nonewline $sock "$size:"
 if {[info exists fd]} {
     fcopy $fd $sock
     close $fd
 } {
     puts -nonewline $sock $data
 }
-puts -nonewline $sock "e"
+puts -nonewline $sock ","
 flush $sock
 while {[gets $sock line] >= 0} { puts $line }
 close $sock
index a4850d574ad47537d819555e540a4bcefc819c27..76f1464697ee567553b46c09e2dd64ebdfa1a408 100755 (executable)
@@ -3,16 +3,17 @@
 set -e
 
 DST=${DST:-paster.example.com 2020}
+ext="0:,"
 [[ -s "$1" ]] && {
     src="$1"
     bn=$src:t
     e=${bn##*.}
-    [[ $bn = $e ]] || ext="1:e${#e}:$e"
+    [[ $bn = $e ]] || ext="${#e}:$e,"
 } || {
     src=`mktemp`
     trap "rm -f $src" HUP PIPE INT QUIT TERM EXIT
     cat > $src
-    [[ $# -eq 0 ]] || ext="1:e${#1}:$1"
+    [[ $# -eq 0 ]] || ext="${#1}:$1,"
 }
 zmodload -F zsh/stat b:zstat
 size=`zstat +size $src`
@@ -20,6 +21,6 @@ size=`zstat +size $src`
 zmodload zsh/net/tcp
 ztcp ${=DST}
 fd=$REPLY
-( print -n "d${ext}1:v${size}:" ; cat $src ; print -n e ) >&$fd
+( print -n "${ext}${size}:" ; cat $src ; print -n , ) >&$fd
 cat <&$fd
 ztcp -c $fd
index 56bcfa6774c240b431015d1d75cf900efc62a706..ffd0539007e52e39f6e55636516936570daf72a8 100644 (file)
@@ -12,8 +12,8 @@
     with varying @code{Content-Types}. You can share images for example,
     not only plaintext
 
-@item No excessive HTTP protocol: just send
-    @url{https://en.wikipedia.org/wiki/Bencode, bencode}-ed dictionary
+@item No excessive HTTP protocol: just send two
+    @url{https://en.wikipedia.org/wiki/Netstring, netstring}-encoded strings
     with the data over the TCP
 
 @item Newline is appended for @file{.txt}/@file{.url} pastes, if it is missing
index 8083cb3443090a69573a8b7740b86e5100168d18..8d62e6b96c85fb8340de5ad00952700a9c3a9da7 100644 (file)
@@ -7,7 +7,7 @@
 Install paster itself:
 
 @example
-$ go get go.stargrave.org/paster
+$ go get go.stargrave.org/paster/v2
 @end example
 
 If you have got problems with your trust anchors, unwilling to
index 491cc790508088412c979c3e14cdf2a4d598b6a6..e1c0acc5553bad9cda348bcc3cb841b14a04d9bd 100644 (file)
@@ -1,16 +1,12 @@
 @node Protocol
 @unnumbered Protocol
 
-Protocol is very simple: @url{https://en.wikipedia.org/wiki/Bencode, bencode}d
-dictionary is sent over TCP.
-
-@itemize
-@item @code{v} key contains the data you want to paste
-@item optional @code{e} key, holding the desired filename extension,
-    without the leading dot, up to 9 characters long
-@end itemize
+Protocol is very simple: two
+@url{https://en.wikipedia.org/wiki/Netstring, netstring}s are sent over TCP.
+First one holds desired filename extension, without the leading dot, up to 9
+characters long. Second one is the paste data itself.
 
 @example
-"hello world" => d1:v11:hello worlde
-"http://example.com/" and "url" extension => d1:e3:url1:v18:http://example.come
+  "hello world", no explicit extension => 0:,11:hello world,
+"http://example.com/", "url" extension => 3:url,18:http://example.com,
 @end example
diff --git a/go.mod b/go.mod
index af8f1e53ffacca55c578f95ab005e34fdbd51a32..4dbc7c13a1407b4c3ffcdbef9bd7e091ca88e38d 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,5 @@
-module go.stargrave.org/paster
+module go.stargrave.org/paster/v2
 
-go 1.16
+go 1.17
+
+require go.cypherpunks.ru/netstring/v2 v2.4.0
diff --git a/go.sum b/go.sum
new file mode 100644 (file)
index 0000000..5044963
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,2 @@
+go.cypherpunks.ru/netstring/v2 v2.4.0 h1:qBOtHJj1hoCUpYkouuTurXl20R1IKnEkh+q7/J0TgZ4=
+go.cypherpunks.ru/netstring/v2 v2.4.0/go.mod h1:6YDx4gW414SmHdvSBMKbHaB2/7w9WZ04NQb7XIUV/pA=
diff --git a/main.go b/main.go
index 912408b035f2b885f3d4998750d00d27d9849737..25e93d4f45ad03d34809e6b1f153b6673b3e6124 100644 (file)
--- a/main.go
+++ b/main.go
@@ -30,9 +30,12 @@ import (
        "html/template"
        "io"
        "os"
-       "strconv"
+
+       "go.cypherpunks.ru/netstring/v2"
 )
 
+const MaxExtLen = 9
+
 var (
        //go:embed asciicast.tmpl
        ASCIICastHTMLTmplRaw string
@@ -74,7 +77,7 @@ func main() {
        maxSize := flag.Uint64("max-size", 1<<20, "Maximal upload size")
        asciicastPath := flag.String("asciicast-path", "", "Generate HTMLs for .cast asciicasts, specify \"asciinema-player-v2.6.1\"")
        flag.Usage = func() {
-               fmt.Fprintf(os.Stderr, "Usage: paster [options] URL [...]\n")
+               fmt.Fprintf(os.Stderr, "Usage: paster [options] URL [URL ...]\n")
                flag.PrintDefaults()
        }
        flag.Parse()
@@ -82,114 +85,81 @@ func main() {
                flag.Usage()
                os.Exit(1)
        }
-       var fn string
-       r := bufio.NewReader(os.Stdin)
-       b, err := r.ReadByte()
+       r := netstring.NewReader(os.Stdin)
+       size, err := r.Next()
        if err != nil {
                fatal(err.Error())
        }
-       if b != 'd' {
-               fatal("bad bencode: no dictionary start")
+       if size > MaxExtLen {
+               fatal("too long extension length")
        }
-       buf := make([]byte, 21)
-       ext := ".txt"
-       var size uint64
-AnotherKey:
-       if _, err = io.ReadFull(r, buf[:3]); err != nil {
+       data, err := io.ReadAll(r)
+       if err != nil {
                fatal(err.Error())
        }
-       switch s := string(buf[:3]); s {
-       case "1:e":
-               if _, err = io.ReadFull(r, buf[:2]); err != nil {
-                       fatal(err.Error())
-               }
-               if buf[1] != ':' {
-                       fatal(`bad bencode: invalid "e" format`)
-               }
-               extLen, err := strconv.Atoi(string(buf[:1]))
-               if err != nil {
-                       fatal(err.Error())
-               }
-               if _, err = io.ReadFull(r, buf[:extLen]); err != nil {
-                       fatal(err.Error())
-               }
-               ext = "." + string(buf[:extLen])
-               goto AnotherKey
-       case "1:v":
-               n, err := r.Read(buf)
-               if err != nil {
-                       fatal(err.Error())
-               }
-               i := bytes.IndexByte(buf[:n], ':')
-               if i == -1 {
-                       fatal(`bad bencode: invalid "v" length`)
-               }
-               size, err = strconv.ParseUint(string(buf[:i]), 10, 64)
-               if err != nil {
-                       fatal(err.Error())
-               }
-               if size == 0 {
-                       fatal("empty paste")
-               }
-               if size > *maxSize {
-                       fatal("too big")
-               }
-               buf = buf[i+1 : n]
-       default:
-               fatal("bad bencode: invalid key")
+       var ext string
+       if len(data) == 0 {
+               ext = ".txt"
+       } else {
+               ext = "." + string(data)
+       }
+       size, err = r.Next()
+       if err != nil {
+               fatal(err.Error())
+       }
+       if size == 0 {
+               fatal("empty paste")
+       }
+       if size > *maxSize {
+               fatal("too big")
        }
        rnd := make([]byte, 12)
        if _, err = io.ReadFull(rand.Reader, rnd); err != nil {
                fatal(err.Error())
        }
-       fn = "." + base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(rnd) + ext
+       fn := "." + base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(rnd) + ext
        fd, err := os.OpenFile(fn, os.O_RDWR|os.O_CREATE|os.O_EXCL, os.FileMode(0666))
        if err != nil {
                fatal(err.Error())
        }
        h := sha512.New()
        bfd := bufio.NewWriter(fd)
-       mr := io.MultiReader(bytes.NewReader(buf), r)
        mw := io.MultiWriter(bfd, h)
-       if _, err = io.CopyN(mw, mr, int64(size-1)); err != nil {
+       buf := make([]byte, 1)
+       _, err = io.CopyN(mw, r, int64(size-1))
+       if err != nil {
                goto Failed
        }
-       if len(buf) == 0 {
-               buf = append(buf, 0)
-       } else {
-               buf = buf[:1]
-       }
-       if _, err = mr.Read(buf); err != nil {
+       _, err = r.Read(buf)
+       if err != nil {
                goto Failed
        }
-       if _, err = mw.Write(buf); err != nil {
+       _, err = mw.Write(buf)
+       if err != nil {
                goto Failed
        }
        if (ext == ".txt" || ext == ".url") && buf[0] != '\n' {
-               if err = bfd.WriteByte('\n'); err != nil {
+               err = bfd.WriteByte('\n')
+               if err != nil {
                        goto Failed
                }
        }
-       if _, err = mr.Read(buf); err != nil {
-               goto Failed
-       }
-       if buf[0] != 'e' {
-               os.Remove(fn)
-               fatal("bad bencode: no dictionary end")
-       }
-       if err = bfd.Flush(); err != nil {
+       err = bfd.Flush()
+       if err != nil {
                goto Failed
        }
-       if err = fd.Close(); err != nil {
+       err = fd.Close()
+       if err != nil {
                goto Failed
        }
-       if err = os.Rename(fn, fn[1:]); err != nil {
+       err = os.Rename(fn, fn[1:])
+       if err != nil {
                goto Failed
        }
        for _, u := range flag.Args() {
                fmt.Println(u + fn[1:])
        }
-       fmt.Println("SHA512/2:", hex.EncodeToString(h.Sum(nil)[:512/2/8]))
+       fmt.Println("SHA512/2:", hex.EncodeToString(h.Sum(nil)[:sha512.Size/2]))
        if ext == ".cast" && *asciicastPath != "" {
                if err = asciicastHTML(*asciicastPath, fn[1:]); err != nil {
                        goto Failed