diff options
Diffstat (limited to 'roseh.moe.go')
| -rw-r--r-- | roseh.moe.go | 181 |
1 files changed, 96 insertions, 85 deletions
diff --git a/roseh.moe.go b/roseh.moe.go index 76b4dc1..5600a75 100644 --- a/roseh.moe.go +++ b/roseh.moe.go @@ -17,6 +17,7 @@ import ( "log" "net/http" "os" + "path/filepath" "strings" "sync" "time" @@ -406,75 +407,18 @@ func listNotes(req *api.ListNotesRequest) (*api.ListNotesResponse, error) { return &api.ListNotesResponse{Notes: names}, nil } -type countingWriter struct { - w io.Writer - count int64 -} - -func (w *countingWriter) Write(buf []byte) (int, error) { - n, err := w.w.Write(buf) - w.count += int64(n) - return n, err -} - -type continuationToken struct { - Name string - Size int64 -} - -func appendNote(req *api.CreateNoteRequest, key cryptoutil.EncryptionKey) (*api.CreateNoteResponse, error) { - msg, ok := secretKey.Verify(req.ContinuationToken, "continuation-token") - if !ok { - return nil, fmt.Errorf("%w: bad continuation token", api.InvalidArgument) - } - var token continuationToken - if err := gob.NewDecoder(bytes.NewReader(msg)).Decode(&token); err != nil { - return nil, fmt.Errorf("failed to decode continuation token: %s", err) - } - var err error - oldNote, err := os.Open(*notepadDir + "/notes/" + token.Name) +func readLinkedList(w io.Writer, next []byte, key cryptoutil.EncryptionKey, name string) error { + f, err := os.Open(name) if err != nil { - return nil, err - } - defer oldNote.Close() - stat, err := oldNote.Stat() - if err != nil { - return nil, err - } - if token.Size != stat.Size() { - return nil, fmt.Errorf("%w: continuation token expired", api.InvalidArgument) - } - newNote, err := os.CreateTemp(*notepadDir+"/notes", token.Name) - if err != nil { - return nil, err - } - defer newNote.Close() - additionalData := []byte("notes/" + token.Name) - cw := &countingWriter{w: newNote} - encryptingWriter := key.NewWriter(cw, additionalData) - if _, err := io.Copy(encryptingWriter, key.NewReader(oldNote, additionalData)); err != nil { - return nil, err - } - if _, err := encryptingWriter.Write(req.Chunk); err != nil { - return nil, err - } - if err := encryptingWriter.Close(); err != nil { - return nil, err - } - if err := newNote.Close(); err != nil { - return nil, err - } - if err := os.Rename(newNote.Name(), *notepadDir+"/notes/"+token.Name); err != nil { - return nil, err + return err } - buf := new(bytes.Buffer) - if err := gob.NewEncoder(buf).Encode(continuationToken{Name: token.Name, Size: cw.count}); err != nil { - return nil, err + defer f.Close() + reader := key.NewReader(f, []byte("chunks/"+filepath.Base(name))) + if _, err := io.ReadFull(reader, next); err != nil { + return err } - return &api.CreateNoteResponse{ - Name: token.Name, - ContinuationToken: secretKey.Sign(buf.Bytes(), "continuation-token"), - }, nil + _, err = io.Copy(w, reader) + return err } var ( @@ -484,17 +428,79 @@ var ( ) func createNote(req *api.CreateNoteRequest) (*api.CreateNoteResponse, error) { + type continuationToken struct { + Head string + Tail []byte + } + encryptionKeyMu.Lock() key := encryptionKey encryptionKeyMu.Unlock() if key == nil { return nil, fmt.Errorf("%w: need login", api.PermissionDenied) } - var name string - var f *os.File + var ( + contToken continuationToken + f *os.File + ) if len(req.ContinuationToken) > 0 { - return appendNote(req, key) + msg, ok := secretKey.Verify(req.ContinuationToken, "continuation-token") + if !ok { + return nil, fmt.Errorf("%w: invalid continuation token", api.InvalidArgument) + } + if err := gob.NewDecoder(bytes.NewReader(msg)).Decode(&contToken); err != nil { + return nil, fmt.Errorf("bad continuation token") + } + var err error + f, err = os.OpenFile(*notepadDir+"/chunks/"+base64.RawURLEncoding.EncodeToString(contToken.Tail), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600) + if err != nil { + if errors.Is(err, os.ErrExist) { + return nil, fmt.Errorf("%w: continuation token expired", api.InvalidArgument) + } + return nil, err + } + defer f.Close() + } else { + if err := os.MkdirAll(*notepadDir+"/chunks", 0700); err != nil { + return nil, err + } + var err error + f, err = os.CreateTemp(*notepadDir+"/chunks", "head") + if err != nil { + return nil, err + } + defer f.Close() } + next := make([]byte, 64) + rand.Read(next) + encryptingWriter := key.NewWriter(f, []byte("chunks/"+filepath.Base(f.Name()))) + if _, err := encryptingWriter.Write(next); err != nil { + return nil, err + } + if _, err := encryptingWriter.Write(req.Chunk); err != nil { + return nil, err + } + if err := encryptingWriter.Close(); err != nil { + return nil, err + } + if err := f.Close(); err != nil { + return nil, err + } + head := contToken.Head + if head == "" { + head = filepath.Base(f.Name()) + } + if req.More { + buf := new(bytes.Buffer) + if err := gob.NewEncoder(buf).Encode(continuationToken{Head: head, Tail: next}); err != nil { + return nil, err + } + return &api.CreateNoteResponse{ContinuationToken: secretKey.Sign(buf.Bytes(), "continuation-token")}, nil + } + if err := os.MkdirAll(*notepadDir+"/notes", 0700); err != nil { + return nil, err + } + var finalNote *os.File for n := 1; ; n++ { buf := make([]byte, 2*n) rand.Read(buf) @@ -502,37 +508,42 @@ func createNote(req *api.CreateNoteRequest) (*api.CreateNoteResponse, error) { for i := range words { words[i] = wordList[binary.NativeEndian.Uint16(buf[2*i:])&0x1fff] } - name = strings.Join(words, "-") + name := strings.Join(words, "-") var err error - f, err = os.OpenFile(*notepadDir+"/notes/"+name, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600) + finalNote, err = os.OpenFile(*notepadDir+"/notes/"+name, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600) if err != nil { if errors.Is(err, os.ErrExist) { continue } return nil, fmt.Errorf("create note: create note file: %s", err) } - defer f.Close() + defer finalNote.Close() break } - cw := &countingWriter{w: f} - encryptingWriter := key.NewWriter(cw, []byte("notes/"+name)) - if _, err := encryptingWriter.Write(req.Chunk); err != nil { - return nil, err + var chunks []string + writer := key.NewWriter(finalNote, []byte("notes/"+filepath.Base(finalNote.Name()))) + tailBase64 := base64.RawURLEncoding.EncodeToString(next) + for chunk := head; chunk != tailBase64; chunk = base64.RawURLEncoding.EncodeToString(next) { + chunks = append(chunks, chunk) + if err := readLinkedList(writer, next, key, *notepadDir+"/chunks/"+chunk); err != nil { + return nil, err + } } - if err := encryptingWriter.Close(); err != nil { + if _, err := writer.Write(req.Chunk); err != nil { return nil, err } - if err := f.Close(); err != nil { - return nil, fmt.Errorf("create note: write file: %s", err) + if err := writer.Close(); err != nil { + return nil, err } - token := new(bytes.Buffer) - if err := gob.NewEncoder(token).Encode(continuationToken{Name: name, Size: cw.count}); err != nil { + if err := finalNote.Close(); err != nil { return nil, err } - return &api.CreateNoteResponse{ - Name: name, - ContinuationToken: secretKey.Sign(token.Bytes(), "continuation-token"), - }, nil + for _, chunk := range chunks { + if err := os.Remove(*notepadDir + "/chunks/" + chunk); err != nil { + log.Printf("Warning: createNote: failed to delete chunk: %s", err) + } + } + return &api.CreateNoteResponse{Name: filepath.Base(finalNote.Name())}, nil } type readNoteResponseWriter struct { |
