summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2026-10-20 21:11:38 -0700
committerRose Hogenson <rosehogenson@posteo.net>2026-10-20 21:35:55 -0700
commit066d51fd620742b766cfe85fbd114f51f573d150 (patch)
tree9af45e3c83c9610edcc5891048d3a500bc14ba26
parentSome small simplifications (diff)
downloadroseh.moe-066d51fd620742b766cfe85fbd114f51f573d150.tar.zst
Move wormhole related functions into a package
-rw-r--r--go.mod2
-rw-r--r--roseh.moe.go262
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)
}
}