damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

commit 066b3cdde8ee3b285b8ab085dafb95e615639299
parent 7f313dcbd4f5cd2f0cb50a37643ccd7b82955b45
Author: Daniel D‘Aquino <daniel@daquino.me>
Date:   Tue, 15 Aug 2023 21:33:47 +0000

Fix image links appearing with escaped slashes

Changelog-Fixed: Fix images and links occasionally appearing with escaped slashes
Closes: https://github.com/damus-io/damus/issues/1468
Signed-off-by: Daniel D‘Aquino <daniel@daquino.me>
Signed-off-by: William Casarin <jb55@jb55.com>
Rewarded-sats: 50000

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 4++++
Mdamus/TestData.swift | 3+++
Mdamus/Views/NoteContentView.swift | 5+++++
AdamusTests/NostrEventTests.swift | 43+++++++++++++++++++++++++++++++++++++++++++
MdamusTests/NoteContentViewTests.swift | 10++++++++++
Mnostrdb/Test/NdbTests.swift | 8++++++++
Mnostrdb/nostrdb.c | 1+
7 files changed, 74 insertions(+), 0 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -383,6 +383,7 @@ BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; }; D2277EEA2A089BD5006C3807 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2277EE92A089BD5006C3807 /* Router.swift */; }; D78525252A7B2EA4002FA637 /* NoteContentViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78525242A7B2EA4002FA637 /* NoteContentViewTests.swift */; }; + D7DEEF2F2A8C021E00E0C99F /* NostrEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */; }; E4FA1C032A24BB7F00482697 /* SearchSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */; }; E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; }; E9E4ED0B295867B900DD7078 /* ThreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadView.swift */; }; @@ -934,6 +935,7 @@ BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; }; D2277EE92A089BD5006C3807 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; }; D78525242A7B2EA4002FA637 /* NoteContentViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteContentViewTests.swift; sourceTree = "<group>"; }; + D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrEventTests.swift; sourceTree = "<group>"; }; E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSettingsView.swift; sourceTree = "<group>"; }; E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = "<group>"; }; E9E4ED0A295867B900DD7078 /* ThreadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadView.swift; sourceTree = "<group>"; }; @@ -1745,6 +1747,7 @@ D78525242A7B2EA4002FA637 /* NoteContentViewTests.swift */, 4C684A542A7E91FE005E6031 /* LongPostTests.swift */, 4C684A562A7FFAE6005E6031 /* UrlTests.swift */, + D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */, ); path = damusTests; sourceTree = "<group>"; @@ -2451,6 +2454,7 @@ 4C19AE552A5D977400C90DB7 /* HashtagTests.swift in Sources */, 3A3040ED29A5CB86008A0F29 /* ReplyDescriptionTests.swift in Sources */, 3AAC7A022A60FE72002B50DF /* LocalizationUtilTests.swift in Sources */, + D7DEEF2F2A8C021E00E0C99F /* NostrEventTests.swift in Sources */, 4C8D00D429E3C5D40036AF10 /* NIP19Tests.swift in Sources */, 3A30410129AB12AA008A0F29 /* EventGroupViewTests.swift in Sources */, 501F8C822A0224EB001AFC1D /* KeychainStorageTests.swift in Sources */, diff --git a/damus/TestData.swift b/damus/TestData.swift @@ -28,6 +28,9 @@ let test_note = createdAt: UInt32(Date().timeIntervalSince1970 - 100) )! +let test_note_json_with_escaped_slash = "{\"tags\":[],\"pubkey\":\"f8e6c64342f1e052480630e27e1016dce35fc3a614e60434fef4aa2503328ca9\",\"content\":\"https:\\/\\/cdn.nostr.build\\/i\\/5c1d3296f66c2630131bf123106486aeaf051ed8466031c0e0532d70b33cddb2.jpg\",\"created_at\":1691864981,\"kind\":1,\"sig\":\"fc0033aa3d4df50b692a5b346fa816fdded698de2045e36e0642a021391468c44ca69c2471adc7e92088131872d4aaa1e90ea6e1ad97f3cc748f4aed96dfae18\",\"id\":\"e8f6eca3b161abba034dac9a02bb6930ecde9fd2fb5d6c5f22a05526e11382cb\"}" +let test_encoded_note_with_image = NostrEvent.owned_from_json(json: test_note_json_with_escaped_slash) + let test_encoded_post = "{\"id\": \"8ba545ab96959fe0ce7db31bc10f3ac3aa5353bc4428dbf1e56a7be7062516db\",\"pubkey\": \"7e27509ccf1e297e1df164912a43406218f8bd80129424c3ef798ca3ef5c8444\",\"created_at\": 1677013417,\"kind\": 1,\"tags\": [],\"content\": \"hello\",\"sig\": \"93684f15eddf11f42afbdd81828ee9fc35350344d8650c78909099d776e9ad8d959cd5c4bff7045be3b0b255144add43d0feef97940794a1bc9c309791bebe4a\"}" let test_repost_1 = NostrEvent(content: test_encoded_post, keypair: test_keypair, kind: 6, tags: [], createdAt: 1)! diff --git a/damus/Views/NoteContentView.swift b/damus/Views/NoteContentView.swift @@ -610,6 +610,11 @@ struct NoteContentView_Previews: PreviewProvider { NoteContentView(damus_state: state, event: test_note, show_images: true, size: .normal, options: []) } .previewDisplayName("Short note") + + VStack { + NoteContentView(damus_state: state, event: test_encoded_note_with_image!, show_images: true, size: .normal, options: []) + } + .previewDisplayName("Note with image") VStack { NoteContentView(damus_state: state2, event: test_longform_event.event, show_images: true, size: .normal, options: [.wide]) diff --git a/damusTests/NostrEventTests.swift b/damusTests/NostrEventTests.swift @@ -0,0 +1,43 @@ +// +// NostrEventTests.swift +// damusTests +// +// Created by Daniel D’Aquino on 2023-08-15. +// + +import Foundation +import XCTest +@testable import damus + +final class NostrEventTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + /// Based on https://github.com/damus-io/damus/issues/1468 + /// Tests whether `decode_nostr_event` correctly decodes nostr note image content written with optional escaped slashes + func testDecodeNostrEventWithEscapedSlashes() throws { + let testMessageString = "[\"EVENT\",\"A54091AC-D144-49F6-853A-2141A5EA09B6\",{\"content\":\"{\\\"tags\\\":[],\\\"pubkey\\\":\\\"f8e6c64342f1e052480630e27e1016dce35fc3a614e60434fef4aa2503328ca9\\\",\\\"content\\\":\\\"https:\\\\/\\\\/cdn.nostr.build\\\\/i\\\\/5c1d3296f66c2630131bf123106486aeaf051ed8466031c0e0532d70b33cddb2.jpg\\\",\\\"created_at\\\":1691864981,\\\"kind\\\":1,\\\"sig\\\":\\\"fc0033aa3d4df50b692a5b346fa816fdded698de2045e36e0642a021391468c44ca69c2471adc7e92088131872d4aaa1e90ea6e1ad97f3cc748f4aed96dfae18\\\",\\\"id\\\":\\\"e8f6eca3b161abba034dac9a02bb6930ecde9fd2fb5d6c5f22a05526e11382cb\\\"}\",\"created_at\":1691866192,\"id\":\"56e3f14cedab76762afef78eeb34b07ce1313543a2f3365a7b99fd5daa65abc9\",\"kind\":6,\"pubkey\":\"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245\",\"sig\":\"3c1161c3b03cafe13e2d9d624b158bdb74867caf61df158871633c859cd587e7779d096680de34024d9acfcba9aa3cb76fdbfa20227afb7e03a9ab588e6b77c9\",\"tags\":[[\"e\",\"e8f6eca3b161abba034dac9a02bb6930ecde9fd2fb5d6c5f22a05526e11382cb\",\"\",\"root\"],[\"p\",\"f8e6c64342f1e052480630e27e1016dce35fc3a614e60434fef4aa2503328ca9\"]]}]" + let response: NostrResponse = decode_nostr_event(txt: testMessageString)! + guard case .event(_, let testEvent) = response else { + XCTAssert(false, "Could not decode event") + return + } + let urlInContent = "https:\\/\\/cdn.nostr.build\\/i\\/5c1d3296f66c2630131bf123106486aeaf051ed8466031c0e0532d70b33cddb2.jpg" + XCTAssert(testEvent.content.contains(urlInContent), "Issue parsing event. Expected to see '\(urlInContent)' inside \(testEvent.content)") + + let testMessageString2 = "[\"EVENT\",\"A54091AC-D144-49F6-853A-2141A5EA09B6\",{\"content\":\"{\\\"tags\\\":[],\\\"pubkey\\\":\\\"f8e6c64342f1e052480630e27e1016dce35fc3a614e60434fef4aa2503328ca9\\\",\\\"content\\\":\\\"https:\\/\\/cdn.nostr.build\\/i\\/5c1d3296f66c2630131bf123106486aeaf051ed8466031c0e0532d70b33cddb2.jpg\\\",\\\"created_at\\\":1691864981,\\\"kind\\\":1,\\\"sig\\\":\\\"fc0033aa3d4df50b692a5b346fa816fdded698de2045e36e0642a021391468c44ca69c2471adc7e92088131872d4aaa1e90ea6e1ad97f3cc748f4aed96dfae18\\\",\\\"id\\\":\\\"e8f6eca3b161abba034dac9a02bb6930ecde9fd2fb5d6c5f22a05526e11382cb\\\"}\",\"created_at\":1691866192,\"id\":\"56e3f14cedab76762afef78eeb34b07ce1313543a2f3365a7b99fd5daa65abc9\",\"kind\":6,\"pubkey\":\"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245\",\"sig\":\"3c1161c3b03cafe13e2d9d624b158bdb74867caf61df158871633c859cd587e7779d096680de34024d9acfcba9aa3cb76fdbfa20227afb7e03a9ab588e6b77c9\",\"tags\":[[\"e\",\"e8f6eca3b161abba034dac9a02bb6930ecde9fd2fb5d6c5f22a05526e11382cb\",\"\",\"root\"],[\"p\",\"f8e6c64342f1e052480630e27e1016dce35fc3a614e60434fef4aa2503328ca9\"]]}]" + let response2: NostrResponse = decode_nostr_event(txt: testMessageString2)! + guard case .event(_, let testEvent2) = response2 else { + XCTAssert(false, "Could not decode event") + return + } + let urlInContent2 = "https://cdn.nostr.build/i/5c1d3296f66c2630131bf123106486aeaf051ed8466031c0e0532d70b33cddb2.jpg" + XCTAssert(testEvent2.content.contains(urlInContent2), "Issue parsing event. Expected to see '\(urlInContent2)' inside \(testEvent2.content)") + } +} diff --git a/damusTests/NoteContentViewTests.swift b/damusTests/NoteContentViewTests.swift @@ -25,5 +25,15 @@ class NoteContentViewTests: XCTestCase { XCTAssertEqual(runArray[1].link?.absoluteString, "damus:t:cool", "Latin-character hashtag is missing. Runs description :\(runArray.description)") XCTAssertEqual(runArray[3].link?.absoluteString.removingPercentEncoding!, "damus:t:かっこいい", "Non-latin-character hashtag is missing. Runs description :\(runArray.description)") } + + /// Based on https://github.com/damus-io/damus/issues/1468 + /// Tests whether a note content view correctly parses an image block when url in JSON content contains optional escaped slashes + func testParseImageBlockInContentWithEscapedSlashes() { + let testJSONWithEscapedSlashes = "{\"tags\":[],\"pubkey\":\"f8e6c64342f1e052480630e27e1016dce35fc3a614e60434fef4aa2503328ca9\",\"content\":\"https:\\/\\/cdn.nostr.build\\/i\\/5c1d3296f66c2630131bf123106486aeaf051ed8466031c0e0532d70b33cddb2.jpg\",\"created_at\":1691864981,\"kind\":1,\"sig\":\"fc0033aa3d4df50b692a5b346fa816fdded698de2045e36e0642a021391468c44ca69c2471adc7e92088131872d4aaa1e90ea6e1ad97f3cc748f4aed96dfae18\",\"id\":\"e8f6eca3b161abba034dac9a02bb6930ecde9fd2fb5d6c5f22a05526e11382cb\"}" + let testNote = NostrEvent.owned_from_json(json: testJSONWithEscapedSlashes)! + let parsed = parse_note_content(content: .init(note: testNote, privkey: test_keypair.privkey)) + + XCTAssertTrue((parsed.blocks[0].is_url != nil), "NoteContentView does not correctly parse an image block when url in JSON content contains optional escaped slashes.") + } } diff --git a/nostrdb/Test/NdbTests.swift b/nostrdb/Test/NdbTests.swift @@ -80,6 +80,14 @@ final class NdbTests: XCTestCase { XCTAssertEqual(tags, 786) XCTAssertEqual(total_count_stored, total_count_iter) } + + /// Based on https://github.com/damus-io/damus/issues/1468 + /// Tests whether a JSON with optional escaped slash characters is correctly unescaped (In accordance to https://datatracker.ietf.org/doc/html/rfc8259#section-7) + func test_decode_json_with_escaped_slashes() { + let testJSONWithEscapedSlashes = "{\"tags\":[],\"pubkey\":\"f8e6c64342f1e052480630e27e1016dce35fc3a614e60434fef4aa2503328ca9\",\"content\":\"https:\\/\\/cdn.nostr.build\\/i\\/5c1d3296f66c2630131bf123106486aeaf051ed8466031c0e0532d70b33cddb2.jpg\",\"created_at\":1691864981,\"kind\":1,\"sig\":\"fc0033aa3d4df50b692a5b346fa816fdded698de2045e36e0642a021391468c44ca69c2471adc7e92088131872d4aaa1e90ea6e1ad97f3cc748f4aed96dfae18\",\"id\":\"e8f6eca3b161abba034dac9a02bb6930ecde9fd2fb5d6c5f22a05526e11382cb\"}" + let testNote = NdbNote.owned_from_json(json: testJSONWithEscapedSlashes)! + XCTAssertEqual(testNote.content, "https://cdn.nostr.build/i/5c1d3296f66c2630131bf123106486aeaf051ed8466031c0e0532d70b33cddb2.jpg") + } func test_decode_perf() throws { // This is an example of a performance test case. diff --git a/nostrdb/nostrdb.c b/nostrdb/nostrdb.c @@ -117,6 +117,7 @@ static int cursor_push_unescaped_char(struct cursor *cur, char c1, char c2) case 'b': return cursor_push_byte(cur, '\b'); case 'f': return cursor_push_byte(cur, '\f'); case '\\': return cursor_push_byte(cur, '\\'); + case '/': return cursor_push_byte(cur, '/'); case '"': return cursor_push_byte(cur, '"'); case 'u': // these aren't handled yet