diff --git a/client.go b/client.go index 27ce4d5..592f6eb 100644 --- a/client.go +++ b/client.go @@ -87,6 +87,8 @@ func (c *Client) handleMessage(m Envelope) error { return c.handleMeta(m.Body) case "note": return c.handleNote(m.Body) + case "list-notes": + return c.handleListNotes(m.Body) default: return fmt.Errorf("received message of unsupported type: %v", m.Kind) } @@ -133,6 +135,38 @@ func (c *Client) handleNote(raw json.RawMessage) error { return nil } +func (c *Client) handleListNotes(raw json.RawMessage) error { + var notes ListNotesResponse + if err := json.Unmarshal(raw, ¬es); err != nil { + return fmt.Errorf("unable to unmarshal listnotes response: %v", err) + } + + writeNoteTitle := func(id int, title string) { + c.mu.Lock() + defer c.mu.Unlock() + c.trunc() + fmt.Printf("%d\t%s\n", id, title) + c.renderLine() + } + + for _, note := range notes { + key, err := rsa.DecryptPKCS1v15(rand.Reader, c.key, note.Key) + if err != nil { + c.err("unable to decrypt note key: %v", err) + continue + } + + title, err := c.aesDecrypt(key, note.Title) + if err != nil { + c.err("unable to decrype not title: %v", err) + continue + } + + writeNoteTitle(note.Id, string(title)) + } + return nil +} + func (c *Client) handshake() error { r := &Auth{Nick: c.nick, Key: c.key.PublicKey} c.info("authenticating as %s", c.nick) diff --git a/key.go b/key.go index 6f91d4c..158eb30 100644 --- a/key.go +++ b/key.go @@ -115,3 +115,9 @@ func getPublic() { exit(1, "unable to marshal key: %v", err) } } + +type KeyRequest string + +func (k KeyRequest) Kind() string { + return "key" +} diff --git a/note.go b/note.go index ccb1ced..5029c61 100644 --- a/note.go +++ b/note.go @@ -2,8 +2,19 @@ package main import ( "crypto/rand" + "github.com/jordanorelli/lexnum" ) +var numEncoder = lexnum.NewEncoder('=', '-') + +func encodeInt(n int) string { + return numEncoder.EncodeInt(n) +} + +func decodeInt(s string) (int, error) { + return numEncoder.DecodeInt(s) +} + type GetNoteRequest int func (g GetNoteRequest) Kind() string { @@ -42,5 +53,14 @@ func (l ListNotes) Kind() string { return "list-notes" } -type ListNotesResponse struct { +type ListNotesResponseItem struct { + Id int + Key []byte + Title []byte +} + +type ListNotesResponse []ListNotesResponseItem + +func (l ListNotesResponse) Kind() string { + return "list-notes" } diff --git a/server.go b/server.go index d18e056..2f377a5 100644 --- a/server.go +++ b/server.go @@ -8,7 +8,6 @@ import ( "github.com/syndtr/goleveldb/leveldb/util" "io" "net" - "strconv" "strings" ) @@ -55,6 +54,8 @@ func (s *serverConnection) handleRequest(request Envelope) error { return s.handleNoteRequest(request.Body) case "get-note": return s.handleGetNoteRequest(request.Body) + case "list-notes": + return s.handleListNotesRequest(request.Body) default: return fmt.Errorf("no such request type: %v", request.Kind) } @@ -117,13 +118,13 @@ func (s *serverConnection) handleNoteRequest(body json.RawMessage) error { if it.Last() { k := it.Key() id_s := strings.TrimPrefix(string(k), "notes/") - lastId, err := strconv.Atoi(id_s) + lastId, err := decodeInt(id_s) if err != nil { return fmt.Errorf("error getting note id: %v", err) } id = lastId + 1 } - key := fmt.Sprintf("notes/%d", id) + key := fmt.Sprintf("notes/%s", encodeInt(id)) if err := s.db.Put([]byte(key), body, nil); err != nil { return fmt.Errorf("unable to write note to db: %v", err) } @@ -136,7 +137,7 @@ func (s *serverConnection) handleGetNoteRequest(body json.RawMessage) error { if err := json.Unmarshal(body, &req); err != nil { return fmt.Errorf("bad getnote request: %v", err) } - key := fmt.Sprintf("notes/%d", req) + key := fmt.Sprintf("notes/%s", encodeInt(int(req))) b, err := s.db.Get([]byte(key), nil) if err != nil { return fmt.Errorf("couldn't retrieve note: %v", err) @@ -151,6 +152,47 @@ func (s *serverConnection) handleGetNoteRequest(body json.RawMessage) error { return nil } +func (s *serverConnection) handleListNotesRequest(body json.RawMessage) error { + r := util.BytesPrefix([]byte("notes/")) + + it := s.db.NewIterator(r, nil) + defer it.Release() + + notes := make(ListNotesResponse, 0, 10) + it.Last() + for i := 0; it.Valid() && i < 10; i++ { + key, val := it.Key(), it.Value() + + info_log.Printf("note %d has key %s", i, string(key)) + id_s := strings.TrimPrefix(string(key), "notes/") + info_log.Printf("note id_s: %s", id_s) + id, err := decodeInt(id_s) + if err != nil { + error_log.Printf("unable to parse note key %s: %v", id_s, err) + it.Prev() + continue + } + info_log.Printf("note key: %s id: %d\n", key, id) + + var note EncryptedNote + if err := json.Unmarshal(val, ¬e); err != nil { + error_log.Printf("unable to unmarshal encrypted note: %v", err) + it.Prev() + continue + } + notes = append(notes, ListNotesResponseItem{ + Id: id, + Key: note.Key, + Title: note.Title, + }) + it.Prev() + } + if err := it.Error(); err != nil { + return fmt.Errorf("error reading listnotes from db: %v", err) + } + return s.sendRequest(notes) +} + func (s *serverConnection) openDB() error { path := fmt.Sprintf("./%s.db", s.nick) db, err := leveldb.OpenFile(path, nil)