1
2
3
4
5
6
7
8
9
10
11
12 package org.eclipse.jgit.lib;
13
14 import static org.eclipse.jgit.lib.Constants.DOT_GIT_MODULES;
15 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
16 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
17 import static org.eclipse.jgit.lib.Constants.OBJ_BAD;
18 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
19 import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
20 import static org.eclipse.jgit.lib.Constants.OBJ_TAG;
21 import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
22 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_DATE;
23 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_EMAIL;
24 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_OBJECT_SHA1;
25 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_PARENT_SHA1;
26 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_TIMEZONE;
27 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_TREE_SHA1;
28 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_UTF8;
29 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.DUPLICATE_ENTRIES;
30 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.EMPTY_NAME;
31 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.FULL_PATHNAME;
32 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOT;
33 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTDOT;
34 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTGIT;
35 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_AUTHOR;
36 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_COMMITTER;
37 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_EMAIL;
38 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_OBJECT;
39 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_SPACE_BEFORE_DATE;
40 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TAG_ENTRY;
41 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TREE;
42 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TYPE_ENTRY;
43 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.NULL_SHA1;
44 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.TREE_NOT_SORTED;
45 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.UNKNOWN_TYPE;
46 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.WIN32_BAD_NAME;
47 import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE;
48 import static org.eclipse.jgit.util.Paths.compare;
49 import static org.eclipse.jgit.util.Paths.compareSameName;
50 import static org.eclipse.jgit.util.RawParseUtils.nextLF;
51 import static org.eclipse.jgit.util.RawParseUtils.parseBase10;
52
53 import java.text.MessageFormat;
54 import java.text.Normalizer;
55 import java.util.ArrayList;
56 import java.util.EnumSet;
57 import java.util.HashSet;
58 import java.util.List;
59 import java.util.Locale;
60 import java.util.Set;
61
62 import org.eclipse.jgit.annotations.NonNull;
63 import org.eclipse.jgit.annotations.Nullable;
64 import org.eclipse.jgit.errors.CorruptObjectException;
65 import org.eclipse.jgit.internal.JGitText;
66 import org.eclipse.jgit.util.MutableInteger;
67 import org.eclipse.jgit.util.RawParseUtils;
68 import org.eclipse.jgit.util.StringUtils;
69
70
71
72
73
74
75
76
77
78
79
80
81
82 public class ObjectChecker {
83
84 public static final byte[] tree = Constants.encodeASCII("tree ");
85
86
87 public static final byte[] parent = Constants.encodeASCII("parent ");
88
89
90 public static final byte[] author = Constants.encodeASCII("author ");
91
92
93 public static final byte[] committer = Constants.encodeASCII("committer ");
94
95
96 public static final byte[] encoding = Constants.encodeASCII("encoding ");
97
98
99 public static final byte[] object = Constants.encodeASCII("object ");
100
101
102 public static final byte[] type = Constants.encodeASCII("type ");
103
104
105 public static final byte[] tag = Constants.encodeASCII("tag ");
106
107
108 public static final byte[] tagger = Constants.encodeASCII("tagger ");
109
110
111 private static final byte[] dotGitmodules = Constants.encodeASCII(DOT_GIT_MODULES);
112
113
114
115
116
117
118 public enum ErrorType {
119
120
121 NULL_SHA1,
122 DUPLICATE_ENTRIES,
123 TREE_NOT_SORTED,
124 ZERO_PADDED_FILEMODE,
125 EMPTY_NAME,
126 FULL_PATHNAME,
127 HAS_DOT,
128 HAS_DOTDOT,
129 HAS_DOTGIT,
130 BAD_OBJECT_SHA1,
131 BAD_PARENT_SHA1,
132 BAD_TREE_SHA1,
133 MISSING_AUTHOR,
134 MISSING_COMMITTER,
135 MISSING_OBJECT,
136 MISSING_TREE,
137 MISSING_TYPE_ENTRY,
138 MISSING_TAG_ENTRY,
139 BAD_DATE,
140 BAD_EMAIL,
141 BAD_TIMEZONE,
142 MISSING_EMAIL,
143 MISSING_SPACE_BEFORE_DATE,
144 GITMODULES_BLOB,
145 GITMODULES_LARGE,
146 GITMODULES_NAME,
147 GITMODULES_PARSE,
148 GITMODULES_PATH,
149 GITMODULES_SYMLINK,
150 GITMODULES_URL,
151 UNKNOWN_TYPE,
152
153
154 WIN32_BAD_NAME,
155 BAD_UTF8;
156
157
158
159 public String getMessageId() {
160 String n = name();
161 StringBuilder r = new StringBuilder(n.length());
162 for (int i = 0; i < n.length(); i++) {
163 char c = n.charAt(i);
164 if (c != '_') {
165 r.append(StringUtils.toLowerCase(c));
166 } else {
167 r.append(n.charAt(++i));
168 }
169 }
170 return r.toString();
171 }
172 }
173
174 private final MutableObjectIdhtml#MutableObjectId">MutableObjectId tempId = new MutableObjectId();
175 private final MutableInteger.html#MutableInteger">MutableInteger bufPtr = new MutableInteger();
176
177 private EnumSet<ErrorType> errors = EnumSet.allOf(ErrorType.class);
178 private ObjectIdSet skipList;
179 private boolean allowInvalidPersonIdent;
180 private boolean windows;
181 private boolean macosx;
182
183 private final List<GitmoduleEntry> gitsubmodules = new ArrayList<>();
184
185
186
187
188
189
190
191
192
193
194 public ObjectChecker setSkipList(@Nullable ObjectIdSet objects) {
195 skipList = objects;
196 return this;
197 }
198
199
200
201
202
203
204
205
206
207 public ObjectChecker setIgnore(@Nullable Set<ErrorType> ids) {
208 errors = EnumSet.allOf(ErrorType.class);
209 if (ids != null) {
210 errors.removeAll(ids);
211 }
212 return this;
213 }
214
215
216
217
218
219
220
221
222
223
224
225
226 public ObjectChecker setIgnore(ErrorType id, boolean ignore) {
227 if (ignore) {
228 errors.remove(id);
229 } else {
230 errors.add(id);
231 }
232 return this;
233 }
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249 public ObjectChecker setAllowLeadingZeroFileMode(boolean allow) {
250 return setIgnore(ZERO_PADDED_FILEMODE, allow);
251 }
252
253
254
255
256
257
258
259
260
261
262
263
264 public ObjectChecker setAllowInvalidPersonIdent(boolean allow) {
265 allowInvalidPersonIdent = allow;
266 return this;
267 }
268
269
270
271
272
273
274
275
276
277
278 public ObjectChecker setSafeForWindows(boolean win) {
279 windows = win;
280 return this;
281 }
282
283
284
285
286
287
288
289
290
291
292
293 public ObjectChecker setSafeForMacOS(boolean mac) {
294 macosx = mac;
295 return this;
296 }
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311 public void check(int objType, byte[] raw)
312 throws CorruptObjectException {
313 check(idFor(objType, raw), objType, raw);
314 }
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332 public void check(@Nullable AnyObjectId id, int objType, byte[] raw)
333 throws CorruptObjectException {
334 switch (objType) {
335 case OBJ_COMMIT:
336 checkCommit(id, raw);
337 break;
338 case OBJ_TAG:
339 checkTag(id, raw);
340 break;
341 case OBJ_TREE:
342 checkTree(id, raw);
343 break;
344 case OBJ_BLOB:
345 BlobObjectChecker checker = newBlobObjectChecker();
346 if (checker == null) {
347 checkBlob(raw);
348 } else {
349 checker.update(raw, 0, raw.length);
350 checker.endBlob(id);
351 }
352 break;
353 default:
354 report(UNKNOWN_TYPE, id, MessageFormat.format(
355 JGitText.get().corruptObjectInvalidType2,
356 Integer.valueOf(objType)));
357 }
358 }
359
360 private boolean checkId(byte[] raw) {
361 int p = bufPtr.value;
362 try {
363 tempId.fromString(raw, p);
364 } catch (IllegalArgumentException e) {
365 bufPtr.value = nextLF(raw, p);
366 return false;
367 }
368
369 p += OBJECT_ID_STRING_LENGTH;
370 if (raw[p] == '\n') {
371 bufPtr.value = p + 1;
372 return true;
373 }
374 bufPtr.value = nextLF(raw, p);
375 return false;
376 }
377
378 private void checkPersonIdent(byte[] raw, @Nullable AnyObjectId id)
379 throws CorruptObjectException {
380 if (allowInvalidPersonIdent) {
381 bufPtr.value = nextLF(raw, bufPtr.value);
382 return;
383 }
384
385 final int emailB = nextLF(raw, bufPtr.value, '<');
386 if (emailB == bufPtr.value || raw[emailB - 1] != '<') {
387 report(MISSING_EMAIL, id, JGitText.get().corruptObjectMissingEmail);
388 bufPtr.value = nextLF(raw, bufPtr.value);
389 return;
390 }
391
392 final int emailE = nextLF(raw, emailB, '>');
393 if (emailE == emailB || raw[emailE - 1] != '>') {
394 report(BAD_EMAIL, id, JGitText.get().corruptObjectBadEmail);
395 bufPtr.value = nextLF(raw, bufPtr.value);
396 return;
397 }
398 if (emailE == raw.length || raw[emailE] != ' ') {
399 report(MISSING_SPACE_BEFORE_DATE, id,
400 JGitText.get().corruptObjectBadDate);
401 bufPtr.value = nextLF(raw, bufPtr.value);
402 return;
403 }
404
405 parseBase10(raw, emailE + 1, bufPtr);
406 if (emailE + 1 == bufPtr.value || bufPtr.value == raw.length
407 || raw[bufPtr.value] != ' ') {
408 report(BAD_DATE, id, JGitText.get().corruptObjectBadDate);
409 bufPtr.value = nextLF(raw, bufPtr.value);
410 return;
411 }
412
413 int p = bufPtr.value + 1;
414 parseBase10(raw, p, bufPtr);
415 if (p == bufPtr.value) {
416 report(BAD_TIMEZONE, id, JGitText.get().corruptObjectBadTimezone);
417 bufPtr.value = nextLF(raw, bufPtr.value);
418 return;
419 }
420
421 p = bufPtr.value;
422 if (raw[p] == '\n') {
423 bufPtr.value = p + 1;
424 } else {
425 report(BAD_TIMEZONE, id, JGitText.get().corruptObjectBadTimezone);
426 bufPtr.value = nextLF(raw, p);
427 }
428 }
429
430
431
432
433
434
435
436
437
438 public void checkCommit(byte[] raw) throws CorruptObjectException {
439 checkCommit(idFor(OBJ_COMMIT, raw), raw);
440 }
441
442
443
444
445
446
447
448
449
450
451
452
453 public void checkCommit(@Nullable AnyObjectId id, byte[] raw)
454 throws CorruptObjectException {
455 bufPtr.value = 0;
456
457 if (!match(raw, tree)) {
458 report(MISSING_TREE, id, JGitText.get().corruptObjectNotreeHeader);
459 } else if (!checkId(raw)) {
460 report(BAD_TREE_SHA1, id, JGitText.get().corruptObjectInvalidTree);
461 }
462
463 while (match(raw, parent)) {
464 if (!checkId(raw)) {
465 report(BAD_PARENT_SHA1, id,
466 JGitText.get().corruptObjectInvalidParent);
467 }
468 }
469
470 if (match(raw, author)) {
471 checkPersonIdent(raw, id);
472 } else {
473 report(MISSING_AUTHOR, id, JGitText.get().corruptObjectNoAuthor);
474 }
475
476 if (match(raw, committer)) {
477 checkPersonIdent(raw, id);
478 } else {
479 report(MISSING_COMMITTER, id,
480 JGitText.get().corruptObjectNoCommitter);
481 }
482 }
483
484
485
486
487
488
489
490
491
492 public void checkTag(byte[] raw) throws CorruptObjectException {
493 checkTag(idFor(OBJ_TAG, raw), raw);
494 }
495
496
497
498
499
500
501
502
503
504
505
506
507 public void checkTag(@Nullable AnyObjectId id, byte[] raw)
508 throws CorruptObjectException {
509 bufPtr.value = 0;
510 if (!match(raw, object)) {
511 report(MISSING_OBJECT, id,
512 JGitText.get().corruptObjectNoObjectHeader);
513 } else if (!checkId(raw)) {
514 report(BAD_OBJECT_SHA1, id,
515 JGitText.get().corruptObjectInvalidObject);
516 }
517
518 if (!match(raw, type)) {
519 report(MISSING_TYPE_ENTRY, id,
520 JGitText.get().corruptObjectNoTypeHeader);
521 }
522 bufPtr.value = nextLF(raw, bufPtr.value);
523
524 if (!match(raw, tag)) {
525 report(MISSING_TAG_ENTRY, id,
526 JGitText.get().corruptObjectNoTagHeader);
527 }
528 bufPtr.value = nextLF(raw, bufPtr.value);
529
530 if (match(raw, tagger)) {
531 checkPersonIdent(raw, id);
532 }
533 }
534
535 private static boolean duplicateName(final byte[] raw,
536 final int thisNamePos, final int thisNameEnd) {
537 final int sz = raw.length;
538 int nextPtr = thisNameEnd + 1 + Constants.OBJECT_ID_LENGTH;
539 for (;;) {
540 int nextMode = 0;
541 for (;;) {
542 if (nextPtr >= sz)
543 return false;
544 final byte c = raw[nextPtr++];
545 if (' ' == c)
546 break;
547 nextMode <<= 3;
548 nextMode += c - '0';
549 }
550
551 final int nextNamePos = nextPtr;
552 for (;;) {
553 if (nextPtr == sz)
554 return false;
555 final byte c = raw[nextPtr++];
556 if (c == 0)
557 break;
558 }
559 if (nextNamePos + 1 == nextPtr)
560 return false;
561
562 int cmp = compareSameName(
563 raw, thisNamePos, thisNameEnd,
564 raw, nextNamePos, nextPtr - 1, nextMode);
565 if (cmp < 0)
566 return false;
567 else if (cmp == 0)
568 return true;
569
570 nextPtr += Constants.OBJECT_ID_LENGTH;
571 }
572 }
573
574
575
576
577
578
579
580
581
582 public void checkTree(byte[] raw) throws CorruptObjectException {
583 checkTree(idFor(OBJ_TREE, raw), raw);
584 }
585
586
587
588
589
590
591
592
593
594
595
596
597 public void checkTree(@Nullable AnyObjectId id, byte[] raw)
598 throws CorruptObjectException {
599 final int sz = raw.length;
600 int ptr = 0;
601 int lastNameB = 0, lastNameE = 0, lastMode = 0;
602 Set<String> normalized = windows || macosx
603 ? new HashSet<>()
604 : null;
605
606 while (ptr < sz) {
607 int thisMode = 0;
608 for (;;) {
609 if (ptr == sz) {
610 throw new CorruptObjectException(
611 JGitText.get().corruptObjectTruncatedInMode);
612 }
613 final byte c = raw[ptr++];
614 if (' ' == c)
615 break;
616 if (c < '0' || c > '7') {
617 throw new CorruptObjectException(
618 JGitText.get().corruptObjectInvalidModeChar);
619 }
620 if (thisMode == 0 && c == '0') {
621 report(ZERO_PADDED_FILEMODE, id,
622 JGitText.get().corruptObjectInvalidModeStartsZero);
623 }
624 thisMode <<= 3;
625 thisMode += c - '0';
626 }
627
628 if (FileMode.fromBits(thisMode).getObjectType() == OBJ_BAD) {
629 throw new CorruptObjectException(MessageFormat.format(
630 JGitText.get().corruptObjectInvalidMode2,
631 Integer.valueOf(thisMode)));
632 }
633
634 final int thisNameB = ptr;
635 ptr = scanPathSegment(raw, ptr, sz, id);
636 if (ptr == sz || raw[ptr] != 0) {
637 throw new CorruptObjectException(
638 JGitText.get().corruptObjectTruncatedInName);
639 }
640 checkPathSegment2(raw, thisNameB, ptr, id);
641 if (normalized != null) {
642 if (!normalized.add(normalize(raw, thisNameB, ptr))) {
643 report(DUPLICATE_ENTRIES, id,
644 JGitText.get().corruptObjectDuplicateEntryNames);
645 }
646 } else if (duplicateName(raw, thisNameB, ptr)) {
647 report(DUPLICATE_ENTRIES, id,
648 JGitText.get().corruptObjectDuplicateEntryNames);
649 }
650
651 if (lastNameB != 0) {
652 int cmp = compare(
653 raw, lastNameB, lastNameE, lastMode,
654 raw, thisNameB, ptr, thisMode);
655 if (cmp > 0) {
656 report(TREE_NOT_SORTED, id,
657 JGitText.get().corruptObjectIncorrectSorting);
658 }
659 }
660
661 lastNameB = thisNameB;
662 lastNameE = ptr;
663 lastMode = thisMode;
664
665 ptr += 1 + OBJECT_ID_LENGTH;
666 if (ptr > sz) {
667 throw new CorruptObjectException(
668 JGitText.get().corruptObjectTruncatedInObjectId);
669 }
670
671 if (ObjectId.zeroId().compareTo(raw, ptr - OBJECT_ID_LENGTH) == 0) {
672 report(NULL_SHA1, id, JGitText.get().corruptObjectZeroId);
673 }
674
675 if (id != null && isGitmodules(raw, lastNameB, lastNameE, id)) {
676 ObjectId blob = ObjectId.fromRaw(raw, ptr - OBJECT_ID_LENGTH);
677 gitsubmodules.add(new GitmoduleEntry(id, blob));
678 }
679 }
680 }
681
682 private int scanPathSegment(byte[] raw, int ptr, int end,
683 @Nullable AnyObjectId id) throws CorruptObjectException {
684 for (; ptr < end; ptr++) {
685 byte c = raw[ptr];
686 if (c == 0) {
687 return ptr;
688 }
689 if (c == '/') {
690 report(FULL_PATHNAME, id,
691 JGitText.get().corruptObjectNameContainsSlash);
692 }
693 if (windows && isInvalidOnWindows(c)) {
694 if (c > 31) {
695 throw new CorruptObjectException(String.format(
696 JGitText.get().corruptObjectNameContainsChar,
697 Byte.valueOf(c)));
698 }
699 throw new CorruptObjectException(String.format(
700 JGitText.get().corruptObjectNameContainsByte,
701 Integer.valueOf(c & 0xff)));
702 }
703 }
704 return ptr;
705 }
706
707 @Nullable
708 private ObjectId idFor(int objType, byte[] raw) {
709 if (skipList != null) {
710 try (ObjectInserter.Formatter fmt = new ObjectInserter.Formatter()) {
711 return fmt.idFor(objType, raw);
712 }
713 }
714 return null;
715 }
716
717 private void report(@NonNull ErrorType err, @Nullable AnyObjectId id,
718 String why) throws CorruptObjectException {
719 if (errors.contains(err)
720 && (id == null || skipList == null || !skipList.contains(id))) {
721 if (id != null) {
722 throw new CorruptObjectException(err, id, why);
723 }
724 throw new CorruptObjectException(why);
725 }
726 }
727
728
729
730
731
732
733
734
735
736
737
738
739
740 public void checkPath(String path) throws CorruptObjectException {
741 byte[] buf = Constants.encode(path);
742 checkPath(buf, 0, buf.length);
743 }
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761 public void checkPath(byte[] raw, int ptr, int end)
762 throws CorruptObjectException {
763 int start = ptr;
764 for (; ptr < end; ptr++) {
765 if (raw[ptr] == '/') {
766 checkPathSegment(raw, start, ptr);
767 start = ptr + 1;
768 }
769 }
770 checkPathSegment(raw, start, end);
771 }
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786 public void checkPathSegment(byte[] raw, int ptr, int end)
787 throws CorruptObjectException {
788 int e = scanPathSegment(raw, ptr, end, null);
789 if (e < end && raw[e] == 0)
790 throw new CorruptObjectException(
791 JGitText.get().corruptObjectNameContainsNullByte);
792 checkPathSegment2(raw, ptr, end, null);
793 }
794
795 private void checkPathSegment2(byte[] raw, int ptr, int end,
796 @Nullable AnyObjectId id) throws CorruptObjectException {
797 if (ptr == end) {
798 report(EMPTY_NAME, id, JGitText.get().corruptObjectNameZeroLength);
799 return;
800 }
801
802 if (raw[ptr] == '.') {
803 switch (end - ptr) {
804 case 1:
805 report(HAS_DOT, id, JGitText.get().corruptObjectNameDot);
806 break;
807 case 2:
808 if (raw[ptr + 1] == '.') {
809 report(HAS_DOTDOT, id,
810 JGitText.get().corruptObjectNameDotDot);
811 }
812 break;
813 case 4:
814 if (isGit(raw, ptr + 1)) {
815 report(HAS_DOTGIT, id, String.format(
816 JGitText.get().corruptObjectInvalidName,
817 RawParseUtils.decode(raw, ptr, end)));
818 }
819 break;
820 default:
821 if (end - ptr > 4 && isNormalizedGit(raw, ptr + 1, end)) {
822 report(HAS_DOTGIT, id, String.format(
823 JGitText.get().corruptObjectInvalidName,
824 RawParseUtils.decode(raw, ptr, end)));
825 }
826 }
827 } else if (isGitTilde1(raw, ptr, end)) {
828 report(HAS_DOTGIT, id, String.format(
829 JGitText.get().corruptObjectInvalidName,
830 RawParseUtils.decode(raw, ptr, end)));
831 }
832 if (macosx && isMacHFSGit(raw, ptr, end, id)) {
833 report(HAS_DOTGIT, id, String.format(
834 JGitText.get().corruptObjectInvalidNameIgnorableUnicode,
835 RawParseUtils.decode(raw, ptr, end)));
836 }
837
838 if (windows) {
839
840 if (raw[end - 1] == ' ' || raw[end - 1] == '.') {
841 report(WIN32_BAD_NAME, id, String.format(
842 JGitText.get().corruptObjectInvalidNameEnd,
843 Character.valueOf(((char) raw[end - 1]))));
844 }
845 if (end - ptr >= 3) {
846 checkNotWindowsDevice(raw, ptr, end, id);
847 }
848 }
849 }
850
851
852
853 private boolean isMacHFSPath(byte[] raw, int ptr, int end, byte[] path,
854 @Nullable AnyObjectId id) throws CorruptObjectException {
855 boolean ignorable = false;
856 int g = 0;
857 while (ptr < end) {
858 switch (raw[ptr]) {
859 case (byte) 0xe2:
860 if (!checkTruncatedIgnorableUTF8(raw, ptr, end, id)) {
861 return false;
862 }
863 switch (raw[ptr + 1]) {
864 case (byte) 0x80:
865 switch (raw[ptr + 2]) {
866 case (byte) 0x8c:
867 case (byte) 0x8d:
868 case (byte) 0x8e:
869 case (byte) 0x8f:
870 case (byte) 0xaa:
871 case (byte) 0xab:
872 case (byte) 0xac:
873 case (byte) 0xad:
874 case (byte) 0xae:
875 ignorable = true;
876 ptr += 3;
877 continue;
878 default:
879 return false;
880 }
881 case (byte) 0x81:
882 switch (raw[ptr + 2]) {
883 case (byte) 0xaa:
884 case (byte) 0xab:
885 case (byte) 0xac:
886 case (byte) 0xad:
887 case (byte) 0xae:
888 case (byte) 0xaf:
889 ignorable = true;
890 ptr += 3;
891 continue;
892 default:
893 return false;
894 }
895 default:
896 return false;
897 }
898 case (byte) 0xef:
899 if (!checkTruncatedIgnorableUTF8(raw, ptr, end, id)) {
900 return false;
901 }
902
903 if ((raw[ptr + 1] == (byte) 0xbb)
904 && (raw[ptr + 2] == (byte) 0xbf)) {
905 ignorable = true;
906 ptr += 3;
907 continue;
908 }
909 return false;
910 default:
911 if (g == path.length) {
912 return false;
913 }
914 if (toLower(raw[ptr++]) != path[g++]) {
915 return false;
916 }
917 }
918 }
919 if (g == path.length && ignorable) {
920 return true;
921 }
922 return false;
923 }
924
925 private boolean isMacHFSGit(byte[] raw, int ptr, int end,
926 @Nullable AnyObjectId id) throws CorruptObjectException {
927 byte[] git = new byte[] { '.', 'g', 'i', 't' };
928 return isMacHFSPath(raw, ptr, end, git, id);
929 }
930
931 private boolean isMacHFSGitmodules(byte[] raw, int ptr, int end,
932 @Nullable AnyObjectId id) throws CorruptObjectException {
933 return isMacHFSPath(raw, ptr, end, dotGitmodules, id);
934 }
935
936 private boolean checkTruncatedIgnorableUTF8(byte[] raw, int ptr, int end,
937 @Nullable AnyObjectId id) throws CorruptObjectException {
938 if ((ptr + 2) >= end) {
939 report(BAD_UTF8, id, MessageFormat.format(
940 JGitText.get().corruptObjectInvalidNameInvalidUtf8,
941 toHexString(raw, ptr, end)));
942 return false;
943 }
944 return true;
945 }
946
947 private static String toHexString(byte[] raw, int ptr, int end) {
948 StringBuilder b = new StringBuilder("0x");
949 for (int i = ptr; i < end; i++)
950 b.append(String.format("%02x", Byte.valueOf(raw[i])));
951 return b.toString();
952 }
953
954 private void checkNotWindowsDevice(byte[] raw, int ptr, int end,
955 @Nullable AnyObjectId id) throws CorruptObjectException {
956 switch (toLower(raw[ptr])) {
957 case 'a':
958 if (end - ptr >= 3
959 && toLower(raw[ptr + 1]) == 'u'
960 && toLower(raw[ptr + 2]) == 'x'
961 && (end - ptr == 3 || raw[ptr + 3] == '.')) {
962 report(WIN32_BAD_NAME, id,
963 JGitText.get().corruptObjectInvalidNameAux);
964 }
965 break;
966
967 case 'c':
968 if (end - ptr >= 3
969 && toLower(raw[ptr + 2]) == 'n'
970 && toLower(raw[ptr + 1]) == 'o'
971 && (end - ptr == 3 || raw[ptr + 3] == '.')) {
972 report(WIN32_BAD_NAME, id,
973 JGitText.get().corruptObjectInvalidNameCon);
974 }
975 if (end - ptr >= 4
976 && toLower(raw[ptr + 2]) == 'm'
977 && toLower(raw[ptr + 1]) == 'o'
978 && isPositiveDigit(raw[ptr + 3])
979 && (end - ptr == 4 || raw[ptr + 4] == '.')) {
980 report(WIN32_BAD_NAME, id, String.format(
981 JGitText.get().corruptObjectInvalidNameCom,
982 Character.valueOf(((char) raw[ptr + 3]))));
983 }
984 break;
985
986 case 'l':
987 if (end - ptr >= 4
988 && toLower(raw[ptr + 1]) == 'p'
989 && toLower(raw[ptr + 2]) == 't'
990 && isPositiveDigit(raw[ptr + 3])
991 && (end - ptr == 4 || raw[ptr + 4] == '.')) {
992 report(WIN32_BAD_NAME, id, String.format(
993 JGitText.get().corruptObjectInvalidNameLpt,
994 Character.valueOf(((char) raw[ptr + 3]))));
995 }
996 break;
997
998 case 'n':
999 if (end - ptr >= 3
1000 && toLower(raw[ptr + 1]) == 'u'
1001 && toLower(raw[ptr + 2]) == 'l'
1002 && (end - ptr == 3 || raw[ptr + 3] == '.')) {
1003 report(WIN32_BAD_NAME, id,
1004 JGitText.get().corruptObjectInvalidNameNul);
1005 }
1006 break;
1007
1008 case 'p':
1009 if (end - ptr >= 3
1010 && toLower(raw[ptr + 1]) == 'r'
1011 && toLower(raw[ptr + 2]) == 'n'
1012 && (end - ptr == 3 || raw[ptr + 3] == '.')) {
1013 report(WIN32_BAD_NAME, id,
1014 JGitText.get().corruptObjectInvalidNamePrn);
1015 }
1016 break;
1017 }
1018 }
1019
1020 private static boolean isInvalidOnWindows(byte c) {
1021
1022 switch (c) {
1023 case '"':
1024 case '*':
1025 case ':':
1026 case '<':
1027 case '>':
1028 case '?':
1029 case '\\':
1030 case '|':
1031 return true;
1032 }
1033 return 1 <= c && c <= 31;
1034 }
1035
1036 private static boolean isGit(byte[] buf, int p) {
1037 return toLower(buf[p]) == 'g'
1038 && toLower(buf[p + 1]) == 'i'
1039 && toLower(buf[p + 2]) == 't';
1040 }
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069 private boolean isGitmodules(byte[] buf, int start, int end, @Nullable AnyObjectId id)
1070 throws CorruptObjectException {
1071
1072 if (end - start < 8) {
1073 return false;
1074 }
1075 return (end - start == dotGitmodules.length
1076 && RawParseUtils.match(buf, start, dotGitmodules) != -1)
1077 || (macosx && isMacHFSGitmodules(buf, start, end, id))
1078 || (windows && isNTFSGitmodules(buf, start, end));
1079 }
1080
1081 private boolean matchLowerCase(byte[] b, int ptr, byte[] src) {
1082 if (ptr + src.length > b.length) {
1083 return false;
1084 }
1085 for (int i = 0; i < src.length; i++, ptr++) {
1086 if (toLower(b[ptr]) != src[i]) {
1087 return false;
1088 }
1089 }
1090 return true;
1091 }
1092
1093
1094 private boolean isNTFSGitmodules(byte[] buf, int start, int end) {
1095 if (end - start == 11) {
1096 return matchLowerCase(buf, start, dotGitmodules);
1097 }
1098
1099 if (end - start != 8) {
1100 return false;
1101 }
1102
1103
1104 byte[] gitmod = new byte[]{'g', 'i', 't', 'm', 'o', 'd', '~'};
1105 if (matchLowerCase(buf, start, gitmod)) {
1106 start += 6;
1107 } else {
1108 byte[] gi7eba = new byte[]{'g', 'i', '7', 'e', 'b', 'a'};
1109 for (int i = 0; i < gi7eba.length; i++, start++) {
1110 byte c = (byte) toLower(buf[start]);
1111 if (c == '~') {
1112 break;
1113 }
1114 if (c != gi7eba[i]) {
1115 return false;
1116 }
1117 }
1118 }
1119
1120
1121 if (end - start < 2) {
1122 return false;
1123 }
1124 if (buf[start] != '~') {
1125 return false;
1126 }
1127 start++;
1128 if (buf[start] < '1' || buf[start] > '9') {
1129 return false;
1130 }
1131 start++;
1132 for (; start != end; start++) {
1133 if (buf[start] < '0' || buf[start] > '9') {
1134 return false;
1135 }
1136 }
1137 return true;
1138 }
1139
1140 private static boolean isGitTilde1(byte[] buf, int p, int end) {
1141 if (end - p != 5)
1142 return false;
1143 return toLower(buf[p]) == 'g' && toLower(buf[p + 1]) == 'i'
1144 && toLower(buf[p + 2]) == 't' && buf[p + 3] == '~'
1145 && buf[p + 4] == '1';
1146 }
1147
1148 private static boolean isNormalizedGit(byte[] raw, int ptr, int end) {
1149 if (isGit(raw, ptr)) {
1150 int dots = 0;
1151 boolean space = false;
1152 int p = end - 1;
1153 for (; (ptr + 2) < p; p--) {
1154 if (raw[p] == '.')
1155 dots++;
1156 else if (raw[p] == ' ')
1157 space = true;
1158 else
1159 break;
1160 }
1161 return p == ptr + 2 && (dots == 1 || space);
1162 }
1163 return false;
1164 }
1165
1166 private boolean match(byte[] b, byte[] src) {
1167 int r = RawParseUtils.match(b, bufPtr.value, src);
1168 if (r < 0) {
1169 return false;
1170 }
1171 bufPtr.value = r;
1172 return true;
1173 }
1174
1175 private static char toLower(byte b) {
1176 if ('A' <= b && b <= 'Z')
1177 return (char) (b + ('a' - 'A'));
1178 return (char) b;
1179 }
1180
1181 private static boolean isPositiveDigit(byte b) {
1182 return '1' <= b && b <= '9';
1183 }
1184
1185
1186
1187
1188
1189
1190
1191 @Nullable
1192 public BlobObjectChecker newBlobObjectChecker() {
1193 return null;
1194 }
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208 public void checkBlob(byte[] raw) throws CorruptObjectException {
1209
1210 }
1211
1212 private String normalize(byte[] raw, int ptr, int end) {
1213 String n = RawParseUtils.decode(raw, ptr, end).toLowerCase(Locale.US);
1214 return macosx ? Normalizer.normalize(n, Normalizer.Form.NFC) : n;
1215 }
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226 public List<GitmoduleEntry> getGitsubmodules() {
1227 return gitsubmodules;
1228 }
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241 public void reset() {
1242 gitsubmodules.clear();
1243 }
1244 }