Skip to content

Commit 1b20581

Browse files
authored
Fix invalid JSON in :message (#867)
1 parent 16229ef commit 1b20581

File tree

3 files changed

+52
-7
lines changed

3 files changed

+52
-7
lines changed

lib/sentry/client.ex

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -283,14 +283,12 @@ defmodule Sentry.Client do
283283
|> Event.remove_non_payload_keys()
284284
|> update_if_present(:breadcrumbs, fn bcs -> Enum.map(bcs, &Map.from_struct/1) end)
285285
|> update_if_present(:sdk, &Map.from_struct/1)
286-
|> update_if_present(:message, fn message ->
287-
message = update_in(message.formatted, &String.slice(&1, 0, @max_message_length))
288-
Map.from_struct(message)
289-
end)
286+
|> update_if_present(:message, &sanitize_message(&1, json_library))
290287
|> update_if_present(:request, &(&1 |> Map.from_struct() |> remove_nils()))
291288
|> update_if_present(:extra, &sanitize_non_jsonable_values(&1, json_library))
292289
|> update_if_present(:user, &sanitize_non_jsonable_values(&1, json_library))
293290
|> update_if_present(:tags, &sanitize_non_jsonable_values(&1, json_library))
291+
|> update_if_present(:fingerprint, &sanitize_non_jsonable_values_in_list(&1, json_library))
294292
|> update_if_present(:exception, fn list -> Enum.map(list, &render_exception/1) end)
295293
|> update_if_present(:threads, fn list -> Enum.map(list, &render_thread/1) end)
296294
end
@@ -332,6 +330,23 @@ defmodule Sentry.Client do
332330
:maps.filter(fn _key, value -> not is_nil(value) end, map)
333331
end
334332

333+
defp sanitize_message(%Interfaces.Message{} = message, json_library) do
334+
message = update_in(message.formatted, &String.slice(&1, 0, @max_message_length))
335+
336+
message
337+
|> Map.from_struct()
338+
|> sanitize_non_jsonable_values(json_library)
339+
end
340+
341+
defp sanitize_non_jsonable_values_in_list(list, json_library) when is_list(list) do
342+
Enum.map(list, fn elem ->
343+
case sanitize_non_jsonable_value(elem, json_library) do
344+
:unchanged -> elem
345+
{:changed, value} -> value
346+
end
347+
end)
348+
end
349+
335350
defp sanitize_non_jsonable_values(map, json_library) do
336351
# We update the existing map instead of building a new one from scratch
337352
# due to performance reasons. See the docs for :maps.map/2.
@@ -344,8 +359,11 @@ defmodule Sentry.Client do
344359
end
345360

346361
# For performance, skip all the keys that we know for sure are JSON encodable.
362+
# Binaries are a little nasty: something like <<1, 2, 3>> can be encoded in JSON,
363+
# but other non-printable binaries can't. That's why we skip them here and we
364+
# let them fall through to when we try to JSON-encode and inspect if that fails.
347365
defp sanitize_non_jsonable_value(value, _json_library)
348-
when is_binary(value) or is_number(value) or is_boolean(value) or is_nil(value) do
366+
when is_number(value) or is_boolean(value) or is_nil(value) do
349367
:unchanged
350368
end
351369

lib/sentry/event.ex

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,11 @@ defmodule Sentry.Event do
284284
{iodata, _params} =
285285
Enum.reduce(parts, {"", params}, fn
286286
"%s", {acc, [param | rest_params]} ->
287-
{[acc, to_string(param)], rest_params}
287+
cond do
288+
is_binary(param) and String.printable?(param) -> {[acc, param], rest_params}
289+
is_binary(param) -> {[acc, inspect(param)], rest_params}
290+
true -> {[acc, to_string(param)], rest_params}
291+
end
288292

289293
"%s", {acc, []} ->
290294
{[acc, "%s"], []}

test/sentry/client_test.exs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,26 @@ defmodule Sentry.ClientTest do
3030
assert Client.render_event(event).message.formatted == String.duplicate("a", max_length)
3131
end
3232

33+
test "sanitizes non-JSON-encodable messages without interpolation" do
34+
event = Event.create_event(message: <<133, 248, 45>>)
35+
36+
assert Client.render_event(event).message == %{
37+
message: nil,
38+
params: nil,
39+
formatted: "<<133, 248, 45>>"
40+
}
41+
end
42+
43+
test "sanitizes non-JSON-encodable messages with interpolation" do
44+
event = Event.create_event(message: "Invalid: %s", interpolation_parameters: [<<133>>])
45+
46+
assert Client.render_event(event).message == %{
47+
message: "Invalid: %s",
48+
params: ["<<133>>"],
49+
formatted: "Invalid: <<133>>"
50+
}
51+
end
52+
3353
test "safely inspects terms that cannot be converted to JSON" do
3454
event =
3555
Event.create_event(
@@ -44,7 +64,8 @@ defmodule Sentry.ClientTest do
4464
map: %{bool: false}
4565
},
4666
user: %{id: "valid-ID", email: {"user", "@example.com"}},
47-
tags: %{valid: "yes", tokens: MapSet.new([1])}
67+
tags: %{valid: "yes", tokens: MapSet.new([1])},
68+
fingerprint: [<<133>>]
4869
)
4970

5071
rendered = Client.render_event(event)
@@ -63,6 +84,8 @@ defmodule Sentry.ClientTest do
6384

6485
assert rendered.tags.valid == "yes"
6586
assert rendered.tags.tokens == inspect(MapSet.new([1]))
87+
88+
assert rendered.fingerprint == [~s(<<133>>)]
6689
end
6790

6891
test "works if the JSON library crashes" do

0 commit comments

Comments
 (0)