private static StandardFileNote CreateNote(ISimpleJsonRest web, StandardNoteConnection conn, APIResultItem encNote, APIResultAuthorize authToken, StandardNoteConfig cfg, StandardNoteData dat) { if (encNote.deleted) { var nd = new StandardFileNote(encNote.uuid, cfg, conn.HConfig) { CreationDate = encNote.created_at, Text = "", InternalTitle = "", AuthHash = encNote.auth_hash, ContentVersion = StandardNoteCrypt.GetSchemaVersion(encNote.content), }; nd.ModificationDate = encNote.updated_at; return(nd); } ContentNote content; try { var contentJson = StandardNoteCrypt.DecryptContent(encNote.content, encNote.enc_item_key, encNote.auth_hash, authToken.masterkey, authToken.masterauthkey); Logger.Debug( StandardNotePlugin.Name, $"DecryptContent of note {encNote.uuid:B}", $"[content]:\r\n{encNote.content}\r\n" + $"[enc_item_key]:\r\n{encNote.enc_item_key}\r\n" + $"[auth_hash]:\r\n{encNote.auth_hash}\r\n" + $"\r\n\r\n" + $"[contentJson]:\r\n{contentJson}\r\n"); content = web.ParseJsonWithoutConverter <ContentNote>(contentJson); } catch (RestException) { throw; } catch (Exception e) { throw new StandardNoteAPIException("Cannot decrypt note with local masterkey", e); } var n = new StandardFileNote(encNote.uuid, cfg, conn.HConfig) { Text = StandardNoteConfig.REX_LINEBREAK.Replace(content.text, Environment.NewLine), InternalTitle = content.title, AuthHash = encNote.auth_hash, ContentVersion = StandardNoteCrypt.GetSchemaVersion(encNote.content), IsPinned = GetAppDataBool(content.appData, APPDATA_PINNED, false), IsLocked = GetAppDataBool(content.appData, APPDATA_LOCKED, false), }; var refTags = new List <StandardFileTagRef>(); foreach (var cref in content.references) { if (cref.content_type == "Note") { // ignore } else if (dat.Tags.Any(t => t.UUID == cref.uuid)) { refTags.Add(new StandardFileTagRef(cref.uuid, dat.Tags.First(t => t.UUID == cref.uuid).Title)); } else if (cref.content_type == "Tag") { Logger.Warn(StandardNotePlugin.Name, $"Reference to missing tag {cref.uuid} in note {encNote.uuid}"); } else { Logger.Error(StandardNotePlugin.Name, $"Downloaded note contains an unknown reference :{cref.uuid} ({cref.content_type}) in note {encNote.uuid}"); } } foreach (var tref in dat.Tags.Where(tag => tag.References.Any(tref => tref == encNote.uuid))) { refTags.Add(new StandardFileTagRef(tref.UUID, tref.Title)); } refTags = refTags.DistinctBy(t => t.UUID).ToList(); n.SetTags(refTags); n.SetReferences(content.references); n.CreationDate = encNote.created_at; n.ModificationDate = encNote.updated_at; return(n); }
private static SyncResultTag CreateTag(ISimpleJsonRest web, APIResultItem encTag, APIResultAuthorize authToken) { if (encTag.deleted) { return(new SyncResultTag { deleted = encTag.deleted, title = "", uuid = encTag.uuid, enc_item_key = encTag.enc_item_key, item_key = "", references = new List <APIResultContentRef>(), }); } ContentTag content; try { var contentJson = StandardNoteCrypt.DecryptContent(encTag.content, encTag.enc_item_key, encTag.auth_hash, authToken.masterkey, authToken.masterauthkey); Logger.Debug( StandardNotePlugin.Name, $"DecryptContent of tag {encTag.uuid:B}", $"[content]:\r\n{encTag.content}\r\n" + $"[enc_item_key]:\r\n{encTag.enc_item_key}\r\n" + $"[auth_hash]:\r\n{encTag.auth_hash}\r\n" + $"\r\n\r\n" + $"[contentJson]:\r\n{contentJson}\r\n"); content = web.ParseJsonWithoutConverter <ContentTag>(contentJson); } catch (RestException) { throw; } catch (Exception e) { throw new StandardNoteAPIException("Cannot decrypt note with local masterkey", e); } return(new SyncResultTag { deleted = encTag.deleted, title = content.title, uuid = encTag.uuid, enc_item_key = encTag.enc_item_key, references = content.references, }); }
private static void PrepareNoteForUpload(ISimpleJsonRest web, APIBodySync body, ref List <APIRawBodyItem> bodyraw, StandardFileNote note, List <StandardFileTag> allTags, APIResultAuthorize token, StandardNoteConfig cfg, bool delete) { var appdata = new Dictionary <string, Dictionary <string, object> >(); SetAppDataBool(appdata, APPDATA_PINNED, note.IsPinned); SetAppDataBool(appdata, APPDATA_LOCKED, note.IsLocked); var objContent = new ContentNote { title = note.InternalTitle, text = note.Text.Replace("\r\n", "\n"), references = new List <APIResultContentRef>(), appData = appdata, }; // Set correct tag UUID if tag already exists foreach (var itertag in note.InternalTags.ToList()) { var itag = itertag; if (itag.UUID == null) { var newTag = allTags.FirstOrDefault(e => e.Title == itag.Title)?.ToRef(); if (newTag == null) { newTag = new StandardFileTagRef(Guid.NewGuid(), itag.Title); allTags.Add(new StandardFileTag(newTag.UUID, newTag.Title, Enumerable.Repeat(note.ID, 1))); } note.UpgradeTag(itag, newTag); } } // Notes no longer have references to their tags (see issue #88) //foreach (var itertag in note.InternalTags.ToList()) //{ // Debug.Assert(itertag.UUID != null, "itertag.UUID != null"); // jsnContent.references.Add(new APIResultContentRef { content_type = "Tag", uuid = itertag.UUID.Value }); //} var jsonContent = web.SerializeJson(objContent); var cryptData = StandardNoteCrypt.EncryptContent(token.version, jsonContent, note.ID, token.masterkey, token.masterauthkey); body.items.Add(new APIBodyItem { content_type = "Note", uuid = note.ID, created_at = note.CreationDate, enc_item_key = cryptData.enc_item_key, auth_hash = cryptData.auth_hash, content = cryptData.enc_content, deleted = delete, }); bodyraw.Add(new APIRawBodyItem { content_type = "Note", uuid = note.ID, created_at = note.CreationDate, content = jsonContent, deleted = delete, }); }
private static void PrepareTagForUpload(ISimpleJsonRest web, APIBodySync body, ref List <APIRawBodyItem> bodyraw, StandardFileTag tag, APIResultAuthorize token, bool delete) { var objContent = new ContentTag { title = tag.Title, references = tag .References .Select(n => new APIResultContentRef { content_type = "Note", uuid = n }) .ToList(), }; Debug.Assert(tag.UUID != null, "tag.UUID != null"); var jsonContent = web.SerializeJson(objContent); var cryptData = StandardNoteCrypt.EncryptContent(token.version, jsonContent, tag.UUID.Value, token.masterkey, token.masterauthkey); body.items.Add(new APIBodyItem { content_type = "Tag", uuid = tag.UUID.Value, enc_item_key = cryptData.enc_item_key, auth_hash = cryptData.auth_hash, content = cryptData.enc_content, deleted = delete, }); bodyraw.Add(new APIRawBodyItem { content_type = "Tag", uuid = tag.UUID.Value, content = jsonContent, deleted = delete, }); }
public static SyncResult Sync(ISimpleJsonRest web, StandardNoteConnection conn, APIResultAuthorize authToken, StandardNoteConfig cfg, StandardNoteData dat, List <StandardFileNote> allNotes, List <StandardFileNote> notesUpload, List <StandardFileNote> notesDelete, List <StandardFileTag> tagsDelete) { APIBodySync d = new APIBodySync(); d.cursor_token = null; d.sync_token = string.IsNullOrWhiteSpace(dat.SyncToken) ? null : dat.SyncToken; d.limit = 150; d.items = new List <APIBodyItem>(); var items_raw = new List <APIRawBodyItem>(); var allTags = dat.Tags.ToList(); // Upload new notes foreach (var mvNote in notesUpload) { PrepareNoteForUpload(web, d, ref items_raw, mvNote, allTags, authToken, cfg, false); } // Delete deleted notes foreach (var rmNote in notesDelete) { PrepareNoteForUpload(web, d, ref items_raw, rmNote, allTags, authToken, cfg, true); } // Update references on tags (from changed notes) foreach (var upTag in GetTagsInNeedOfUpdate(dat.Tags, notesUpload, notesDelete)) { PrepareTagForUpload(web, d, ref items_raw, upTag, authToken, false); } // Remove unused tags if (cfg.RemEmptyTags) { foreach (var rmTag in tagsDelete) { PrepareTagForUpload(web, d, ref items_raw, rmTag, authToken, true); } } Logger.Debug( StandardNotePlugin.Name, $"Perform sync request ({items_raw.Count} items send)", "Sent Items (unencrypted):\n\n" + string.Join("\n", items_raw.Select(i => $"{{\n content_type = {i.content_type}\n uuid = {i.uuid}\n created_at = {i.created_at}\n deleted = {i.deleted}\n content =\n{CompactJsonFormatter.FormatJSON(i.content, 2, 1)}\n}}"))); var result = GetCursorResult(web, dat, d); var syncresult = new SyncResult(); syncresult.retrieved_tags = result .retrieved_items .Where(p => p.content_type.ToLower() == "tag") .Where(p => !p.deleted) .Select(n => CreateTag(web, n, authToken)) .ToList(); syncresult.deleted_tags = result .retrieved_items .Where(p => p.content_type.ToLower() == "tag") .Where(p => p.deleted) .Select(n => CreateTag(web, n, authToken)) .ToList(); syncresult.saved_tags = result .saved_items .Where(p => p.content_type.ToLower() == "tag") .Select(n => CreateTag(web, n, authToken)) .ToList(); syncresult.unsaved_tags = result .unsaved .Where(p => p.item.content_type.ToLower() == "tag") .Select(n => CreateTag(web, n.item, authToken)) .ToList(); dat.UpdateTags(syncresult.retrieved_tags, syncresult.saved_tags, syncresult.unsaved_tags, syncresult.deleted_tags); syncresult.retrieved_notes = result .retrieved_items .Where(p => p.content_type.ToLower() == "note") .Where(p => !p.deleted) .Select(n => CreateNote(web, conn, n, authToken, cfg, dat)) .ToList(); syncresult.deleted_notes = result .retrieved_items .Where(p => p.content_type.ToLower() == "note") .Where(p => p.deleted) .Select(n => CreateNote(web, conn, n, authToken, cfg, dat)) .ToList(); syncresult.saved_notes = result .saved_items .Where(p => p.content_type.ToLower() == "note") .Select(n => CreateNote(web, conn, n, authToken, cfg, dat)) .ToList(); syncresult.conflict_notes = result .unsaved .Where(p => p.item.content_type.ToLower() == "note") .Where(p => p.error.tag == "sync_conflict") .Select(n => CreateNote(web, conn, n.item, authToken, cfg, dat)) .ToList(); syncresult.error_notes = result .unsaved .Where(p => p.item.content_type.ToLower() == "note") .Where(p => p.error.tag != "sync_conflict") .Select(n => CreateNote(web, conn, n.item, authToken, cfg, dat)) .ToList(); syncresult.retrieved_notes.AddRange(GetMissingNoteUpdates(syncresult.retrieved_tags.Concat(syncresult.saved_tags), dat.Tags, allNotes, syncresult.retrieved_notes)); return(syncresult); }
private static void PrepareForUpload(ISimpleJsonRest web, APIBodySync body, StandardFileTag tag, List <StandardFileNote> allNotes, APIResultAuthorize token, StandardNoteConfig cfg, bool delete) { var jsnContent = new ContentTag { title = tag.Title, references = allNotes .Where(n => n.InternalTags.Any(it => it.UUID == tag.UUID)) .Select(n => new APIResultContentRef { content_type = "Note", uuid = n.ID }) .ToList(), }; Debug.Assert(tag.UUID != null, "tag.UUID != null"); var cdNote = StandardNoteCrypt.EncryptContent(token.version, web.SerializeJson(jsnContent), tag.UUID.Value, token.masterkey, token.masterauthkey); body.items.Add(new APIBodyItem { content_type = "Tag", uuid = tag.UUID.Value, enc_item_key = cdNote.enc_item_key, auth_hash = cdNote.auth_hash, content = cdNote.enc_content, deleted = delete, }); }
private static void PrepareForUpload(ISimpleJsonRest web, APIBodySync body, StandardFileNote note, List <StandardFileTag> tags, APIResultAuthorize token, StandardNoteConfig cfg, bool delete) { var jsnContent = new ContentNote { title = note.InternalTitle, text = note.Text, references = new List <APIResultContentRef>(), appData = new Dictionary <string, Dictionary <string, object> > { { "org.standardnotes.sn", new Dictionary <string, object> { { "pinned", note.IsPinned } } } }, }; foreach (var itertag in note.InternalTags.ToList()) { var itag = itertag; if (itag.UUID == null) { var newTag = tags.FirstOrDefault(e => e.Title == itag.Title); if (newTag == null) { newTag = new StandardFileTag(Guid.NewGuid(), itag.Title); tags.Add(newTag); } note.UpgradeTag(itag, newTag); itag = newTag; } Debug.Assert(itag.UUID != null, "itag.UUID != null"); jsnContent.references.Add(new APIResultContentRef { content_type = "Tag", uuid = itag.UUID.Value }); } var cdNote = StandardNoteCrypt.EncryptContent(token.version, web.SerializeJson(jsnContent), note.ID, token.masterkey, token.masterauthkey); body.items.Add(new APIBodyItem { content_type = "Note", uuid = note.ID, created_at = note.CreationDate, enc_item_key = cdNote.enc_item_key, auth_hash = cdNote.auth_hash, content = cdNote.enc_content, deleted = delete, }); }
public static SyncResult Sync(ISimpleJsonRest web, StandardNoteConnection conn, APIResultAuthorize authToken, StandardNoteConfig cfg, StandardNoteData dat, List <StandardFileNote> allNotes, List <StandardFileNote> notesUpload, List <StandardFileNote> notesDelete, List <StandardFileTag> tagsDelete) { APIBodySync d = new APIBodySync(); d.cursor_token = null; d.sync_token = string.IsNullOrWhiteSpace(dat.SyncToken) ? null : dat.SyncToken; d.limit = 150; d.items = new List <APIBodyItem>(); var allTags = dat.Tags.ToList(); // Upload new notes foreach (var mvNote in notesUpload) { PrepareForUpload(web, d, mvNote, allTags, authToken, cfg, false); } // Delete deleted notes foreach (var rmNote in notesDelete) { PrepareForUpload(web, d, rmNote, allTags, authToken, cfg, true); } // Update references on tags (from changed notes) foreach (var upTag in notesUpload.SelectMany(n => n.InternalTags).Concat(notesDelete.SelectMany(n => n.InternalTags)).Except(tagsDelete)) { PrepareForUpload(web, d, upTag, allNotes, authToken, cfg, false); } // Remove unused tags if (cfg.RemEmptyTags) { foreach (var rmTag in tagsDelete) { PrepareForUpload(web, d, rmTag, allNotes, authToken, cfg, true); } } var result = GetCursorResult(web, dat, d); var syncresult = new SyncResult(); syncresult.retrieved_tags = result .retrieved_items .Where(p => p.content_type.ToLower() == "tag") .Where(p => !p.deleted) .Select(n => CreateTag(web, n, authToken)) .ToList(); syncresult.deleted_tags = result .retrieved_items .Where(p => p.content_type.ToLower() == "tag") .Where(p => p.deleted) .Select(n => CreateTag(web, n, authToken)) .ToList(); syncresult.saved_tags = result .saved_items .Where(p => p.content_type.ToLower() == "tag") .Select(n => CreateTag(web, n, authToken)) .ToList(); syncresult.unsaved_tags = result .unsaved .Where(p => p.item.content_type.ToLower() == "tag") .Select(n => CreateTag(web, n.item, authToken)) .ToList(); dat.UpdateTags(syncresult.retrieved_tags, syncresult.saved_tags, syncresult.unsaved_tags, syncresult.deleted_tags); syncresult.retrieved_notes = result .retrieved_items .Where(p => p.content_type.ToLower() == "note") .Where(p => !p.deleted) .Select(n => CreateNote(web, conn, n, authToken, cfg, dat)) .ToList(); syncresult.deleted_notes = result .retrieved_items .Where(p => p.content_type.ToLower() == "note") .Where(p => p.deleted) .Select(n => CreateNote(web, conn, n, authToken, cfg, dat)) .ToList(); syncresult.saved_notes = result .saved_items .Where(p => p.content_type.ToLower() == "note") .Select(n => CreateNote(web, conn, n, authToken, cfg, dat)) .ToList(); syncresult.conflict_notes = result .unsaved .Where(p => p.item.content_type.ToLower() == "note") .Where(p => p.error.tag == "sync_conflict") .Select(n => CreateNote(web, conn, n.item, authToken, cfg, dat)) .ToList(); syncresult.error_notes = result .unsaved .Where(p => p.item.content_type.ToLower() == "note") .Where(p => p.error.tag != "sync_conflict") .Select(n => CreateNote(web, conn, n.item, authToken, cfg, dat)) .ToList(); return(syncresult); }
private static StandardFileNote CreateNote(ISimpleJsonRest web, StandardNoteConnection conn, APIResultItem encNote, APIResultAuthorize authToken, StandardNoteConfig cfg, StandardNoteData dat) { if (encNote.deleted) { var nd = new StandardFileNote(encNote.uuid, cfg, conn.HConfig) { CreationDate = encNote.created_at, AuthHash = encNote.auth_hash, ContentVersion = StandardNoteCrypt.GetSchemaVersion(encNote.content), }; nd.RawModificationDate = encNote.updated_at; return(nd); } ContentNote content; string appDataContentString; try { var contentJson = StandardNoteCrypt.DecryptContent(encNote.content, encNote.enc_item_key, encNote.auth_hash, authToken.masterkey, authToken.masterauthkey); Logger.Debug( StandardNotePlugin.Name, $"DecryptContent of note {encNote.uuid:B}", $"[content]:\r\n{encNote.content}\r\n" + $"[enc_item_key]:\r\n{encNote.enc_item_key}\r\n" + $"[auth_hash]:\r\n{encNote.auth_hash}\r\n" + $"\r\n\r\n" + $"[contentJson]:\r\n{contentJson}\r\n"); content = web.ParseJsonWithoutConverter <ContentNote>(contentJson); appDataContentString = web.ParseJsonAndGetSubJson(contentJson, "appData", string.Empty); } catch (RestException) { throw; } catch (Exception e) { throw new StandardNoteAPIException("Cannot decrypt note with local masterkey", e); } var n = new StandardFileNote(encNote.uuid, cfg, conn.HConfig); using (n.SuppressDirtyChanges()) { n.Text = StandardNoteConfig.REX_LINEBREAK.Replace(content.text, Environment.NewLine); n.InternalTitle = content.title; n.AuthHash = encNote.auth_hash; n.ContentVersion = StandardNoteCrypt.GetSchemaVersion(encNote.content); n.IsPinned = GetAppDataBool(content.appData, APPDATA_PINNED, false); n.IsLocked = GetAppDataBool(content.appData, APPDATA_LOCKED, false); n.IsArchived = GetAppDataBool(content.appData, APPDATA_ARCHIVED, false); n.IsProtected = content.@protected; n.IsHidePreview = content.hidePreview; var refTags = new List <StandardFileTagRef>(); foreach (var cref in content.references) { if (cref.content_type == "Note") { // ignore } else if (dat.Tags.Any(t => t.UUID == cref.uuid)) { refTags.Add(new StandardFileTagRef(cref.uuid, dat.Tags.First(t => t.UUID == cref.uuid).Title)); } else if (cref.content_type == "Tag") { Logger.Warn(StandardNotePlugin.Name, $"Reference to missing tag {cref.uuid} in note {encNote.uuid}"); } else { Logger.Error(StandardNotePlugin.Name, $"Downloaded note contains an unknown reference :{cref.uuid} ({cref.content_type}) in note {encNote.uuid}"); } } foreach (var tref in dat.Tags.Where(tag => tag.References.Any(tref => tref == encNote.uuid))) { refTags.Add(new StandardFileTagRef(tref.UUID, tref.Title)); } refTags = refTags.DistinctBy(t => t.UUID).ToList(); n.SetTags(refTags); n.SetReferences(content.references); n.CreationDate = encNote.created_at; n.RawModificationDate = encNote.updated_at; n.ClientUpdatedAt = GetAppDataDTO(content.appData, APPDATA_CLIENTUPDATEDAT, null); n.NoteCreationDate = GetAppDataDTO(content.appData, APPDATA_NOTECDATE, null); n.NoteModificationDate = GetAppDataDTO(content.appData, APPDATA_NOTEMDATE, null); n.TextModificationDate = GetAppDataDTO(content.appData, APPDATA_TEXTMDATE, null); n.TitleModificationDate = GetAppDataDTO(content.appData, APPDATA_TITLEMDATE, null); n.TagsModificationDate = GetAppDataDTO(content.appData, APPDATA_TAGSMDATE, null); n.PathModificationDate = GetAppDataDTO(content.appData, APPDATA_PATHMDATE, null); n.RawAppData = appDataContentString; } return(n); }