summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2025-10-04 22:44:33 -0700
committerRose Hogenson <rosehogenson@posteo.net>2025-10-04 22:44:33 -0700
commit624985afaaa8b94e4d9685bcdc935d467f9e7060 (patch)
tree7b13e8f4ccf75b6042bebea7fb239570644a216e
parent573601b19c8ae582adcd1a7c4b8c0ebfa4295aed (diff)
downloadroseh.moe-624985afaaa8b94e4d9685bcdc935d467f9e7060.tar.zst
Allow downloading without gob
-rw-r--r--internal/api/api.go4
-rw-r--r--internal/cryptoutil/oae2.go113
-rw-r--r--roseh.moe.go147
-rw-r--r--tools/notes/notes.go6
4 files changed, 98 insertions, 172 deletions
diff --git a/internal/api/api.go b/internal/api/api.go
index 88a0011..6c9ab3a 100644
--- a/internal/api/api.go
+++ b/internal/api/api.go
@@ -38,6 +38,7 @@ type ListNotesResponse struct {
type CreateNoteRequest struct {
ContinuationToken []byte
+ FileName string
Chunk []byte
More bool
}
@@ -52,7 +53,8 @@ type ReadNoteRequest struct {
}
type ReadNoteResponseStream struct {
- Chunk []byte
+ FileName string
+ Chunk []byte
}
type DeleteNoteRequest struct{}
diff --git a/internal/cryptoutil/oae2.go b/internal/cryptoutil/oae2.go
index a230c06..bc8f53e 100644
--- a/internal/cryptoutil/oae2.go
+++ b/internal/cryptoutil/oae2.go
@@ -11,7 +11,6 @@ import (
"encoding/binary"
"errors"
"io"
- "strings"
)
// Online Authenticated Encryption from https://eprint.iacr.org/2015/189.pdf
@@ -217,77 +216,6 @@ func (w *EncryptingWriter) Close() error {
return nil
}
-func (w *EncryptingWriter) ReadFrom(r io.Reader) (int64, error) {
- if !w.initialized {
- if err := w.initialize(); err != nil {
- return 0, err
- }
- }
- if w.err != nil {
- return 0, w.err
- }
- var nn int64
- bufReader := bufio.NewReaderSize(r, 1)
- for {
- if _, err := bufReader.Peek(1); err != nil {
- if err == io.EOF {
- return nn, nil
- }
- return nn, err
- }
- if len(w.buf) == w.segmentSize {
- if err := w.writeBuf(); err != nil {
- return nn, err
- }
- }
- n, _ := io.ReadFull(bufReader, w.buf[len(w.buf):w.segmentSize])
- w.buf = w.buf[:len(w.buf)+n]
- nn += int64(n)
- }
-}
-
-func (w *EncryptingWriter) WriteByte(c byte) error {
- if !w.initialized {
- if err := w.initialize(); err != nil {
- return err
- }
- }
- if w.err != nil {
- return w.err
- }
- if len(w.buf) == w.segmentSize {
- if err := w.writeBuf(); err != nil {
- return err
- }
- }
- w.buf = append(w.buf, c)
- return nil
-}
-
-func (w *EncryptingWriter) WriteString(s string) (int, error) {
- if !w.initialized {
- if err := w.initialize(); err != nil {
- return 0, err
- }
- }
- if w.err != nil {
- return 0, w.err
- }
- r := strings.NewReader(s)
- nn := 0
- for r.Len() > 0 {
- if len(w.buf) == w.segmentSize {
- if err := w.writeBuf(); err != nil {
- return nn, err
- }
- }
- n, _ := r.Read(w.buf[len(w.buf):w.segmentSize])
- w.buf = w.buf[:len(w.buf)+n]
- nn += n
- }
- return nn, nil
-}
-
// A DecryptingReader decrypts data using the STREAM construction.
type DecryptingReader struct {
r *bufio.Reader
@@ -367,44 +295,3 @@ func (r *DecryptingReader) Read(buf []byte) (int, error) {
n, _ := r.buf.Read(buf)
return n, nil
}
-
-func (r *DecryptingReader) WriteTo(w io.Writer) (int64, error) {
- if !r.initialized {
- if err := r.initialize(); err != nil {
- return 0, err
- }
- }
- var nn int64
- if r.buf.Len() > 0 {
- n, err := w.Write(r.buf.Bytes())
- nn += int64(n)
- if err != nil {
- return nn, err
- }
- }
- for {
- if err := r.fillBuf(); err != nil {
- return nn, err
- }
- n, err := w.Write(r.buf.Bytes())
- nn += int64(n)
- if err != nil {
- return nn, err
- }
- }
-}
-
-func (r *DecryptingReader) ReadByte() (byte, error) {
- if !r.initialized {
- if err := r.initialize(); err != nil {
- return 0, err
- }
- }
- if r.buf.Len() == 0 {
- if err := r.fillBuf(); err != nil {
- return 0, err
- }
- }
- b, _ := r.buf.ReadByte()
- return b, nil
-}
diff --git a/roseh.moe.go b/roseh.moe.go
index 988938d..8c7e176 100644
--- a/roseh.moe.go
+++ b/roseh.moe.go
@@ -1,6 +1,7 @@
package main
import (
+ "bufio"
"bytes"
"crypto/rand"
"crypto/subtle"
@@ -16,6 +17,7 @@ import (
"io"
"log"
"net/http"
+ "net/url"
"os"
"path/filepath"
"strconv"
@@ -233,33 +235,6 @@ func setEncryptionKey(rawKey cryptoutil.RawKey) error {
return nil
}
-type redirectTarget int
-
-const (
- redirectDefault redirectTarget = iota
- redirectNotepad
- redirectUpload
-)
-
-func parseRedirectTarget(s string) redirectTarget {
- i, err := strconv.Atoi(s)
- if err != nil {
- return redirectDefault
- }
- return redirectTarget(i)
-}
-
-func (t redirectTarget) path() string {
- switch t {
- case redirectNotepad:
- return "/notepad"
- case redirectUpload:
- return "/notes/upload"
- default:
- return "/"
- }
-}
-
var (
//go:embed templates/login.html.template
loginString string
@@ -268,13 +243,19 @@ var (
type loginTemplateArgs struct {
Error bool
- Redirect redirectTarget
+ Redirect string
+}
+
+func executeLoginTemplate(w io.Writer, redirect string) {
+ if err := loginTemplate.Execute(w, loginTemplateArgs{Redirect: base64.RawURLEncoding.EncodeToString(secretKey.Sign([]byte(redirect), "redirect"))}); err != nil {
+ log.Printf("Warning: login: %s", err)
+ }
}
func login(w http.ResponseWriter, r *http.Request) {
rawKey, err := notepadPassword.CheckPassword(r.FormValue("password"))
if err != nil {
- if err := loginTemplate.Execute(w, loginTemplateArgs{Error: true, Redirect: parseRedirectTarget(r.FormValue("redirect"))}); err != nil {
+ if err := loginTemplate.Execute(w, loginTemplateArgs{Error: true, Redirect: r.FormValue("redirect")}); err != nil {
log.Printf("Warning: login: %s", err)
}
return
@@ -284,7 +265,13 @@ func login(w http.ResponseWriter, r *http.Request) {
return
}
attachCookie(w)
- http.Redirect(w, r, parseRedirectTarget(r.FormValue("redirect")).path(), http.StatusSeeOther)
+ redirect := "/"
+ if b, err := base64.RawURLEncoding.DecodeString(r.FormValue("redirect")); err == nil {
+ if r, ok := secretKey.Verify(b, "redirect"); ok {
+ redirect = string(r)
+ }
+ }
+ http.Redirect(w, r, redirect, http.StatusSeeOther)
}
func readNotepad(key cryptoutil.EncryptionKey) (string, error) {
@@ -314,18 +301,14 @@ type notepadTemplateArgs struct {
func notepad(w http.ResponseWriter, r *http.Request) {
csrfToken, ok := cookieAuth(w, r)
if !ok {
- if err := loginTemplate.Execute(w, loginTemplateArgs{Redirect: redirectNotepad}); err != nil {
- log.Printf("Warning: login: %s", err)
- }
+ executeLoginTemplate(w, "/notepad")
return
}
encryptionKeyMu.Lock()
key := encryptionKey
encryptionKeyMu.Unlock()
if key == nil {
- if err := loginTemplate.Execute(w, loginTemplateArgs{Redirect: redirectNotepad}); err != nil {
- log.Printf("Warning: login: %s", err)
- }
+ executeLoginTemplate(w, "/notepad")
return
}
currentContent, err := readNotepad(key)
@@ -529,6 +512,14 @@ func createNote(req *api.CreateNoteRequest) (*api.CreateNoteResponse, error) {
}
}
encryptingWriter := key.NewWriter(f, cryptoutil.WithAdditionalData([]byte("chunks/"+name)))
+ if contToken.I == 0 {
+ if _, err := encryptingWriter.Write(binary.AppendVarint(nil, int64(len(req.FileName)))); err != nil {
+ return nil, err
+ }
+ if _, err := io.WriteString(encryptingWriter, req.FileName); err != nil {
+ return nil, err
+ }
+ }
if _, err := encryptingWriter.Write(req.Chunk); err != nil {
return nil, err
}
@@ -577,13 +568,15 @@ func createNote(req *api.CreateNoteRequest) (*api.CreateNoteResponse, error) {
}
type readNoteResponseWriter struct {
- stream *encoder
+ fileName string
+ stream *encoder
}
func (w *readNoteResponseWriter) Write(buf []byte) (int, error) {
- if err := w.stream.send(&api.ReadNoteResponseStream{Chunk: buf}); err != nil {
+ if err := w.stream.send(&api.ReadNoteResponseStream{FileName: w.fileName, Chunk: buf}); err != nil {
return 0, err
}
+ w.fileName = ""
return len(buf), nil
}
@@ -602,7 +595,16 @@ func readNote(stream *encoder, req *api.ReadNoteRequest) error {
if key == nil {
return fmt.Errorf("%w: need login", api.PermissionDenied)
}
- _, err = io.Copy(&readNoteResponseWriter{stream: stream}, key.NewReader(f, cryptoutil.WithAdditionalData([]byte("notes/"+req.Note))))
+ reader := bufio.NewReader(key.NewReader(f, cryptoutil.WithAdditionalData([]byte("notes/"+req.Note))))
+ nameLen, err := binary.ReadVarint(reader)
+ if err != nil {
+ return err
+ }
+ fileName := make([]byte, nameLen)
+ if _, err := io.ReadFull(reader, fileName); err != nil {
+ return err
+ }
+ _, err = io.Copy(&readNoteResponseWriter{fileName: string(fileName), stream: stream}, reader)
return err
}
@@ -671,18 +673,14 @@ type uploadTemplateArgs struct {
func uploadPage(w http.ResponseWriter, r *http.Request) {
csrfToken, ok := cookieAuth(w, r)
if !ok {
- if err := loginTemplate.Execute(w, loginTemplateArgs{Redirect: redirectUpload}); err != nil {
- log.Printf("Warning: login: %s", err)
- }
+ executeLoginTemplate(w, "/upload")
return
}
encryptionKeyMu.Lock()
key := encryptionKey
encryptionKeyMu.Unlock()
if key == nil {
- if err := loginTemplate.Execute(w, loginTemplateArgs{Redirect: redirectUpload}); err != nil {
- log.Printf("Warning: login: %s", err)
- }
+ executeLoginTemplate(w, "/upload")
return
}
if err := uploadTemplate.Execute(w, uploadTemplateArgs{CSRFToken: csrfToken}); err != nil {
@@ -696,20 +694,14 @@ func uploadNote(w http.ResponseWriter, r *http.Request) {
return
}
if _, ok := cookieAuth(w, r); !ok {
- log.Printf("asdf: bad auth")
- if err := loginTemplate.Execute(w, loginTemplateArgs{Redirect: redirectUpload}); err != nil {
- log.Printf("Warning: login: %s", err)
- }
+ executeLoginTemplate(w, "/upload")
return
}
encryptionKeyMu.Lock()
key := encryptionKey
encryptionKeyMu.Unlock()
if key == nil {
- log.Printf("asdf: key nil")
- if err := loginTemplate.Execute(w, loginTemplateArgs{Redirect: redirectUpload}); err != nil {
- log.Printf("Warning: login: %s", err)
- }
+ executeLoginTemplate(w, "/upload")
return
}
noteFile, err := newNoteName()
@@ -718,12 +710,20 @@ func uploadNote(w http.ResponseWriter, r *http.Request) {
return
}
defer noteFile.Close()
- contentReader, _, err := r.FormFile("content")
+ contentReader, metadata, err := r.FormFile("content")
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
writer := key.NewWriter(noteFile, cryptoutil.WithAdditionalData([]byte("notes/"+filepath.Base(noteFile.Name()))))
+ if _, err := writer.Write(binary.AppendVarint(nil, int64(len(metadata.Filename)))); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if _, err := io.WriteString(writer, metadata.Filename); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
if _, err := io.Copy(writer, contentReader); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -739,6 +739,39 @@ func uploadNote(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "upload! pog\n%s", filepath.Base(noteFile.Name()))
}
+func readNoteContent(w http.ResponseWriter, r *http.Request) {
+ if _, ok := cookieAuth(w, r); !ok {
+ executeLoginTemplate(w, "/note/"+r.PathValue("note"))
+ return
+ }
+ f, err := os.OpenInRoot(*notepadDir+"/notes", r.PathValue("note"))
+ if err != nil {
+ notFound(w, r)
+ return
+ }
+ defer f.Close()
+ encryptionKeyMu.Lock()
+ key := encryptionKey
+ encryptionKeyMu.Unlock()
+ if key == nil {
+ executeLoginTemplate(w, "/note/"+r.PathValue("note"))
+ return
+ }
+ reader := bufio.NewReader(key.NewReader(f, cryptoutil.WithAdditionalData([]byte("notes/"+r.PathValue("note")))))
+ nameLen, err := binary.ReadVarint(reader)
+ if err != nil {
+ http.Error(w, "invalid file", http.StatusInternalServerError)
+ return
+ }
+ fileName := make([]byte, nameLen)
+ if _, err := io.ReadFull(reader, fileName); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ w.Header().Set("Content-Disposition", "attachment; filename*=UTF-8''"+url.QueryEscape(string(fileName)))
+ io.Copy(w, reader)
+}
+
func main() {
flag.Parse()
@@ -751,9 +784,9 @@ func main() {
http.HandleFunc("POST /login", login)
http.HandleFunc("GET /notepad", notepad)
http.HandleFunc("POST /notepad/autosave", autosave)
- http.HandleFunc("GET /notes/upload", uploadPage)
- http.HandleFunc("POST /notes/upload", uploadNote)
- // http.HandleFunc("GET /notes/{note}", readNoteContent)
+ http.HandleFunc("GET /upload", uploadPage)
+ http.HandleFunc("POST /upload", uploadNote)
+ http.HandleFunc("GET /notes/{note}", readNoteContent)
http.HandleFunc("POST /api/login", apiLogin)
http.HandleFunc("GET /api/list-notes", gobReqRespMiddleware(listNotes))
http.HandleFunc("POST /api/create-note", gobReqRespMiddleware(createNote))
diff --git a/tools/notes/notes.go b/tools/notes/notes.go
index 9f190f6..7714a89 100644
--- a/tools/notes/notes.go
+++ b/tools/notes/notes.go
@@ -10,6 +10,7 @@ import (
"io"
"net/http"
"os"
+ "path/filepath"
"time"
"github.com/google/subcommands"
@@ -213,6 +214,7 @@ func (*newCommand) SetFlags(*flag.FlagSet) {}
type createNoteRequestStreamWriter struct {
ctx context.Context
+ fileName string
continuationToken []byte
}
@@ -225,6 +227,7 @@ func (w *createNoteRequestStreamWriter) Write(buf []byte) (int, error) {
for i := 0; i < 3; i++ {
resp, err = createNote(w.ctx, &api.CreateNoteRequest{
ContinuationToken: w.continuationToken,
+ FileName: w.fileName,
Chunk: buf,
More: true,
})
@@ -242,6 +245,7 @@ func (w *createNoteRequestStreamWriter) Write(buf []byte) (int, error) {
if err != nil {
return 0, err
}
+ w.fileName = ""
w.continuationToken = resp.ContinuationToken
return len(buf), nil
}
@@ -252,7 +256,7 @@ func (*newCommand) new(ctx context.Context, fileName string) error {
return err
}
defer f.Close()
- streamWriter := &createNoteRequestStreamWriter{ctx: ctx}
+ streamWriter := &createNoteRequestStreamWriter{ctx: ctx, fileName: filepath.Base(fileName)}
if _, err := io.Copy(streamWriter, f); err != nil {
return err
}