Skip to content

Commit 49cae60

Browse files
authored
fix(envelope) Add support for implicitly sized envelope items (#1229)
add implicitly sized items to envelope parsing
1 parent cad2f65 commit 49cae60

File tree

2 files changed

+141
-4
lines changed

2 files changed

+141
-4
lines changed

sentry_sdk/envelope.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -295,13 +295,18 @@ def deserialize_from(
295295
if not line:
296296
return None
297297
headers = parse_json(line)
298-
length = headers["length"]
299-
payload = f.read(length)
300-
if headers.get("type") in ("event", "transaction"):
298+
length = headers.get("length")
299+
if length is not None:
300+
payload = f.read(length)
301+
f.readline()
302+
else:
303+
# if no length was specified we need to read up to the end of line
304+
# and remove it (if it is present, i.e. not the very last char in an eof terminated envelope)
305+
payload = f.readline().rstrip(b"\n")
306+
if headers.get("type") in ("event", "transaction", "metric_buckets"):
301307
rv = cls(headers=headers, payload=PayloadRef(json=parse_json(payload)))
302308
else:
303309
rv = cls(headers=headers, payload=payload)
304-
f.readline()
305310
return rv
306311

307312
@classmethod

tests/test_envelope.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,135 @@ def test_envelope_headers(
132132
"event_id": "15210411201320122115110420122013",
133133
"sent_at": "2012-11-21T12:31:12.415908Z",
134134
}
135+
136+
137+
def test_envelope_with_sized_items():
138+
"""
139+
Tests that it successfully parses envelopes with
140+
the item size specified in the header
141+
"""
142+
envelope_raw = (
143+
b'{"event_id":"9ec79c33ec9942ab8353589fcb2e04dc"}\n'
144+
+ b'{"type":"type1","length":4 }\n1234\n'
145+
+ b'{"type":"type2","length":4 }\nabcd\n'
146+
+ b'{"type":"type3","length":0}\n\n'
147+
+ b'{"type":"type4","length":4 }\nab12\n'
148+
)
149+
envelope_raw_eof_terminated = envelope_raw[:-1]
150+
151+
for envelope_raw in (envelope_raw, envelope_raw_eof_terminated):
152+
actual = Envelope.deserialize(envelope_raw)
153+
154+
items = [item for item in actual]
155+
156+
assert len(items) == 4
157+
158+
assert items[0].type == "type1"
159+
assert items[0].get_bytes() == b"1234"
160+
161+
assert items[1].type == "type2"
162+
assert items[1].get_bytes() == b"abcd"
163+
164+
assert items[2].type == "type3"
165+
assert items[2].get_bytes() == b""
166+
167+
assert items[3].type == "type4"
168+
assert items[3].get_bytes() == b"ab12"
169+
170+
assert actual.headers["event_id"] == "9ec79c33ec9942ab8353589fcb2e04dc"
171+
172+
173+
def test_envelope_with_implicitly_sized_items():
174+
"""
175+
Tests that it successfully parses envelopes with
176+
the item size not specified in the header
177+
"""
178+
envelope_raw = (
179+
b'{"event_id":"9ec79c33ec9942ab8353589fcb2e04dc"}\n'
180+
+ b'{"type":"type1"}\n1234\n'
181+
+ b'{"type":"type2"}\nabcd\n'
182+
+ b'{"type":"type3"}\n\n'
183+
+ b'{"type":"type4"}\nab12\n'
184+
)
185+
envelope_raw_eof_terminated = envelope_raw[:-1]
186+
187+
for envelope_raw in (envelope_raw, envelope_raw_eof_terminated):
188+
actual = Envelope.deserialize(envelope_raw)
189+
assert actual.headers["event_id"] == "9ec79c33ec9942ab8353589fcb2e04dc"
190+
191+
items = [item for item in actual]
192+
193+
assert len(items) == 4
194+
195+
assert items[0].type == "type1"
196+
assert items[0].get_bytes() == b"1234"
197+
198+
assert items[1].type == "type2"
199+
assert items[1].get_bytes() == b"abcd"
200+
201+
assert items[2].type == "type3"
202+
assert items[2].get_bytes() == b""
203+
204+
assert items[3].type == "type4"
205+
assert items[3].get_bytes() == b"ab12"
206+
207+
208+
def test_envelope_with_two_attachments():
209+
"""
210+
Test that items are correctly parsed in an envelope with to size specified items
211+
"""
212+
two_attachments = (
213+
b'{"event_id":"9ec79c33ec9942ab8353589fcb2e04dc","dsn":"https://e12d836b15bb49d7bbf99e64295d995b:@sentry.io/42"}\n'
214+
+ b'{"type":"attachment","length":10,"content_type":"text/plain","filename":"hello.txt"}\n'
215+
+ b"\xef\xbb\xbfHello\r\n\n"
216+
+ b'{"type":"event","length":41,"content_type":"application/json","filename":"application.log"}\n'
217+
+ b'{"message":"hello world","level":"error"}\n'
218+
)
219+
two_attachments_eof_terminated = two_attachments[
220+
:-1
221+
] # last \n is optional, without it should still be a valid envelope
222+
223+
for envelope_raw in (two_attachments, two_attachments_eof_terminated):
224+
actual = Envelope.deserialize(envelope_raw)
225+
items = [item for item in actual]
226+
227+
assert len(items) == 2
228+
assert items[0].get_bytes() == b"\xef\xbb\xbfHello\r\n"
229+
assert items[1].payload.json == {"message": "hello world", "level": "error"}
230+
231+
232+
def test_envelope_with_empty_attachments():
233+
"""
234+
Test that items are correctly parsed in an envelope with two 0 length items (with size specified in the header
235+
"""
236+
two_empty_attachments = (
237+
b'{"event_id":"9ec79c33ec9942ab8353589fcb2e04dc"}\n'
238+
+ b'{"type":"attachment","length":0}\n\n'
239+
+ b'{"type":"attachment","length":0}\n\n'
240+
)
241+
242+
two_empty_attachments_eof_terminated = two_empty_attachments[
243+
:-1
244+
] # last \n is optional, without it should still be a valid envelope
245+
246+
for envelope_raw in (two_empty_attachments, two_empty_attachments_eof_terminated):
247+
actual = Envelope.deserialize(envelope_raw)
248+
items = [item for item in actual]
249+
250+
assert len(items) == 2
251+
assert items[0].get_bytes() == b""
252+
assert items[1].get_bytes() == b""
253+
254+
255+
def test_envelope_without_headers():
256+
"""
257+
Test that an envelope without headers is parsed successfully
258+
"""
259+
envelope_without_headers = (
260+
b"{}\n" + b'{"type":"session"}\n' + b'{"started": "2020-02-07T14:16:00Z"}'
261+
)
262+
actual = Envelope.deserialize(envelope_without_headers)
263+
items = [item for item in actual]
264+
265+
assert len(items) == 1
266+
assert items[0].payload.get_bytes() == b'{"started": "2020-02-07T14:16:00Z"}'

0 commit comments

Comments
 (0)