View Javadoc
1   /*
2    * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
3    * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
4    * Copyright (C) 2010, Google Inc.
5    * and other copyright owners as documented in the project's IP log.
6    *
7    * This program and the accompanying materials are made available
8    * under the terms of the Eclipse Distribution License v1.0 which
9    * accompanies this distribution, is reproduced below, and is
10   * available at http://www.eclipse.org/org/documents/edl-v10.php
11   *
12   * All rights reserved.
13   *
14   * Redistribution and use in source and binary forms, with or
15   * without modification, are permitted provided that the following
16   * conditions are met:
17   *
18   * - Redistributions of source code must retain the above copyright
19   *   notice, this list of conditions and the following disclaimer.
20   *
21   * - Redistributions in binary form must reproduce the above
22   *   copyright notice, this list of conditions and the following
23   *   disclaimer in the documentation and/or other materials provided
24   *   with the distribution.
25   *
26   * - Neither the name of the Eclipse Foundation, Inc. nor the
27   *   names of its contributors may be used to endorse or promote
28   *   products derived from this software without specific prior
29   *   written permission.
30   *
31   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
32   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
33   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
34   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
35   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
36   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
37   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
38   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
39   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
40   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
43   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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   * Loose object loader. This class loads an object not stored in a pack.
76   */
77  public class UnpackedObject {
78  	private static final int BUFFER_SIZE = 8192;
79  
80  	/**
81  	 * Parse an object from the unpacked object format.
82  	 *
83  	 * @param raw
84  	 *            complete contents of the compressed object.
85  	 * @param id
86  	 *            expected ObjectId of the object, used only for error reporting
87  	 *            in exceptions.
88  	 * @return loader to read the inflated contents.
89  	 * @throws java.io.IOException
90  	 *             the object cannot be parsed.
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 					// Acceptable types for a loose object.
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 		 * We must determine if the buffer contains the standard
272 		 * zlib-deflated stream or the experimental format based
273 		 * on the in-pack object format. Compare the header byte
274 		 * for each format:
275 		 *
276 		 * RFC1950 zlib w/ deflate : 0www1000 : 0 <= www <= 7
277 		 * Experimental pack-based : Stttssss : ttt = 1,2,3,4
278 		 *
279 		 * If bit 7 is clear and bits 0-3 equal 8, the buffer MUST be
280 		 * in standard loose-object format, UNLESS it is a Git-pack
281 		 * format object *exactly* 8 bytes in size when inflated.
282 		 *
283 		 * However, RFC1950 also specifies that the 1st 16-bit word
284 		 * must be divisible by 31 - this checksum tells us our buffer
285 		 * is in the standard format, giving a false positive only if
286 		 * the 1st word of the Git-pack format object happens to be
287 		 * divisible by 31, ie:
288 		 *      ((byte0 * 256) + byte1) % 31 = 0
289 		 *   =>        0ttt10000www1000 % 31 = 0
290 		 *
291 		 * As it happens, this case can only arise for www=3 & ttt=1
292 		 * - ie, a Commit object, which would have to be 8 bytes in
293 		 * size. As no Commit can be that small, we find that the
294 		 * combination of these two criteria (bitmask & checksum)
295 		 * can always correctly determine the buffer format.
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 				// If the loose file no longer exists, it may have been
406 				// moved into a pack file in the mean time. Try again
407 				// to locate the object.
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 }