From 066d51fd620742b766cfe85fbd114f51f573d150 Mon Sep 17 00:00:00 2001 From: Rose Hogenson Date: Tue, 20 Oct 2026 21:11:38 -0700 Subject: Move wormhole related functions into a package --- go.mod | 2 +- roseh.moe.go | 262 ++++------------------------------------------------------- 2 files changed, 17 insertions(+), 247 deletions(-) diff --git a/go.mod b/go.mod index 033c5b1..3120707 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module roseh.moe/pkg/roseh.moe -go 1.26.0 +go 1.27 require ( github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e diff --git a/roseh.moe.go b/roseh.moe.go index 3b9a54d..e8fb802 100644 --- a/roseh.moe.go +++ b/roseh.moe.go @@ -19,14 +19,11 @@ package main import ( - "archive/tar" "bytes" - "cmp" "compress/gzip" "context" "crypto" "crypto/ecdsa" - "crypto/hkdf" "crypto/hmac" "crypto/rand" "crypto/rsa" @@ -35,16 +32,14 @@ import ( "embed" "encoding/base64" "encoding/binary" - "encoding/json" + "encoding/json/v2" "encoding/xml" - "errors" "flag" "fmt" "html/template" "io" "io/fs" "log" - "mime/multipart" "net/http" "net/http/cgi" "net/http/httputil" @@ -60,7 +55,7 @@ import ( "unicode" "github.com/skip2/go-qrcode" - "roseh.moe/pkg/oae2" + "roseh.moe/pkg/roseh.moe/internal/hole" "roseh.moe/pkg/wordlist" ) @@ -70,13 +65,16 @@ var ( configFile = flag.String("config", "config.xml", "configuration file (XML, baby!!)") https = flag.String("https", "", "directory containing cert.pem and key.pem, or empty string to use unencrypted http") httpRedirect = flag.String("http-redirect", "", "port to serve HTTP redirect") - holeTempDir = flag.String("hole-temp-dir", "/tmp/hole", "directory to store temporary files for the wormhole") nezuko = flag.String("nezuko", "/mnt/nezuko", "nezuko partition") cgit = flag.String("cgit", "", "directory containing cgit.cgi") cgitConfig = flag.String("cgit-config", "", "cgit config file") v = flag.Bool("v", true, "verbose") ) +func init() { + flag.StringVar(&hole.HoleTempDir, "hole-temp-dir", "/tmp/hole", "directory to store temporary files for the wormhole") +} + type urlValue struct { URL *url.URL } @@ -433,118 +431,15 @@ type wormholeTemplateArgs struct { Err string } -func makeHole() string { - const nWords = 10 - buf := make([]byte, 2*nWords) - rand.Read(buf) - words := make([]string, nWords) - for i := range words { - words[i] = wordlist.Words[binary.NativeEndian.Uint16(buf[2*i:])&0x1fff] - } - return strings.Join(words, "-") -} - -const blockSize = 4 * 1024 * 1024 - -type holeFileWriter struct { - *oae2.Writer - f *os.File -} - -func (w *holeFileWriter) Close() error { - return cmp.Or(w.Writer.Close(), w.f.Close()) -} - -func writeHoleFile(filename string) (_ string, _ *holeFileWriter, err error) { - hole := makeHole() - primaryKey, err := hkdf.Key(sha256.New, []byte(hole), nil, "", 32) - if err != nil { - return "", nil, err - } - f, err := os.Create(fmt.Sprintf("%s/%x", *holeTempDir, primaryKey)) - if err != nil { - return "", nil, err - } - defer func() { - if err != nil { - f.Close() - } - }() - w := oae2.NewWriter(f, []byte(hole), blockSize, nil) - buf := make([]byte, 8) - binary.BigEndian.PutUint64(buf, uint64(len(filename))) - if _, err := w.Write(buf); err != nil { - return "", nil, err - } - if _, err := io.WriteString(w, filename); err != nil { - return "", nil, err - } - return hole, &holeFileWriter{w, f}, nil -} - -type offsetReader struct { - io.ReadSeeker - offset int -} - -func (r *offsetReader) Seek(offset int64, whence int) (int64, error) { - if whence == io.SeekStart { - offset += int64(r.offset) - } - n, err := r.ReadSeeker.Seek(offset, whence) - return n - int64(r.offset), err -} - -type holeFileReader struct { - *offsetReader - f *os.File -} - -func (r *holeFileReader) Close() error { - return r.f.Close() -} - -var errNoHole = errors.New("no such hole") - -func openHoleFile(hole string) (_ string, _ *holeFileReader, err error) { - primaryKey, err := hkdf.Key(sha256.New, []byte(hole), nil, "", 32) - if err != nil { - return "", nil, err - } - f, err := os.Open(fmt.Sprintf("%s/%x", *holeTempDir, primaryKey)) - if err != nil { - if errors.Is(err, fs.ErrNotExist) { - return "", nil, errNoHole - } - return "", nil, err - } - defer func() { - if err != nil { - f.Close() - } - }() - r := oae2.NewReader(f, []byte(hole), blockSize, nil) - buf := make([]byte, 8) - if _, err := io.ReadFull(r, buf); err != nil { - return "", nil, err - } - fileNameLen := binary.BigEndian.Uint64(buf) - fileName := make([]byte, fileNameLen) - if _, err := io.ReadFull(r, fileName); err != nil { - return "", nil, err - } - return string(fileName), &holeFileReader{&offsetReader{r, 8 + int(fileNameLen)}, f}, nil -} - -func readHoleFile(w http.ResponseWriter, req *http.Request, hole string) error { - fileName, r, err := openHoleFile(hole) +func readHoleFile(w http.ResponseWriter, req *http.Request, holeName string) error { + fileName, r, err := hole.Open(holeName) if err != nil { return err } defer r.Close() w.Header().Set("Content-Disposition", "attachment; filename*=UTF-8''"+fileName) var modTime time.Time - if stat, err := r.f.Stat(); err == nil { + if stat, err := r.F.Stat(); err == nil { modTime = stat.ModTime() } http.ServeContent(w, req, fileName, modTime, r) @@ -557,83 +452,6 @@ func wormhole(w http.ResponseWriter, r *http.Request) { } } -func writeHoleFileToTarFile(tarWriter *tar.Writer, hole string, now time.Time) error { - fileName, f, err := openHoleFile(hole) - if err != nil { - return fmt.Errorf("read back hole file: %s", err) - } - defer f.Close() - size, err := f.Seek(0, io.SeekEnd) - if err != nil { - return fmt.Errorf("read back hole file: %s", err) - } - if _, err := f.Seek(0, io.SeekStart); err != nil { - return fmt.Errorf("read back hole file: %s", err) - } - if err := tarWriter.WriteHeader(&tar.Header{Name: "wormhole-files/" + fileName, Mode: 0644, Size: size, ModTime: now, Format: tar.FormatPAX}); err != nil { - return err - } - if _, err := io.Copy(tarWriter, f); err != nil { - return fmt.Errorf("write file to tar file: %s", err) - } - return nil -} - -var errNoFile = errors.New("no file") - -func uploadWormhole(reader *multipart.Reader) (string, error) { - var holes []string - for { - part, err := reader.NextPart() - if err != nil { - break - } - if part.FormName() != "file" || part.FileName() == "" { - continue - } - hole, err := writeSingleHoleFile(part.FileName(), part) - if err != nil { - return "", err - } - holes = append(holes, hole) - } - if len(holes) == 0 { - return "", errNoFile - } - if len(holes) == 1 { - return holes[0], nil - } - tarFileHole, tarHoleWriter, err := writeHoleFile("wormhole-files.tar.gz") - if err != nil { - return "", fmt.Errorf("make tar file: %s", err) - } - defer tarHoleWriter.Close() - gzipWriter, err := gzip.NewWriterLevel(tarHoleWriter, gzip.BestSpeed) - if err != nil { - return "", err - } - tarWriter := tar.NewWriter(gzipWriter) - now := time.Now() - if err := tarWriter.WriteHeader(&tar.Header{Name: "wormhole-files/", Mode: 0755, ModTime: now, Format: tar.FormatPAX}); err != nil { - return "", fmt.Errorf("create tar file main directory: %s", err) - } - for _, hole := range holes { - if err := writeHoleFileToTarFile(tarWriter, hole, now); err != nil { - return "", err - } - } - if err := tarWriter.Close(); err != nil { - return "", fmt.Errorf("write tar file: %s", err) - } - if err := gzipWriter.Close(); err != nil { - return "", fmt.Errorf("gzip tar file: %s", err) - } - if err := tarHoleWriter.Close(); err != nil { - return "", fmt.Errorf("write tar hole file: %s", err) - } - return tarFileHole, nil -} - var ( //go:embed templates/wormhole-success.html.template wormholeSuccessTemplateString string @@ -651,9 +469,9 @@ func wormholeUpload(w http.ResponseWriter, r *http.Request) { http.Error(w, fmt.Sprintf("Not a multipart/form-data request: %s", err), http.StatusBadRequest) return } - hole, err := uploadWormhole(reader) + holeName, err := hole.Upload(reader) if err != nil { - if err == errNoFile { + if err == hole.ErrNoFile { w.WriteHeader(http.StatusBadRequest) if err := wormholeTemplate.Execute(w, wormholeTemplateArgs{Err: "don't forget to pick a file"}); err != nil { log.Printf("Warning: wormholeTemplate: %s", err) @@ -664,7 +482,7 @@ func wormholeUpload(w http.ResponseWriter, r *http.Request) { http.Error(w, "Internal error", http.StatusInternalServerError) return } - downloadURL := selfURL.String() + "/wormhole/" + hole + downloadURL := selfURL.String() + "/wormhole/" + holeName if err := wormholeSuccessTemplate.Execute(w, wormholeSuccessTemplateArgs{ URL: downloadURL, QR: "/qr.png?t=" + url.QueryEscape(signMsg(downloadURL, "qr")), @@ -673,23 +491,8 @@ func wormholeUpload(w http.ResponseWriter, r *http.Request) { } } -func writeSingleHoleFile(filename string, r io.Reader) (string, error) { - hole, holeWriter, err := writeHoleFile(filename) - if err != nil { - return "", fmt.Errorf("write hole file: %s", err) - } - defer holeWriter.Close() - if _, err := io.Copy(holeWriter, r); err != nil { - return "", fmt.Errorf("write hole file: %s", err) - } - if err := holeWriter.Close(); err != nil { - return "", fmt.Errorf("write hole file: %s", err) - } - return hole, nil -} - func wormholeAPIUpload(w http.ResponseWriter, r *http.Request) { - hole, err := writeSingleHoleFile(r.PathValue("name"), r.Body) + hole, err := hole.WriteSingleFile(r.PathValue("name"), r.Body) if err != nil { log.Printf("Error: failed to write hole file: %s", err) http.Error(w, "Failed to write hole file", http.StatusInternalServerError) @@ -704,7 +507,7 @@ func wormholeAPIUpload(w http.ResponseWriter, r *http.Request) { func wormholeDownload(w http.ResponseWriter, r *http.Request) { if err := readHoleFile(w, r, r.PathValue("hole")); err != nil { - if err == errNoHole { + if err == hole.ErrNoHole { notFound(w, r) return } @@ -714,39 +517,6 @@ func wormholeDownload(w http.ResponseWriter, r *http.Request) { } } -func cleanHole() error { - log.Printf("Cleaning hole...") - holeDir, err := os.Open(*holeTempDir) - if err != nil { - return err - } - defer holeDir.Close() - deadline := time.Now().Add(-time.Hour) - for { - files, err := holeDir.ReadDir(1024) - for _, file := range files { - stat, err := file.Info() - if err != nil { - log.Printf("Error: clean hole: %s", err) - continue - } - if !stat.ModTime().Before(deadline) { - continue - } - if err := os.Remove(*holeTempDir + "/" + file.Name()); err != nil { - log.Printf("Error: clean hole: %s", err) - continue - } - } - if err != nil { - if err != io.EOF { - return err - } - return nil - } - } -} - func mac(msg []byte, purpose string) []byte { mac := hmac.New(sha256.New, secretKey) io.WriteString(mac, purpose) @@ -1460,13 +1230,13 @@ func main() { log.Fatal(err) } - if err := os.MkdirAll(*holeTempDir, 0700); err != nil { + if err := hole.Init(); err != nil { log.Fatal(err) } go func() { for range time.NewTicker(2 * time.Hour).C { - if err := cleanHole(); err != nil { + if err := hole.Clean(); err != nil { log.Printf("Error: clean hole: %s", err) } } -- cgit v1.3.1