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