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