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 checkBlob(raw);
363 break;
364 default:
365 report(UNKNOWN_TYPE, id, MessageFormat.format(
366 JGitText.get().corruptObjectInvalidType2,
367 Integer.valueOf(objType)));
368 }
369 }
370
371 private boolean checkId(byte[] raw) {
372 int p = bufPtr.value;
373 try {
374 tempId.fromString(raw, p);
375 } catch (IllegalArgumentException e) {
376 bufPtr.value = nextLF(raw, p);
377 return false;
378 }
379
380 p += OBJECT_ID_STRING_LENGTH;
381 if (raw[p] == '\n') {
382 bufPtr.value = p + 1;
383 return true;
384 }
385 bufPtr.value = nextLF(raw, p);
386 return false;
387 }
388
389 private void checkPersonIdent(byte[] raw, @Nullable AnyObjectId id)
390 throws CorruptObjectException {
391 if (allowInvalidPersonIdent) {
392 bufPtr.value = nextLF(raw, bufPtr.value);
393 return;
394 }
395
396 final int emailB = nextLF(raw, bufPtr.value, '<');
397 if (emailB == bufPtr.value || raw[emailB - 1] != '<') {
398 report(MISSING_EMAIL, id, JGitText.get().corruptObjectMissingEmail);
399 bufPtr.value = nextLF(raw, bufPtr.value);
400 return;
401 }
402
403 final int emailE = nextLF(raw, emailB, '>');
404 if (emailE == emailB || raw[emailE - 1] != '>') {
405 report(BAD_EMAIL, id, JGitText.get().corruptObjectBadEmail);
406 bufPtr.value = nextLF(raw, bufPtr.value);
407 return;
408 }
409 if (emailE == raw.length || raw[emailE] != ' ') {
410 report(MISSING_SPACE_BEFORE_DATE, id,
411 JGitText.get().corruptObjectBadDate);
412 bufPtr.value = nextLF(raw, bufPtr.value);
413 return;
414 }
415
416 parseBase10(raw, emailE + 1, bufPtr);
417 if (emailE + 1 == bufPtr.value || bufPtr.value == raw.length
418 || raw[bufPtr.value] != ' ') {
419 report(BAD_DATE, id, JGitText.get().corruptObjectBadDate);
420 bufPtr.value = nextLF(raw, bufPtr.value);
421 return;
422 }
423
424 int p = bufPtr.value + 1;
425 parseBase10(raw, p, bufPtr);
426 if (p == bufPtr.value) {
427 report(BAD_TIMEZONE, id, JGitText.get().corruptObjectBadTimezone);
428 bufPtr.value = nextLF(raw, bufPtr.value);
429 return;
430 }
431
432 p = bufPtr.value;
433 if (raw[p] == '\n') {
434 bufPtr.value = p + 1;
435 } else {
436 report(BAD_TIMEZONE, id, JGitText.get().corruptObjectBadTimezone);
437 bufPtr.value = nextLF(raw, p);
438 }
439 }
440
441
442
443
444
445
446
447
448
449 public void checkCommit(byte[] raw) throws CorruptObjectException {
450 checkCommit(idFor(OBJ_COMMIT, raw), raw);
451 }
452
453
454
455
456
457
458
459
460
461
462
463
464 public void checkCommit(@Nullable AnyObjectId id, byte[] raw)
465 throws CorruptObjectException {
466 bufPtr.value = 0;
467
468 if (!match(raw, tree)) {
469 report(MISSING_TREE, id, JGitText.get().corruptObjectNotreeHeader);
470 } else if (!checkId(raw)) {
471 report(BAD_TREE_SHA1, id, JGitText.get().corruptObjectInvalidTree);
472 }
473
474 while (match(raw, parent)) {
475 if (!checkId(raw)) {
476 report(BAD_PARENT_SHA1, id,
477 JGitText.get().corruptObjectInvalidParent);
478 }
479 }
480
481 if (match(raw, author)) {
482 checkPersonIdent(raw, id);
483 } else {
484 report(MISSING_AUTHOR, id, JGitText.get().corruptObjectNoAuthor);
485 }
486
487 if (match(raw, committer)) {
488 checkPersonIdent(raw, id);
489 } else {
490 report(MISSING_COMMITTER, id,
491 JGitText.get().corruptObjectNoCommitter);
492 }
493 }
494
495
496
497
498
499
500
501
502
503 public void checkTag(byte[] raw) throws CorruptObjectException {
504 checkTag(idFor(OBJ_TAG, raw), raw);
505 }
506
507
508
509
510
511
512
513
514
515
516
517
518 public void checkTag(@Nullable AnyObjectId id, byte[] raw)
519 throws CorruptObjectException {
520 bufPtr.value = 0;
521 if (!match(raw, object)) {
522 report(MISSING_OBJECT, id,
523 JGitText.get().corruptObjectNoObjectHeader);
524 } else if (!checkId(raw)) {
525 report(BAD_OBJECT_SHA1, id,
526 JGitText.get().corruptObjectInvalidObject);
527 }
528
529 if (!match(raw, type)) {
530 report(MISSING_TYPE_ENTRY, id,
531 JGitText.get().corruptObjectNoTypeHeader);
532 }
533 bufPtr.value = nextLF(raw, bufPtr.value);
534
535 if (!match(raw, tag)) {
536 report(MISSING_TAG_ENTRY, id,
537 JGitText.get().corruptObjectNoTagHeader);
538 }
539 bufPtr.value = nextLF(raw, bufPtr.value);
540
541 if (match(raw, tagger)) {
542 checkPersonIdent(raw, id);
543 }
544 }
545
546 private static boolean duplicateName(final byte[] raw,
547 final int thisNamePos, final int thisNameEnd) {
548 final int sz = raw.length;
549 int nextPtr = thisNameEnd + 1 + Constants.OBJECT_ID_LENGTH;
550 for (;;) {
551 int nextMode = 0;
552 for (;;) {
553 if (nextPtr >= sz)
554 return false;
555 final byte c = raw[nextPtr++];
556 if (' ' == c)
557 break;
558 nextMode <<= 3;
559 nextMode += c - '0';
560 }
561
562 final int nextNamePos = nextPtr;
563 for (;;) {
564 if (nextPtr == sz)
565 return false;
566 final byte c = raw[nextPtr++];
567 if (c == 0)
568 break;
569 }
570 if (nextNamePos + 1 == nextPtr)
571 return false;
572
573 int cmp = compareSameName(
574 raw, thisNamePos, thisNameEnd,
575 raw, nextNamePos, nextPtr - 1, nextMode);
576 if (cmp < 0)
577 return false;
578 else if (cmp == 0)
579 return true;
580
581 nextPtr += Constants.OBJECT_ID_LENGTH;
582 }
583 }
584
585
586
587
588
589
590
591
592
593 public void checkTree(byte[] raw) throws CorruptObjectException {
594 checkTree(idFor(OBJ_TREE, raw), raw);
595 }
596
597
598
599
600
601
602
603
604
605
606
607
608 public void checkTree(@Nullable AnyObjectId id, byte[] raw)
609 throws CorruptObjectException {
610 final int sz = raw.length;
611 int ptr = 0;
612 int lastNameB = 0, lastNameE = 0, lastMode = 0;
613 Set<String> normalized = windows || macosx
614 ? new HashSet<String>()
615 : null;
616
617 while (ptr < sz) {
618 int thisMode = 0;
619 for (;;) {
620 if (ptr == sz) {
621 throw new CorruptObjectException(
622 JGitText.get().corruptObjectTruncatedInMode);
623 }
624 final byte c = raw[ptr++];
625 if (' ' == c)
626 break;
627 if (c < '0' || c > '7') {
628 throw new CorruptObjectException(
629 JGitText.get().corruptObjectInvalidModeChar);
630 }
631 if (thisMode == 0 && c == '0') {
632 report(ZERO_PADDED_FILEMODE, id,
633 JGitText.get().corruptObjectInvalidModeStartsZero);
634 }
635 thisMode <<= 3;
636 thisMode += c - '0';
637 }
638
639 if (FileMode.fromBits(thisMode).getObjectType() == OBJ_BAD) {
640 throw new CorruptObjectException(MessageFormat.format(
641 JGitText.get().corruptObjectInvalidMode2,
642 Integer.valueOf(thisMode)));
643 }
644
645 final int thisNameB = ptr;
646 ptr = scanPathSegment(raw, ptr, sz, id);
647 if (ptr == sz || raw[ptr] != 0) {
648 throw new CorruptObjectException(
649 JGitText.get().corruptObjectTruncatedInName);
650 }
651 checkPathSegment2(raw, thisNameB, ptr, id);
652 if (normalized != null) {
653 if (!normalized.add(normalize(raw, thisNameB, ptr))) {
654 report(DUPLICATE_ENTRIES, id,
655 JGitText.get().corruptObjectDuplicateEntryNames);
656 }
657 } else if (duplicateName(raw, thisNameB, ptr)) {
658 report(DUPLICATE_ENTRIES, id,
659 JGitText.get().corruptObjectDuplicateEntryNames);
660 }
661
662 if (lastNameB != 0) {
663 int cmp = compare(
664 raw, lastNameB, lastNameE, lastMode,
665 raw, thisNameB, ptr, thisMode);
666 if (cmp > 0) {
667 report(TREE_NOT_SORTED, id,
668 JGitText.get().corruptObjectIncorrectSorting);
669 }
670 }
671
672 lastNameB = thisNameB;
673 lastNameE = ptr;
674 lastMode = thisMode;
675
676 ptr += 1 + OBJECT_ID_LENGTH;
677 if (ptr > sz) {
678 throw new CorruptObjectException(
679 JGitText.get().corruptObjectTruncatedInObjectId);
680 }
681 if (ObjectId.zeroId().compareTo(raw, ptr - OBJECT_ID_LENGTH) == 0) {
682 report(NULL_SHA1, id, JGitText.get().corruptObjectZeroId);
683 }
684 }
685 }
686
687 private int scanPathSegment(byte[] raw, int ptr, int end,
688 @Nullable AnyObjectId id) throws CorruptObjectException {
689 for (; ptr < end; ptr++) {
690 byte c = raw[ptr];
691 if (c == 0) {
692 return ptr;
693 }
694 if (c == '/') {
695 report(FULL_PATHNAME, id,
696 JGitText.get().corruptObjectNameContainsSlash);
697 }
698 if (windows && isInvalidOnWindows(c)) {
699 if (c > 31) {
700 throw new CorruptObjectException(String.format(
701 JGitText.get().corruptObjectNameContainsChar,
702 Byte.valueOf(c)));
703 }
704 throw new CorruptObjectException(String.format(
705 JGitText.get().corruptObjectNameContainsByte,
706 Integer.valueOf(c & 0xff)));
707 }
708 }
709 return ptr;
710 }
711
712 @SuppressWarnings("resource")
713 @Nullable
714 private ObjectId idFor(int objType, byte[] raw) {
715 if (skipList != null) {
716 return new ObjectInserter.Formatter().idFor(objType, raw);
717 }
718 return null;
719 }
720
721 private void report(@NonNull ErrorType err, @Nullable AnyObjectId id,
722 String why) throws CorruptObjectException {
723 if (errors.contains(err)
724 && (id == null || skipList == null || !skipList.contains(id))) {
725 if (id != null) {
726 throw new CorruptObjectException(err, id, why);
727 }
728 throw new CorruptObjectException(why);
729 }
730 }
731
732
733
734
735
736
737
738
739
740
741
742 public void checkPath(String path) throws CorruptObjectException {
743 byte[] buf = Constants.encode(path);
744 checkPath(buf, 0, buf.length);
745 }
746
747
748
749
750
751
752
753
754
755
756
757
758
759 public void checkPath(byte[] raw, int ptr, int end)
760 throws CorruptObjectException {
761 int start = ptr;
762 for (; ptr < end; ptr++) {
763 if (raw[ptr] == '/') {
764 checkPathSegment(raw, start, ptr);
765 start = ptr + 1;
766 }
767 }
768 checkPathSegment(raw, start, end);
769 }
770
771
772
773
774
775
776
777
778
779
780 public void checkPathSegment(byte[] raw, int ptr, int end)
781 throws CorruptObjectException {
782 int e = scanPathSegment(raw, ptr, end, null);
783 if (e < end && raw[e] == 0)
784 throw new CorruptObjectException(
785 JGitText.get().corruptObjectNameContainsNullByte);
786 checkPathSegment2(raw, ptr, end, null);
787 }
788
789 private void checkPathSegment2(byte[] raw, int ptr, int end,
790 @Nullable AnyObjectId id) throws CorruptObjectException {
791 if (ptr == end) {
792 report(EMPTY_NAME, id, JGitText.get().corruptObjectNameZeroLength);
793 return;
794 }
795
796 if (raw[ptr] == '.') {
797 switch (end - ptr) {
798 case 1:
799 report(HAS_DOT, id, JGitText.get().corruptObjectNameDot);
800 break;
801 case 2:
802 if (raw[ptr + 1] == '.') {
803 report(HAS_DOTDOT, id,
804 JGitText.get().corruptObjectNameDotDot);
805 }
806 break;
807 case 4:
808 if (isGit(raw, ptr + 1)) {
809 report(HAS_DOTGIT, id, String.format(
810 JGitText.get().corruptObjectInvalidName,
811 RawParseUtils.decode(raw, ptr, end)));
812 }
813 break;
814 default:
815 if (end - ptr > 4 && isNormalizedGit(raw, ptr + 1, end)) {
816 report(HAS_DOTGIT, id, String.format(
817 JGitText.get().corruptObjectInvalidName,
818 RawParseUtils.decode(raw, ptr, end)));
819 }
820 }
821 } else if (isGitTilde1(raw, ptr, end)) {
822 report(HAS_DOTGIT, id, String.format(
823 JGitText.get().corruptObjectInvalidName,
824 RawParseUtils.decode(raw, ptr, end)));
825 }
826 if (macosx && isMacHFSGit(raw, ptr, end, id)) {
827 report(HAS_DOTGIT, id, String.format(
828 JGitText.get().corruptObjectInvalidNameIgnorableUnicode,
829 RawParseUtils.decode(raw, ptr, end)));
830 }
831
832 if (windows) {
833
834 if (raw[end - 1] == ' ' || raw[end - 1] == '.') {
835 report(WIN32_BAD_NAME, id, String.format(
836 JGitText.get().corruptObjectInvalidNameEnd,
837 Character.valueOf(((char) raw[end - 1]))));
838 }
839 if (end - ptr >= 3) {
840 checkNotWindowsDevice(raw, ptr, end, id);
841 }
842 }
843 }
844
845
846
847 private boolean isMacHFSGit(byte[] raw, int ptr, int end,
848 @Nullable AnyObjectId id) throws CorruptObjectException {
849 boolean ignorable = false;
850 byte[] git = new byte[] { '.', 'g', 'i', 't' };
851 int g = 0;
852 while (ptr < end) {
853 switch (raw[ptr]) {
854 case (byte) 0xe2:
855 if (!checkTruncatedIgnorableUTF8(raw, ptr, end, id)) {
856 return false;
857 }
858 switch (raw[ptr + 1]) {
859 case (byte) 0x80:
860 switch (raw[ptr + 2]) {
861 case (byte) 0x8c:
862 case (byte) 0x8d:
863 case (byte) 0x8e:
864 case (byte) 0x8f:
865 case (byte) 0xaa:
866 case (byte) 0xab:
867 case (byte) 0xac:
868 case (byte) 0xad:
869 case (byte) 0xae:
870 ignorable = true;
871 ptr += 3;
872 continue;
873 default:
874 return false;
875 }
876 case (byte) 0x81:
877 switch (raw[ptr + 2]) {
878 case (byte) 0xaa:
879 case (byte) 0xab:
880 case (byte) 0xac:
881 case (byte) 0xad:
882 case (byte) 0xae:
883 case (byte) 0xaf:
884 ignorable = true;
885 ptr += 3;
886 continue;
887 default:
888 return false;
889 }
890 default:
891 return false;
892 }
893 case (byte) 0xef:
894 if (!checkTruncatedIgnorableUTF8(raw, ptr, end, id)) {
895 return false;
896 }
897
898 if ((raw[ptr + 1] == (byte) 0xbb)
899 && (raw[ptr + 2] == (byte) 0xbf)) {
900 ignorable = true;
901 ptr += 3;
902 continue;
903 }
904 return false;
905 default:
906 if (g == 4)
907 return false;
908 if (raw[ptr++] != git[g++])
909 return false;
910 }
911 }
912 if (g == 4 && ignorable)
913 return true;
914 return false;
915 }
916
917 private boolean checkTruncatedIgnorableUTF8(byte[] raw, int ptr, int end,
918 @Nullable AnyObjectId id) throws CorruptObjectException {
919 if ((ptr + 2) >= end) {
920 report(BAD_UTF8, id, MessageFormat.format(
921 JGitText.get().corruptObjectInvalidNameInvalidUtf8,
922 toHexString(raw, ptr, end)));
923 return false;
924 }
925 return true;
926 }
927
928 private static String toHexString(byte[] raw, int ptr, int end) {
929 StringBuilder b = new StringBuilder("0x");
930 for (int i = ptr; i < end; i++)
931 b.append(String.format("%02x", Byte.valueOf(raw[i])));
932 return b.toString();
933 }
934
935 private void checkNotWindowsDevice(byte[] raw, int ptr, int end,
936 @Nullable AnyObjectId id) throws CorruptObjectException {
937 switch (toLower(raw[ptr])) {
938 case 'a':
939 if (end - ptr >= 3
940 && toLower(raw[ptr + 1]) == 'u'
941 && toLower(raw[ptr + 2]) == 'x'
942 && (end - ptr == 3 || raw[ptr + 3] == '.')) {
943 report(WIN32_BAD_NAME, id,
944 JGitText.get().corruptObjectInvalidNameAux);
945 }
946 break;
947
948 case 'c':
949 if (end - ptr >= 3
950 && toLower(raw[ptr + 2]) == 'n'
951 && toLower(raw[ptr + 1]) == 'o'
952 && (end - ptr == 3 || raw[ptr + 3] == '.')) {
953 report(WIN32_BAD_NAME, id,
954 JGitText.get().corruptObjectInvalidNameCon);
955 }
956 if (end - ptr >= 4
957 && toLower(raw[ptr + 2]) == 'm'
958 && toLower(raw[ptr + 1]) == 'o'
959 && isPositiveDigit(raw[ptr + 3])
960 && (end - ptr == 4 || raw[ptr + 4] == '.')) {
961 report(WIN32_BAD_NAME, id, String.format(
962 JGitText.get().corruptObjectInvalidNameCom,
963 Character.valueOf(((char) raw[ptr + 3]))));
964 }
965 break;
966
967 case 'l':
968 if (end - ptr >= 4
969 && toLower(raw[ptr + 1]) == 'p'
970 && toLower(raw[ptr + 2]) == 't'
971 && isPositiveDigit(raw[ptr + 3])
972 && (end - ptr == 4 || raw[ptr + 4] == '.')) {
973 report(WIN32_BAD_NAME, id, String.format(
974 JGitText.get().corruptObjectInvalidNameLpt,
975 Character.valueOf(((char) raw[ptr + 3]))));
976 }
977 break;
978
979 case 'n':
980 if (end - ptr >= 3
981 && toLower(raw[ptr + 1]) == 'u'
982 && toLower(raw[ptr + 2]) == 'l'
983 && (end - ptr == 3 || raw[ptr + 3] == '.')) {
984 report(WIN32_BAD_NAME, id,
985 JGitText.get().corruptObjectInvalidNameNul);
986 }
987 break;
988
989 case 'p':
990 if (end - ptr >= 3
991 && toLower(raw[ptr + 1]) == 'r'
992 && toLower(raw[ptr + 2]) == 'n'
993 && (end - ptr == 3 || raw[ptr + 3] == '.')) {
994 report(WIN32_BAD_NAME, id,
995 JGitText.get().corruptObjectInvalidNamePrn);
996 }
997 break;
998 }
999 }
1000
1001 private static boolean isInvalidOnWindows(byte c) {
1002
1003 switch (c) {
1004 case '"':
1005 case '*':
1006 case ':':
1007 case '<':
1008 case '>':
1009 case '?':
1010 case '\\':
1011 case '|':
1012 return true;
1013 }
1014 return 1 <= c && c <= 31;
1015 }
1016
1017 private static boolean isGit(byte[] buf, int p) {
1018 return toLower(buf[p]) == 'g'
1019 && toLower(buf[p + 1]) == 'i'
1020 && toLower(buf[p + 2]) == 't';
1021 }
1022
1023 private static boolean isGitTilde1(byte[] buf, int p, int end) {
1024 if (end - p != 5)
1025 return false;
1026 return toLower(buf[p]) == 'g' && toLower(buf[p + 1]) == 'i'
1027 && toLower(buf[p + 2]) == 't' && buf[p + 3] == '~'
1028 && buf[p + 4] == '1';
1029 }
1030
1031 private static boolean isNormalizedGit(byte[] raw, int ptr, int end) {
1032 if (isGit(raw, ptr)) {
1033 int dots = 0;
1034 boolean space = false;
1035 int p = end - 1;
1036 for (; (ptr + 2) < p; p--) {
1037 if (raw[p] == '.')
1038 dots++;
1039 else if (raw[p] == ' ')
1040 space = true;
1041 else
1042 break;
1043 }
1044 return p == ptr + 2 && (dots == 1 || space);
1045 }
1046 return false;
1047 }
1048
1049 private boolean match(byte[] b, byte[] src) {
1050 int r = RawParseUtils.match(b, bufPtr.value, src);
1051 if (r < 0) {
1052 return false;
1053 }
1054 bufPtr.value = r;
1055 return true;
1056 }
1057
1058 private static char toLower(byte b) {
1059 if ('A' <= b && b <= 'Z')
1060 return (char) (b + ('a' - 'A'));
1061 return (char) b;
1062 }
1063
1064 private static boolean isPositiveDigit(byte b) {
1065 return '1' <= b && b <= '9';
1066 }
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076 public void checkBlob(final byte[] raw) throws CorruptObjectException {
1077
1078 }
1079
1080 private String normalize(byte[] raw, int ptr, int end) {
1081 String n = RawParseUtils.decode(raw, ptr, end).toLowerCase(Locale.US);
1082 return macosx ? Normalizer.normalize(n, Normalizer.Form.NFC) : n;
1083 }
1084 }