also looking at this
fix(android): an apostrophe in a reply hangs the page forever
#156
4 files
+279
-73
| @@ -1,28 +1,26 @@ | ||
| 1 | 1 | //! `deleteCalendarEvent` on Android — the first action here that answers |
| 2 | 2 | //! through the reply channel rather than by returning. |
| 3 | 3 | //! |
| 4 | //! ## What the shim does, and the one thing this does not reproduce | |
| 4 | //! ## What the shim does | |
| 5 | 5 | //! |
| 6 | 6 | //! The Kotlin builds a content URI from the event id, deletes through the |
| 7 | 7 | //! `ContentResolver`, and resolves `true`; on any exception it rejects with |
| 8 | 8 | //! the message. That is all ported. |
| 9 | 9 | //! |
| 10 | //! What is not ported is how it *sends* the rejection: | |
| 10 | //! The rejection is built as JSON through `bridge_error.appendJsonEscaped` | |
| 11 | //! rather than pasted into a quoted string. When this module was written the | |
| 12 | //! shim did the latter — | |
| 11 | 13 | //! |
| 12 | 14 | //! "window._craftDeleteEventReject && window._craftDeleteEventReject('${e.message}')" |
| 13 | 15 | //! |
| 14 | //! The message goes into a single-quoted JavaScript string with no escaping, | |
| 15 | //! and `eventId.toLong()` puts the caller's own text in that message — | |
| 16 | //! `NumberFormatException: For input string: "1'x"`. A `'` in the id therefore | |
| 17 | //! produces a script that does not parse, `evaluateJavascript` runs nothing, | |
| 18 | //! and **neither the resolve nor the reject fires**. The promise never | |
| 19 | //! settles, and the injected JS builds it by hand with no timeout, so the page | |
| 20 | //! waits forever with nothing in the console. | |
| 21 | //! | |
| 22 | //! Here the payload is built as JSON through `bridge_error.appendJsonEscaped`, | |
| 23 | //! so the same id rejects normally. That is a divergence in the page's favour | |
| 24 | //! and it is deliberate; the shim keeps the bug on the twenty other paths | |
| 25 | //! until #154 is fixed. | |
| 16 | //! — and `eventId.toLong()` puts the caller's own text in that message, so | |
| 17 | //! `craft.calendar.delete("1'x")` produced a script that did not parse: | |
| 18 | //! `evaluateJavascript` ran nothing, neither the resolve nor the reject fired, | |
| 19 | //! and the hand-built promise had no timeout to notice. #154 fixed all | |
| 20 | //! fifty-eight such emissions, and `android_reply_escaping_test` now keeps | |
| 21 | //! them fixed, so this is no longer a divergence — but the escaping stays | |
| 22 | //! here, because `events.settle` takes a payload that is already JSON and this | |
| 23 | //! module is what has to produce it. | |
| 26 | 24 | //! |
| 27 | 25 | //! ## `toLong()` is reproduced exactly, including what it refuses |
| 28 | 26 | //! |
| @@ -114,7 +112,8 @@ pub fn deleteEvent(j: Jni, activity: jobject, id: i64) !void { | ||
| 114 | 112 | /// Reject with `message`, escaped. |
| 115 | 113 | /// |
| 116 | 114 | /// The escaping is the whole point of this function existing rather than the |
| 117 | /// call being inline — see the module comment and #154. | |
| 115 | /// call being inline — `events.settle` sends the payload as written, so a raw | |
| 116 | /// message would emit a script that does not parse. See the module comment. | |
| 118 | 117 | pub fn rejectWith(allocator: std.mem.Allocator, message: []const u8) !void { |
| 119 | 118 | const payload = try rejectPayload(allocator, message); |
| 120 | 119 | defer allocator.free(payload); |
| @@ -171,9 +170,9 @@ test "an id Java would refuse is refused here too" { | ||
| 171 | 170 | } |
| 172 | 171 | |
| 173 | 172 | test "a rejection message with a quote is escaped rather than emitted raw" { |
| 174 | // The bug this action does not reproduce. On the shim, an id of `1'x` | |
| 175 | // produces `…Reject('For input string: "1'x"')`, which does not parse — so | |
| 176 | // evaluateJavascript runs nothing and the promise never settles. | |
| 173 | // The bug that used to be one line away. Before #154 the shim turned an id | |
| 174 | // of `1'x` into `…Reject('For input string: "1'x"')`, which does not parse | |
| 175 | // — so evaluateJavascript ran nothing and the promise never settled. | |
| 177 | 176 | const payload = try rejectPayload(testing.allocator, "For input string: \"1'x\""); |
| 178 | 177 | defer testing.allocator.free(payload); |
| 179 | 178 | |