// Copyright 2026 Rose Hogenson // // This file is part of roseh.moe. // // roseh.moe is free software: you can redistribute it and/or modify it // under the terms of the GNU Affero General Public License as published // by the Free Software Foundation, either version 3 of the License, // or (at your option) any later version. // // roseh.moe is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public // License along with roseh.moe. If not, see // . package hole import ( "archive/tar" "cmp" "compress/gzip" "crypto/hkdf" "crypto/rand" "crypto/sha256" "encoding/binary" "errors" "fmt" "io" "io/fs" "log" "mime/multipart" "os" "strings" "time" "roseh.moe/pkg/oae2" "roseh.moe/pkg/wordlist" ) var HoleTempDir 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 Open(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 writeHoleFileToTarFile(tarWriter *tar.Writer, hole string, now time.Time) error { fileName, f, err := Open(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 Upload(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 := WriteSingleFile(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 } func WriteSingleFile(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 Clean() 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 Init() error { if err := os.MkdirAll(HoleTempDir, 0700); err != nil { return err } return nil }