Skip to content

Commit 50c23b4

Browse files
committed
Validate timestamps when encoding JSON
Closes #437.
1 parent f58845f commit 50c23b4

4 files changed

Lines changed: 80 additions & 5 deletions

File tree

conformance/exemptions.txt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,10 @@ Required.Proto2.ProtobufInput.BadTag_OverlongVarint
1212
Required.Proto2.ProtobufInput.MessageSetEncoding.UnknownExtension.ProtobufOutput
1313
Required.Proto2.ProtobufInput.ValidMessageSetEncoding.OutOfOrderGroupsEntries.ProtobufOutput
1414
Required.Proto2.ProtobufInput.ValidMessageSetEncoding.ProtobufOutput
15-
Required.Proto3.DurationProtoNanosTooLarge.JsonOutput
16-
Required.Proto3.DurationProtoNanosTooSmall.JsonOutput
17-
Required.Proto3.DurationProtoNanosWrongSign.JsonOutput
18-
Required.Proto3.DurationProtoNanosWrongSignNegativeSecs.JsonOutput
1915
Required.Proto3.JsonInput.AnyEmpty.JsonOutput
2016
Required.Proto3.JsonInput.AnyWithNoType.JsonOutput
2117
Required.Proto3.JsonInput.AnyWktRepresentationWithBadType
2218
Required.Proto3.JsonInput.AnyWktRepresentationWithEmptyTypeAndValue
2319
Required.Proto3.ProtobufInput.BadTag_FieldNumberSlightlyTooHigh
2420
Required.Proto3.ProtobufInput.BadTag_FieldNumberTooHigh
2521
Required.Proto3.ProtobufInput.BadTag_OverlongVarint
26-
Required.Proto3.TimestampProtoNegativeNanos.JsonOutput

lib/protobuf/json/encode.ex

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ defmodule Protobuf.JSON.Encode do
1717
safe_enum_key: 2}
1818

1919
@duration_seconds_range -315_576_000_000..315_576_000_000
20+
@nanos_range -999_999_999..999_999_999
21+
@timestamp_nanos_range 0..999_999_999
2022

2123
@built_in_google_messages [
2224
Google.Protobuf.FieldMask,
@@ -51,6 +53,13 @@ defmodule Protobuf.JSON.Encode do
5153
%{seconds: seconds} when seconds not in @duration_seconds_range ->
5254
throw({:bad_duration, :seconds_outside_of_range, seconds})
5355

56+
%{nanos: nanos} when nanos not in @nanos_range ->
57+
throw({:bad_duration, :nanos_outside_of_range, nanos})
58+
59+
%{seconds: seconds, nanos: nanos}
60+
when (seconds > 0 and nanos < 0) or (seconds < 0 and nanos > 0) ->
61+
throw({:bad_duration, :seconds_and_nanos_different_signs, {seconds, nanos}})
62+
5463
%{seconds: seconds, nanos: 0} ->
5564
Integer.to_string(seconds) <> "s"
5665

@@ -63,6 +72,10 @@ defmodule Protobuf.JSON.Encode do
6372
def encodable(%mod{} = struct, _opts) when mod == Google.Protobuf.Timestamp do
6473
%{seconds: seconds, nanos: nanos} = struct
6574

75+
if nanos not in @timestamp_nanos_range do
76+
throw({:invalid_timestamp, struct, "nanos outside of allowed range 0..999999999"})
77+
end
78+
6679
case Protobuf.JSON.RFC3339.encode(seconds, nanos) do
6780
{:ok, string} -> string
6881
{:error, reason} -> throw({:invalid_timestamp, struct, reason})

lib/protobuf/json/encode_error.ex

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ defmodule Protobuf.JSON.EncodeError do
1717
}
1818
end
1919

20+
def new({:bad_duration, :nanos_outside_of_range, nanos}) do
21+
%__MODULE__{
22+
message:
23+
"invalid Google.Protobuf.Duration, nanos must be in -999999999..999999999, got: #{nanos}"
24+
}
25+
end
26+
27+
def new({:bad_duration, :seconds_and_nanos_different_signs, {seconds, nanos}}) do
28+
%__MODULE__{
29+
message:
30+
"invalid Google.Protobuf.Duration, seconds and nanos must have the same sign, " <>
31+
"got seconds #{seconds} and nanos #{nanos}"
32+
}
33+
end
34+
2035
def new({:invalid_timestamp, timestamp, reason}) do
2136
%__MODULE__{
2237
message:

test/protobuf/json/encode_test.exs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,42 @@ defmodule Protobuf.JSON.EncodeTest do
414414
assert catch_throw(encode(message)) ==
415415
{:bad_duration, :seconds_outside_of_range, -max_duration - 1}
416416
end
417+
418+
test "returns an error if the nanos of the duration are too large" do
419+
message = %TestAllTypesProto3{
420+
optional_duration: %Google.Protobuf.Duration{seconds: 1, nanos: 1_000_000_000}
421+
}
422+
423+
assert catch_throw(encode(message)) ==
424+
{:bad_duration, :nanos_outside_of_range, 1_000_000_000}
425+
end
426+
427+
test "returns an error if the nanos of the duration are too small" do
428+
message = %TestAllTypesProto3{
429+
optional_duration: %Google.Protobuf.Duration{seconds: -1, nanos: -1_000_000_000}
430+
}
431+
432+
assert catch_throw(encode(message)) ==
433+
{:bad_duration, :nanos_outside_of_range, -1_000_000_000}
434+
end
435+
436+
test "returns an error if the seconds and nanos have different signs (positive seconds)" do
437+
message = %TestAllTypesProto3{
438+
optional_duration: %Google.Protobuf.Duration{seconds: 1, nanos: -1}
439+
}
440+
441+
assert catch_throw(encode(message)) ==
442+
{:bad_duration, :seconds_and_nanos_different_signs, {1, -1}}
443+
end
444+
445+
test "returns an error if the seconds and nanos have different signs (negative seconds)" do
446+
message = %TestAllTypesProto3{
447+
optional_duration: %Google.Protobuf.Duration{seconds: -1, nanos: 1}
448+
}
449+
450+
assert catch_throw(encode(message)) ==
451+
{:bad_duration, :seconds_and_nanos_different_signs, {-1, 1}}
452+
end
417453
end
418454

419455
describe "Google.Protobuf.Timestamp" do
@@ -437,6 +473,22 @@ defmodule Protobuf.JSON.EncodeTest do
437473
assert catch_throw(encode(message)) ==
438474
{:invalid_timestamp, timestamp, "timestamp is outside of allowed range"}
439475
end
476+
477+
test "returns an error if the nanos of the timestamp are negative" do
478+
timestamp = %Google.Protobuf.Timestamp{seconds: 0, nanos: -1}
479+
message = %TestAllTypesProto3{optional_timestamp: timestamp}
480+
481+
assert catch_throw(encode(message)) ==
482+
{:invalid_timestamp, timestamp, "nanos outside of allowed range 0..999999999"}
483+
end
484+
485+
test "returns an error if the nanos of the timestamp are too large" do
486+
timestamp = %Google.Protobuf.Timestamp{seconds: 0, nanos: 1_000_000_000}
487+
message = %TestAllTypesProto3{optional_timestamp: timestamp}
488+
489+
assert catch_throw(encode(message)) ==
490+
{:invalid_timestamp, timestamp, "nanos outside of allowed range 0..999999999"}
491+
end
440492
end
441493

442494
describe "Google.Protobuf.Value" do

0 commit comments

Comments
 (0)