forked from getsentry/sentry-java
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathEnvelopeReader.java
More file actions
150 lines (130 loc) · 5.41 KB
/
EnvelopeReader.java
File metadata and controls
150 lines (130 loc) · 5.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package io.sentry;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ApiStatus.Internal
public final class EnvelopeReader implements IEnvelopeReader {
@SuppressWarnings("CharsetObjectCanBeUsed")
private static final Charset UTF_8 = Charset.forName("UTF-8");
private final Gson gson =
new GsonBuilder()
.registerTypeAdapter(SentryEnvelopeHeader.class, new SentryEnvelopeHeaderAdapter())
.registerTypeAdapter(
SentryEnvelopeItemHeader.class, new SentryEnvelopeItemHeaderAdapter())
.disableHtmlEscaping()
.create();
public @Override @Nullable SentryEnvelope read(final @NotNull InputStream stream)
throws IOException {
byte[] buffer = new byte[1024];
int currentLength;
int streamOffset = 0;
// Offset of the line break defining the end of the envelope header
int envelopeEndHeaderOffset = -1;
try (final ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
while ((currentLength = stream.read(buffer)) > 0) {
for (int i = 0; envelopeEndHeaderOffset == -1 && i < currentLength; i++) {
if (buffer[i] == '\n') {
envelopeEndHeaderOffset = streamOffset + i;
break;
}
}
outputStream.write(buffer, 0, currentLength);
streamOffset += currentLength;
}
// TODO: Work on the stream instead reading to the whole thing and allocating this array
byte[] envelopeBytes = outputStream.toByteArray();
if (envelopeBytes.length == 0) {
throw new IllegalArgumentException("Empty stream.");
}
if (envelopeEndHeaderOffset == -1) {
throw new IllegalArgumentException("Envelope contains no header.");
}
SentryEnvelopeHeader header =
deserializeEnvelopeHeader(envelopeBytes, 0, envelopeEndHeaderOffset);
if (header == null) {
throw new IllegalArgumentException("Envelope header is null.");
}
int itemHeaderStartOffset = envelopeEndHeaderOffset + 1;
int payloadEndOffsetExclusive;
List<SentryEnvelopeItem> items = new ArrayList<>();
do {
int lineBreakIndex = -1;
// Look from startHeaderOffset until line break to find next header
for (int i = itemHeaderStartOffset; i < envelopeBytes.length; i++) {
if (envelopeBytes[i] == '\n') {
lineBreakIndex = i;
break;
}
}
if (lineBreakIndex == -1) {
throw new IllegalArgumentException(
"Invalid envelope. Item at index '"
+ items.size()
+ "'. "
+ "has no header delimiter.");
}
SentryEnvelopeItemHeader itemHeader =
deserializeEnvelopeItemHeader(
envelopeBytes, itemHeaderStartOffset, lineBreakIndex - itemHeaderStartOffset);
if (itemHeader.getLength() <= 0) {
throw new IllegalArgumentException(
"Item header at index '"
+ items.size()
+ "' has an invalid value: '"
+ itemHeader.getLength()
+ "'.");
}
payloadEndOffsetExclusive = lineBreakIndex + itemHeader.getLength() + 1;
if (payloadEndOffsetExclusive > envelopeBytes.length) {
throw new IllegalArgumentException(
"Invalid length for item at index '"
+ items.size()
+ "'. "
+ "Item is '"
+ payloadEndOffsetExclusive
+ "' bytes. There are '"
+ envelopeBytes.length
+ "' in the buffer.");
}
// if 'to' parameter overflows, copyOfRange is happy to pretend nothing happened. Bound need
// checking.
byte[] envelopeItemBytes =
Arrays.copyOfRange(
envelopeBytes, lineBreakIndex + 1, payloadEndOffsetExclusive /* to is exclusive */);
SentryEnvelopeItem item = new SentryEnvelopeItem(itemHeader, envelopeItemBytes);
items.add(item);
if (payloadEndOffsetExclusive == envelopeBytes.length) {
// End of envelope
break;
} else if (payloadEndOffsetExclusive + 1 == envelopeBytes.length) {
// Envelope items can be closed with a final line break
if (envelopeBytes[payloadEndOffsetExclusive] == '\n') {
break;
} else {
throw new IllegalArgumentException("Envelope has invalid data following an item.");
}
}
itemHeaderStartOffset = payloadEndOffsetExclusive + 1; // Skip over delimiter
} while (true);
return new SentryEnvelope(header, items);
}
}
private SentryEnvelopeHeader deserializeEnvelopeHeader(byte[] buffer, int offset, int length) {
String json = new String(buffer, offset, length, UTF_8);
return gson.fromJson(json, SentryEnvelopeHeader.class);
}
private SentryEnvelopeItemHeader deserializeEnvelopeItemHeader(
byte[] buffer, int offset, int length) {
String json = new String(buffer, offset, length, UTF_8);
return gson.fromJson(json, SentryEnvelopeItemHeader.class);
}
}