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<>()
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 @Nullable
713 private ObjectId idFor(int objType, byte[] raw) {
714 if (skipList != null) {
715 try (ObjectInserter.Formatter fmt = new ObjectInserter.Formatter()) {
716 return fmt.idFor(objType, raw);
717 }
718 }
719 return null;
720 }
721
722 private void report(@NonNull ErrorType err, @Nullable AnyObjectId id,
723 String why) throws CorruptObjectException {
724 if (errors.contains(err)
725 && (id == null || skipList == null || !skipList.contains(id))) {
726 if (id != null) {
727 throw new CorruptObjectException(err, id, why);
728 }
729 throw new CorruptObjectException(why);
730 }
731 }
732
733
734
735
736
737
738
739
740
741
742
743 public void checkPath(String path) throws CorruptObjectException {
744 byte[] buf = Constants.encode(path);
745 checkPath(buf, 0, buf.length);
746 }
747
748
749
750
751
752
753
754
755
756
757
758
759
760 public void checkPath(byte[] raw, int ptr, int end)
761 throws CorruptObjectException {
762 int start = ptr;
763 for (; ptr < end; ptr++) {
764 if (raw[ptr] == '/') {
765 checkPathSegment(raw, start, ptr);
766 start = ptr + 1;
767 }
768 }
769 checkPathSegment(raw, start, end);
770 }
771
772
773
774
775
776
777
778
779
780
781 public void checkPathSegment(byte[] raw, int ptr, int end)
782 throws CorruptObjectException {
783 int e = scanPathSegment(raw, ptr, end, null);
784 if (e < end && raw[e] == 0)
785 throw new CorruptObjectException(
786 JGitText.get().corruptObjectNameContainsNullByte);
787 checkPathSegment2(raw, ptr, end, null);
788 }
789
790 private void checkPathSegment2(byte[] raw, int ptr, int end,
791 @Nullable AnyObjectId id) throws CorruptObjectException {
792 if (ptr == end) {
793 report(EMPTY_NAME, id, JGitText.get().corruptObjectNameZeroLength);
794 return;
795 }
796
797 if (raw[ptr] == '.') {
798 switch (end - ptr) {
799 case 1:
800 report(HAS_DOT, id, JGitText.get().corruptObjectNameDot);
801 break;
802 case 2:
803 if (raw[ptr + 1] == '.') {
804 report(HAS_DOTDOT, id,
805 JGitText.get().corruptObjectNameDotDot);
806 }
807 break;
808 case 4:
809 if (isGit(raw, ptr + 1)) {
810 report(HAS_DOTGIT, id, String.format(
811 JGitText.get().corruptObjectInvalidName,
812 RawParseUtils.decode(raw, ptr, end)));
813 }
814 break;
815 default:
816 if (end - ptr > 4 && isNormalizedGit(raw, ptr + 1, end)) {
817 report(HAS_DOTGIT, id, String.format(
818 JGitText.get().corruptObjectInvalidName,
819 RawParseUtils.decode(raw, ptr, end)));
820 }
821 }
822 } else if (isGitTilde1(raw, ptr, end)) {
823 report(HAS_DOTGIT, id, String.format(
824 JGitText.get().corruptObjectInvalidName,
825 RawParseUtils.decode(raw, ptr, end)));
826 }
827 if (macosx && isMacHFSGit(raw, ptr, end, id)) {
828 report(HAS_DOTGIT, id, String.format(
829 JGitText.get().corruptObjectInvalidNameIgnorableUnicode,
830 RawParseUtils.decode(raw, ptr, end)));
831 }
832
833 if (windows) {
834
835 if (raw[end - 1] == ' ' || raw[end - 1] == '.') {
836 report(WIN32_BAD_NAME, id, String.format(
837 JGitText.get().corruptObjectInvalidNameEnd,
838 Character.valueOf(((char) raw[end - 1]))));
839 }
840 if (end - ptr >= 3) {
841 checkNotWindowsDevice(raw, ptr, end, id);
842 }
843 }
844 }
845
846
847
848 private boolean isMacHFSGit(byte[] raw, int ptr, int end,
849 @Nullable AnyObjectId id) throws CorruptObjectException {
850 boolean ignorable = false;
851 byte[] git = new byte[] { '.', 'g', 'i', 't' };
852 int g = 0;
853 while (ptr < end) {
854 switch (raw[ptr]) {
855 case (byte) 0xe2:
856 if (!checkTruncatedIgnorableUTF8(raw, ptr, end, id)) {
857 return false;
858 }
859 switch (raw[ptr + 1]) {
860 case (byte) 0x80:
861 switch (raw[ptr + 2]) {
862 case (byte) 0x8c:
863 case (byte) 0x8d:
864 case (byte) 0x8e:
865 case (byte) 0x8f:
866 case (byte) 0xaa:
867 case (byte) 0xab:
868 case (byte) 0xac:
869 case (byte) 0xad:
870 case (byte) 0xae:
871 ignorable = true;
872 ptr += 3;
873 continue;
874 default:
875 return false;
876 }
877 case (byte) 0x81:
878 switch (raw[ptr + 2]) {
879 case (byte) 0xaa:
880 case (byte) 0xab:
881 case (byte) 0xac:
882 case (byte) 0xad:
883 case (byte) 0xae:
884 case (byte) 0xaf:
885 ignorable = true;
886 ptr += 3;
887 continue;
888 default:
889 return false;
890 }
891 default:
892 return false;
893 }
894 case (byte) 0xef:
895 if (!checkTruncatedIgnorableUTF8(raw, ptr, end, id)) {
896 return false;
897 }
898
899 if ((raw[ptr + 1] == (byte) 0xbb)
900 && (raw[ptr + 2] == (byte) 0xbf)) {
901 ignorable = true;
902 ptr += 3;
903 continue;
904 }
905 return false;
906 default:
907 if (g == 4)
908 return false;
909 if (raw[ptr++] != git[g++])
910 return false;
911 }
912 }
913 if (g == 4 && ignorable)
914 return true;
915 return false;
916 }
917
918 private boolean checkTruncatedIgnorableUTF8(byte[] raw, int ptr, int end,
919 @Nullable AnyObjectId id) throws CorruptObjectException {
920 if ((ptr + 2) >= end) {
921 report(BAD_UTF8, id, MessageFormat.format(
922 JGitText.get().corruptObjectInvalidNameInvalidUtf8,
923 toHexString(raw, ptr, end)));
924 return false;
925 }
926 return true;
927 }
928
929 private static String toHexString(byte[] raw, int ptr, int end) {
930 StringBuilder b = new StringBuilder("0x");
931 for (int i = ptr; i < end; i++)
932 b.append(String.format("%02x", Byte.valueOf(raw[i])));
933 return b.toString();
934 }
935
936 private void checkNotWindowsDevice(byte[] raw, int ptr, int end,
937 @Nullable AnyObjectId id) throws CorruptObjectException {
938 switch (toLower(raw[ptr])) {
939 case 'a':
940 if (end - ptr >= 3
941 && toLower(raw[ptr + 1]) == 'u'
942 && toLower(raw[ptr + 2]) == 'x'
943 && (end - ptr == 3 || raw[ptr + 3] == '.')) {
944 report(WIN32_BAD_NAME, id,
945 JGitText.get().corruptObjectInvalidNameAux);
946 }
947 break;
948
949 case 'c':
950 if (end - ptr >= 3
951 && toLower(raw[ptr + 2]) == 'n'
952 && toLower(raw[ptr + 1]) == 'o'
953 && (end - ptr == 3 || raw[ptr + 3] == '.')) {
954 report(WIN32_BAD_NAME, id,
955 JGitText.get().corruptObjectInvalidNameCon);
956 }
957 if (end - ptr >= 4
958 && toLower(raw[ptr + 2]) == 'm'
959 && toLower(raw[ptr + 1]) == 'o'
960 && isPositiveDigit(raw[ptr + 3])
961 && (end - ptr == 4 || raw[ptr + 4] == '.')) {
962 report(WIN32_BAD_NAME, id, String.format(
963 JGitText.get().corruptObjectInvalidNameCom,
964 Character.valueOf(((char) raw[ptr + 3]))));
965 }
966 break;
967
968 case 'l':
969 if (end - ptr >= 4
970 && toLower(raw[ptr + 1]) == 'p'
971 && toLower(raw[ptr + 2]) == 't'
972 && isPositiveDigit(raw[ptr + 3])
973 && (end - ptr == 4 || raw[ptr + 4] == '.')) {
974 report(WIN32_BAD_NAME, id, String.format(
975 JGitText.get().corruptObjectInvalidNameLpt,
976 Character.valueOf(((char) raw[ptr + 3]))));
977 }
978 break;
979
980 case 'n':
981 if (end - ptr >= 3
982 && toLower(raw[ptr + 1]) == 'u'
983 && toLower(raw[ptr + 2]) == 'l'
984 && (end - ptr == 3 || raw[ptr + 3] == '.')) {
985 report(WIN32_BAD_NAME, id,
986 JGitText.get().corruptObjectInvalidNameNul);
987 }
988 break;
989
990 case 'p':
991 if (end - ptr >= 3
992 && toLower(raw[ptr + 1]) == 'r'
993 && toLower(raw[ptr + 2]) == 'n'
994 && (end - ptr == 3 || raw[ptr + 3] == '.')) {
995 report(WIN32_BAD_NAME, id,
996 JGitText.get().corruptObjectInvalidNamePrn);
997 }
998 break;
999 }
1000 }
1001
1002 private static boolean isInvalidOnWindows(byte c) {
1003
1004 switch (c) {
1005 case '"':
1006 case '*':
1007 case ':':
1008 case '<':
1009 case '>':
1010 case '?':
1011 case '\\':
1012 case '|':
1013 return true;
1014 }
1015 return 1 <= c && c <= 31;
1016 }
1017
1018 private static boolean isGit(byte[] buf, int p) {
1019 return toLower(buf[p]) == 'g'
1020 && toLower(buf[p + 1]) == 'i'
1021 && toLower(buf[p + 2]) == 't';
1022 }
1023
1024 private static boolean isGitTilde1(byte[] buf, int p, int end) {
1025 if (end - p != 5)
1026 return false;
1027 return toLower(buf[p]) == 'g' && toLower(buf[p + 1]) == 'i'
1028 && toLower(buf[p + 2]) == 't' && buf[p + 3] == '~'
1029 && buf[p + 4] == '1';
1030 }
1031
1032 private static boolean isNormalizedGit(byte[] raw, int ptr, int end) {
1033 if (isGit(raw, ptr)) {
1034 int dots = 0;
1035 boolean space = false;
1036 int p = end - 1;
1037 for (; (ptr + 2) < p; p--) {
1038 if (raw[p] == '.')
1039 dots++;
1040 else if (raw[p] == ' ')
1041 space = true;
1042 else
1043 break;
1044 }
1045 return p == ptr + 2 && (dots == 1 || space);
1046 }
1047 return false;
1048 }
1049
1050 private boolean match(byte[] b, byte[] src) {
1051 int r = RawParseUtils.match(b, bufPtr.value, src);
1052 if (r < 0) {
1053 return false;
1054 }
1055 bufPtr.value = r;
1056 return true;
1057 }
1058
1059 private static char toLower(byte b) {
1060 if ('A' <= b && b <= 'Z')
1061 return (char) (b + ('a' - 'A'));
1062 return (char) b;
1063 }
1064
1065 private static boolean isPositiveDigit(byte b) {
1066 return '1' <= b && b <= '9';
1067 }
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077 public void checkBlob(final byte[] raw) throws CorruptObjectException {
1078
1079 }
1080
1081 private String normalize(byte[] raw, int ptr, int end) {
1082 String n = RawParseUtils.decode(raw, ptr, end).toLowerCase(Locale.US);
1083 return macosx ? Normalizer.normalize(n, Normalizer.Form.NFC) : n;
1084 }
1085 }