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 package org.eclipse.jgit.internal.storage.file;
47
48 import java.io.BufferedInputStream;
49 import java.io.ByteArrayInputStream;
50 import java.io.File;
51 import java.io.FileInputStream;
52 import java.io.FileNotFoundException;
53 import java.io.IOException;
54 import java.io.InputStream;
55 import java.util.zip.DataFormatException;
56 import java.util.zip.Inflater;
57 import java.util.zip.InflaterInputStream;
58 import java.util.zip.ZipException;
59
60 import org.eclipse.jgit.errors.CorruptObjectException;
61 import org.eclipse.jgit.errors.LargeObjectException;
62 import org.eclipse.jgit.errors.MissingObjectException;
63 import org.eclipse.jgit.internal.JGitText;
64 import org.eclipse.jgit.lib.AnyObjectId;
65 import org.eclipse.jgit.lib.Constants;
66 import org.eclipse.jgit.lib.InflaterCache;
67 import org.eclipse.jgit.lib.ObjectId;
68 import org.eclipse.jgit.lib.ObjectLoader;
69 import org.eclipse.jgit.lib.ObjectStream;
70 import org.eclipse.jgit.util.IO;
71 import org.eclipse.jgit.util.MutableInteger;
72 import org.eclipse.jgit.util.RawParseUtils;
73
74
75
76
77 public class UnpackedObject {
78 private static final int BUFFER_SIZE = 8192;
79
80
81
82
83
84
85
86
87
88
89
90
91
92 public static ObjectLoader parse(byte[] raw, AnyObjectId id)
93 throws IOException {
94 try (WindowCursor wc = new WindowCursor(null)) {
95 return open(new ByteArrayInputStream(raw), null, id, wc);
96 }
97 }
98
99 static ObjectLoader open(InputStream in, File path, AnyObjectId id,
100 WindowCursor wc) throws IOException {
101 try {
102 in = buffer(in);
103 in.mark(20);
104 final byte[] hdr = new byte[64];
105 IO.readFully(in, hdr, 0, 2);
106
107 if (isStandardFormat(hdr)) {
108 in.reset();
109 Inflater inf = wc.inflater();
110 InputStream zIn = inflate(in, inf);
111 int avail = readSome(zIn, hdr, 0, 64);
112 if (avail < 5)
113 throw new CorruptObjectException(id,
114 JGitText.get().corruptObjectNoHeader);
115
116 final MutableInteger p = new MutableInteger();
117 int type = Constants.decodeTypeString(id, hdr, (byte) ' ', p);
118 long size = RawParseUtils.parseLongBase10(hdr, p.value, p);
119 if (size < 0)
120 throw new CorruptObjectException(id,
121 JGitText.get().corruptObjectNegativeSize);
122 if (hdr[p.value++] != 0)
123 throw new CorruptObjectException(id,
124 JGitText.get().corruptObjectGarbageAfterSize);
125 if (path == null && Integer.MAX_VALUE < size) {
126 LargeObjectException.ExceedsByteArrayLimit e;
127 e = new LargeObjectException.ExceedsByteArrayLimit();
128 e.setObjectId(id);
129 throw e;
130 }
131 if (size < wc.getStreamFileThreshold() || path == null) {
132 byte[] data = new byte[(int) size];
133 int n = avail - p.value;
134 if (n > 0)
135 System.arraycopy(hdr, p.value, data, 0, n);
136 IO.readFully(zIn, data, n, data.length - n);
137 checkValidEndOfStream(in, inf, id, hdr);
138 return new ObjectLoader.SmallObject(type, data);
139 }
140 return new LargeObject(type, size, path, id, wc.db);
141
142 } else {
143 readSome(in, hdr, 2, 18);
144 int c = hdr[0] & 0xff;
145 int type = (c >> 4) & 7;
146 long size = c & 15;
147 int shift = 4;
148 int p = 1;
149 while ((c & 0x80) != 0) {
150 c = hdr[p++] & 0xff;
151 size += ((long) (c & 0x7f)) << shift;
152 shift += 7;
153 }
154
155 switch (type) {
156 case Constants.OBJ_COMMIT:
157 case Constants.OBJ_TREE:
158 case Constants.OBJ_BLOB:
159 case Constants.OBJ_TAG:
160
161 break;
162 default:
163 throw new CorruptObjectException(id,
164 JGitText.get().corruptObjectInvalidType);
165 }
166
167 if (path == null && Integer.MAX_VALUE < size) {
168 LargeObjectException.ExceedsByteArrayLimit e;
169 e = new LargeObjectException.ExceedsByteArrayLimit();
170 e.setObjectId(id);
171 throw e;
172 }
173 if (size < wc.getStreamFileThreshold() || path == null) {
174 in.reset();
175 IO.skipFully(in, p);
176 Inflater inf = wc.inflater();
177 InputStream zIn = inflate(in, inf);
178 byte[] data = new byte[(int) size];
179 IO.readFully(zIn, data, 0, data.length);
180 checkValidEndOfStream(in, inf, id, hdr);
181 return new ObjectLoader.SmallObject(type, data);
182 }
183 return new LargeObject(type, size, path, id, wc.db);
184 }
185 } catch (ZipException badStream) {
186 throw new CorruptObjectException(id,
187 JGitText.get().corruptObjectBadStream);
188 }
189 }
190
191 static long getSize(InputStream in, AnyObjectId id, WindowCursor wc)
192 throws IOException {
193 try {
194 in = buffer(in);
195 in.mark(20);
196 final byte[] hdr = new byte[64];
197 IO.readFully(in, hdr, 0, 2);
198
199 if (isStandardFormat(hdr)) {
200 in.reset();
201 Inflater inf = wc.inflater();
202 InputStream zIn = inflate(in, inf);
203 int avail = readSome(zIn, hdr, 0, 64);
204 if (avail < 5)
205 throw new CorruptObjectException(id,
206 JGitText.get().corruptObjectNoHeader);
207
208 final MutableInteger p = new MutableInteger();
209 Constants.decodeTypeString(id, hdr, (byte) ' ', p);
210 long size = RawParseUtils.parseLongBase10(hdr, p.value, p);
211 if (size < 0)
212 throw new CorruptObjectException(id,
213 JGitText.get().corruptObjectNegativeSize);
214 return size;
215
216 } else {
217 readSome(in, hdr, 2, 18);
218 int c = hdr[0] & 0xff;
219 long size = c & 15;
220 int shift = 4;
221 int p = 1;
222 while ((c & 0x80) != 0) {
223 c = hdr[p++] & 0xff;
224 size += ((long) (c & 0x7f)) << shift;
225 shift += 7;
226 }
227 return size;
228 }
229 } catch (ZipException badStream) {
230 throw new CorruptObjectException(id,
231 JGitText.get().corruptObjectBadStream);
232 }
233 }
234
235 static void checkValidEndOfStream(InputStream in, Inflater inf,
236 AnyObjectId id, final byte[] buf) throws IOException,
237 CorruptObjectException {
238 for (;;) {
239 int r;
240 try {
241 r = inf.inflate(buf);
242 } catch (DataFormatException e) {
243 throw new CorruptObjectException(id,
244 JGitText.get().corruptObjectBadStream);
245 }
246 if (r != 0)
247 throw new CorruptObjectException(id,
248 JGitText.get().corruptObjectIncorrectLength);
249
250 if (inf.finished()) {
251 if (inf.getRemaining() != 0 || in.read() != -1)
252 throw new CorruptObjectException(id,
253 JGitText.get().corruptObjectBadStream);
254 break;
255 }
256
257 if (!inf.needsInput())
258 throw new CorruptObjectException(id,
259 JGitText.get().corruptObjectBadStream);
260
261 r = in.read(buf);
262 if (r <= 0)
263 throw new CorruptObjectException(id,
264 JGitText.get().corruptObjectBadStream);
265 inf.setInput(buf, 0, r);
266 }
267 }
268
269 static boolean isStandardFormat(byte[] hdr) {
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297 final int fb = hdr[0] & 0xff;
298 return (fb & 0x8f) == 0x08 && (((fb << 8) | hdr[1] & 0xff) % 31) == 0;
299 }
300
301 static InputStream inflate(final InputStream in, final long size,
302 final ObjectId id) {
303 final Inflater inf = InflaterCache.get();
304 return new InflaterInputStream(in, inf) {
305 private long remaining = size;
306
307 @Override
308 public int read(byte[] b, int off, int cnt) throws IOException {
309 try {
310 int r = super.read(b, off, cnt);
311 if (r > 0)
312 remaining -= r;
313 return r;
314 } catch (ZipException badStream) {
315 throw new CorruptObjectException(id,
316 JGitText.get().corruptObjectBadStream);
317 }
318 }
319
320 @Override
321 public void close() throws IOException {
322 try {
323 if (remaining <= 0)
324 checkValidEndOfStream(in, inf, id, new byte[64]);
325 } finally {
326 InflaterCache.release(inf);
327 super.close();
328 }
329 }
330 };
331 }
332
333 private static InflaterInputStream inflate(InputStream in, Inflater inf) {
334 return new InflaterInputStream(in, inf, BUFFER_SIZE);
335 }
336
337 static BufferedInputStream buffer(InputStream in) {
338 return new BufferedInputStream(in, BUFFER_SIZE);
339 }
340
341 static int readSome(InputStream in, final byte[] hdr, int off,
342 int cnt) throws IOException {
343 int avail = 0;
344 while (0 < cnt) {
345 int n = in.read(hdr, off, cnt);
346 if (n < 0)
347 break;
348 avail += n;
349 off += n;
350 cnt -= n;
351 }
352 return avail;
353 }
354
355 private static final class LargeObject extends ObjectLoader {
356 private final int type;
357
358 private final long size;
359
360 private final File path;
361
362 private final ObjectId id;
363
364 private final FileObjectDatabase source;
365
366 LargeObject(int type, long size, File path, AnyObjectId id,
367 FileObjectDatabase db) {
368 this.type = type;
369 this.size = size;
370 this.path = path;
371 this.id = id.copy();
372 this.source = db;
373 }
374
375 @Override
376 public int getType() {
377 return type;
378 }
379
380 @Override
381 public long getSize() {
382 return size;
383 }
384
385 @Override
386 public boolean isLarge() {
387 return true;
388 }
389
390 @Override
391 public byte[] getCachedBytes() throws LargeObjectException {
392 throw new LargeObjectException(id);
393 }
394
395 @Override
396 public ObjectStream openStream() throws MissingObjectException,
397 IOException {
398 InputStream in;
399 try {
400 in = buffer(new FileInputStream(path));
401 } catch (FileNotFoundException gone) {
402 if (path.exists()) {
403 throw gone;
404 }
405
406
407
408
409 return source.open(id, type).openStream();
410 }
411
412 boolean ok = false;
413 try {
414 final byte[] hdr = new byte[64];
415 in.mark(20);
416 IO.readFully(in, hdr, 0, 2);
417
418 if (isStandardFormat(hdr)) {
419 in.reset();
420 in = buffer(inflate(in, size, id));
421 while (0 < in.read())
422 continue;
423 } else {
424 readSome(in, hdr, 2, 18);
425 int c = hdr[0] & 0xff;
426 int p = 1;
427 while ((c & 0x80) != 0)
428 c = hdr[p++] & 0xff;
429
430 in.reset();
431 IO.skipFully(in, p);
432 in = buffer(inflate(in, size, id));
433 }
434
435 ok = true;
436 return new ObjectStream.Filter(type, size, in);
437 } finally {
438 if (!ok)
439 in.close();
440 }
441 }
442 }
443 }