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