summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--internal/api/api.go1
-rw-r--r--roseh.moe.go181
-rw-r--r--tools/notes/notes.go9
3 files changed, 103 insertions, 88 deletions
diff --git a/internal/api/api.go b/internal/api/api.go
index b925a7a..88a0011 100644
--- a/internal/api/api.go
+++ b/internal/api/api.go
@@ -39,6 +39,7 @@ type ListNotesResponse struct {
type CreateNoteRequest struct {
ContinuationToken []byte
Chunk []byte
+ More bool
}
type CreateNoteResponse struct {
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 {
diff --git a/tools/notes/notes.go b/tools/notes/notes.go
index 2b03694..f22b7f9 100644
--- a/tools/notes/notes.go
+++ b/tools/notes/notes.go
@@ -211,19 +211,18 @@ func (*newCommand) SetFlags(*flag.FlagSet) {}
type createNoteRequestStreamWriter struct {
ctx context.Context
continuationToken []byte
- name string
}
func (w *createNoteRequestStreamWriter) Write(buf []byte) (int, error) {
resp, err := createNote(w.ctx, &api.CreateNoteRequest{
ContinuationToken: w.continuationToken,
Chunk: buf,
+ More: true,
})
if err != nil {
return 0, err
}
w.continuationToken = resp.ContinuationToken
- w.name = resp.Name
return len(buf), nil
}
@@ -237,7 +236,11 @@ func (*newCommand) new(ctx context.Context, fileName string) error {
if _, err := io.Copy(streamWriter, f); err != nil {
return err
}
- fmt.Println(streamWriter.name)
+ resp, err := createNote(ctx, &api.CreateNoteRequest{ContinuationToken: streamWriter.continuationToken})
+ if err != nil {
+ return err
+ }
+ fmt.Println(resp.Name)
return nil
}