1
2
3
4
5
6
7
8
9
10
11
12
13 package org.eclipse.jgit.transport;
14
15 import static java.nio.charset.StandardCharsets.UTF_8;
16
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.io.UncheckedIOException;
20 import java.text.MessageFormat;
21 import java.util.Iterator;
22
23 import org.eclipse.jgit.errors.PackProtocolException;
24 import org.eclipse.jgit.internal.JGitText;
25 import org.eclipse.jgit.lib.MutableObjectId;
26 import org.eclipse.jgit.util.IO;
27 import org.eclipse.jgit.util.RawParseUtils;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30
31
32
33
34
35
36
37
38
39
40
41 public class PacketLineIn {
42 private static final Logger log = LoggerFactory.getLogger(PacketLineIn.class);
43
44
45
46
47
48
49
50
51
52 @Deprecated
53 public static final String END = new String();
54
55
56
57
58
59
60
61
62 @Deprecated
63 public static final String DELIM = new String();
64
65 enum AckNackResult {
66
67 NAK,
68
69 ACK,
70
71 ACK_CONTINUE,
72
73 ACK_COMMON,
74
75 ACK_READY;
76 }
77
78 private final byte[] lineBuffer = new byte[SideBandOutputStream.SMALL_BUF];
79 private final InputStream in;
80 private long limit;
81
82
83
84
85
86
87
88 public PacketLineIn(InputStream in) {
89 this(in, 0);
90 }
91
92
93
94
95
96
97
98
99
100
101 public PacketLineIn(InputStream in, long limit) {
102 this.in = in;
103 this.limit = limit;
104 }
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119 static AckNackResult parseACKv2(String line, MutableObjectId returnedId)
120 throws IOException {
121 if ("NAK".equals(line)) {
122 return AckNackResult.NAK;
123 }
124 if (line.startsWith("ACK ") && line.length() == 44) {
125 returnedId.fromString(line.substring(4, 44));
126 return AckNackResult.ACK_COMMON;
127 }
128 if ("ready".equals(line)) {
129 return AckNackResult.ACK_READY;
130 }
131 if (line.startsWith("ERR ")) {
132 throw new PackProtocolException(line.substring(4));
133 }
134 throw new PackProtocolException(
135 MessageFormat.format(JGitText.get().expectedACKNAKGot, line));
136 }
137
138 AckNackResult readACK(MutableObjectId returnedId) throws IOException {
139 final String line = readString();
140 if (line.length() == 0)
141 throw new PackProtocolException(JGitText.get().expectedACKNAKFoundEOF);
142 if ("NAK".equals(line))
143 return AckNackResult.NAK;
144 if (line.startsWith("ACK ")) {
145 returnedId.fromString(line.substring(4, 44));
146 if (line.length() == 44)
147 return AckNackResult.ACK;
148
149 final String arg = line.substring(44);
150 switch (arg) {
151 case " continue":
152 return AckNackResult.ACK_CONTINUE;
153 case " common":
154 return AckNackResult.ACK_COMMON;
155 case " ready":
156 return AckNackResult.ACK_READY;
157 default:
158 break;
159 }
160 }
161 if (line.startsWith("ERR "))
162 throw new PackProtocolException(line.substring(4));
163 throw new PackProtocolException(MessageFormat.format(JGitText.get().expectedACKNAKGot, line));
164 }
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179 public String readString() throws IOException {
180 int len = readLength();
181 if (len == 0) {
182 log.debug("git< 0000");
183 return END;
184 }
185 if (len == 1) {
186 log.debug("git< 0001");
187 return DELIM;
188 }
189
190 len -= 4;
191 if (len == 0) {
192 log.debug("git< ");
193 return "";
194 }
195
196 byte[] raw;
197 if (len <= lineBuffer.length)
198 raw = lineBuffer;
199 else
200 raw = new byte[len];
201
202 IO.readFully(in, raw, 0, len);
203 if (raw[len - 1] == '\n')
204 len--;
205
206 String s = RawParseUtils.decode(UTF_8, raw, 0, len);
207 log.debug("git< " + s);
208 return s;
209 }
210
211
212
213
214
215
216
217
218
219
220
221 public PacketLineInIterator readStrings() throws IOException {
222 return new PacketLineInIterator(this);
223 }
224
225
226
227
228
229
230
231
232
233
234
235 public String readStringRaw() throws IOException {
236 int len = readLength();
237 if (len == 0) {
238 log.debug("git< 0000");
239 return END;
240 }
241
242 len -= 4;
243
244 byte[] raw;
245 if (len <= lineBuffer.length)
246 raw = lineBuffer;
247 else
248 raw = new byte[len];
249
250 IO.readFully(in, raw, 0, len);
251
252 String s = RawParseUtils.decode(UTF_8, raw, 0, len);
253 log.debug("git< " + s);
254 return s;
255 }
256
257
258
259
260
261
262
263
264
265 @SuppressWarnings({ "ReferenceEquality", "StringEquality" })
266 public static boolean isDelimiter(String s) {
267 return s == DELIM;
268 }
269
270
271
272
273
274
275
276
277 static String delimiter() {
278 return DELIM;
279 }
280
281
282
283
284
285
286
287
288 static String end() {
289 return END;
290 }
291
292
293
294
295
296
297
298
299
300 @SuppressWarnings({ "ReferenceEquality", "StringEquality" })
301 public static boolean isEnd(String s) {
302 return s == END;
303 }
304
305 void discardUntilEnd() throws IOException {
306 for (;;) {
307 int n = readLength();
308 if (n == 0) {
309 break;
310 }
311 IO.skipFully(in, n - 4);
312 }
313 }
314
315 int readLength() throws IOException {
316 IO.readFully(in, lineBuffer, 0, 4);
317 int len;
318 try {
319 len = RawParseUtils.parseHexInt16(lineBuffer, 0);
320 } catch (ArrayIndexOutOfBoundsException err) {
321 throw invalidHeader(err);
322 }
323
324 if (len == 0) {
325 return 0;
326 } else if (len == 1) {
327 return 1;
328 } else if (len < 4) {
329 throw invalidHeader();
330 }
331
332 if (limit != 0) {
333 int n = len - 4;
334 if (limit < n) {
335 limit = -1;
336 try {
337 IO.skipFully(in, n);
338 } catch (IOException e) {
339
340 }
341 throw new InputOverLimitIOException();
342 }
343
344 limit = n < limit ? limit - n : -1;
345 }
346 return len;
347 }
348
349 private IOException invalidHeader() {
350 return new IOException(MessageFormat.format(JGitText.get().invalidPacketLineHeader,
351 "" + (char) lineBuffer[0] + (char) lineBuffer[1]
352 + (char) lineBuffer[2] + (char) lineBuffer[3]));
353 }
354
355 private IOException invalidHeader(Throwable cause) {
356 IOException ioe = invalidHeader();
357 ioe.initCause(cause);
358 return ioe;
359 }
360
361
362
363
364
365
366 public static class InputOverLimitIOException extends IOException {
367 private static final long serialVersionUID = 1L;
368 }
369
370
371
372
373
374
375
376
377
378
379 public static class PacketLineInIterator implements Iterable<String> {
380 private PacketLineIn in;
381
382 private String current;
383
384 PacketLineInIterator(PacketLineIn in) throws IOException {
385 this.in = in;
386 current = in.readString();
387 }
388
389 @Override
390 public Iterator<String> iterator() {
391 return new Iterator<>() {
392
393 @Override
394 public boolean hasNext() {
395 return !PacketLineIn.isEnd(current);
396 }
397
398 @Override
399 public String next() {
400 String next = current;
401 try {
402 current = in.readString();
403 } catch (IOException e) {
404 throw new UncheckedIOException(e);
405 }
406 return next;
407 }
408 };
409 }
410
411 }
412 }