Fossil SCM
Enhance the manifest parser to support parsing of Forum posts artifacts. At the same time, simplify the artifact syntax error detection logic using tables rather than straight code.
Commit
e893e9d01b8b217d8dccbf111e31e5e6fec5c1e62f7191c8b6661ada73c5519a
Parent
15fa605318fc0e4…
2 files changed
+163
-79
+11
+163
-79
| --- src/manifest.c | ||
| +++ src/manifest.c | ||
| @@ -34,10 +34,11 @@ | ||
| 34 | 34 | #define CFTYPE_CONTROL 3 |
| 35 | 35 | #define CFTYPE_WIKI 4 |
| 36 | 36 | #define CFTYPE_TICKET 5 |
| 37 | 37 | #define CFTYPE_ATTACHMENT 6 |
| 38 | 38 | #define CFTYPE_EVENT 7 |
| 39 | +#define CFTYPE_FORUM 8 | |
| 39 | 40 | |
| 40 | 41 | /* |
| 41 | 42 | ** File permissions used by Fossil internally. |
| 42 | 43 | */ |
| 43 | 44 | #define PERM_REG 0 /* regular file */ |
| @@ -76,16 +77,19 @@ | ||
| 76 | 77 | char *zUser; /* Name of the user from the U card. */ |
| 77 | 78 | char *zRepoCksum; /* MD5 checksum of the baseline content. R card. */ |
| 78 | 79 | char *zWiki; /* Text of the wiki page. W card. */ |
| 79 | 80 | char *zWikiTitle; /* Name of the wiki page. L card. */ |
| 80 | 81 | char *zMimetype; /* Mime type of wiki or comment text. N card. */ |
| 82 | + char *zThreadTitle; /* The forum thread title. H card */ | |
| 81 | 83 | double rEventDate; /* Date of an event. E card. */ |
| 82 | 84 | char *zEventId; /* Artifact hash for an event. E card. */ |
| 83 | 85 | char *zTicketUuid; /* UUID for a ticket. K card. */ |
| 84 | 86 | char *zAttachName; /* Filename of an attachment. A card. */ |
| 85 | 87 | char *zAttachSrc; /* Artifact hash for document being attached. A card. */ |
| 86 | 88 | char *zAttachTarget; /* Ticket or wiki that attachment applies to. A card */ |
| 89 | + char *zThreadRoot; /* Thread root artifact. G card */ | |
| 90 | + char *zInReplyTo; /* Forum in-reply-to artifact. I card */ | |
| 87 | 91 | int nFile; /* Number of F cards */ |
| 88 | 92 | int nFileAlloc; /* Slots allocated in aFile[] */ |
| 89 | 93 | int iFile; /* Index of current file in iterator */ |
| 90 | 94 | ManifestFile *aFile; /* One entry for each F-card */ |
| 91 | 95 | int nParent; /* Number of parents. */ |
| @@ -112,10 +116,42 @@ | ||
| 112 | 116 | char *zName; /* Key or field name */ |
| 113 | 117 | char *zValue; /* Value of the field */ |
| 114 | 118 | } *aField; /* One for each J card */ |
| 115 | 119 | }; |
| 116 | 120 | #endif |
| 121 | + | |
| 122 | +/* | |
| 123 | +** Allowed and required card types in each style of artifact | |
| 124 | +*/ | |
| 125 | +static struct { | |
| 126 | + const char *zAllowed; /* Allowed cards. Human-readable */ | |
| 127 | + const char *zRequired; /* Required cards. Human-readable */ | |
| 128 | +} manifestCardTypes[] = { | |
| 129 | + /* Allowed Required */ | |
| 130 | + /* CFTYPE_MANIFEST 1 */ { "BCDFNPQRTUZ", "CDUZ" }, | |
| 131 | + /* CFTYPE_CLUSTER 2 */ { "MZ", "MZ" }, | |
| 132 | + /* CFTYPE_CONTROL 3 */ { "DTUZ", "DTUZ" }, | |
| 133 | + /* CFTYPE_WIKI 4 */ { "DLNPUWZ", "DLUWZ" }, | |
| 134 | + /* CFTYPE_TICKET 5 */ { "DJKUZ", "DJKUZ" }, | |
| 135 | + /* CFTYPE_ATTACHMENT 6 */ { "ACDNUZ", "ADZ" }, | |
| 136 | + /* CFTYPE_EVENT 7 */ { "CDENPTUWZ", "DEWZ" }, | |
| 137 | + /* CFTYPE_FORUM 8 */ { "DGHINPUWZ", "DUWZ" }, | |
| 138 | +}; | |
| 139 | + | |
| 140 | +/* | |
| 141 | +** Names of manifest types | |
| 142 | +*/ | |
| 143 | +static const char *azNameOfMType[] = { | |
| 144 | + "manifest", | |
| 145 | + "cluster", | |
| 146 | + "tag", | |
| 147 | + "wiki", | |
| 148 | + "ticket", | |
| 149 | + "attachment", | |
| 150 | + "technote", | |
| 151 | + "forum post" | |
| 152 | +}; | |
| 117 | 153 | |
| 118 | 154 | /* |
| 119 | 155 | ** A cache of parsed manifests. This reduces the number of |
| 120 | 156 | ** calls to manifest_parse() when doing a rebuild. |
| 121 | 157 | */ |
| @@ -147,10 +183,35 @@ | ||
| 147 | 183 | if( p->pBaseline ) manifest_destroy(p->pBaseline); |
| 148 | 184 | memset(p, 0, sizeof(*p)); |
| 149 | 185 | fossil_free(p); |
| 150 | 186 | } |
| 151 | 187 | } |
| 188 | + | |
| 189 | +/* | |
| 190 | +** Given a string of upper-case letters, compute a mask of the letters | |
| 191 | +** present. For example, "ABC" computes 0x0007. "DE" gives 0x0018". | |
| 192 | +*/ | |
| 193 | +static unsigned int manifest_card_mask(const char *z){ | |
| 194 | + unsigned int m = 0; | |
| 195 | + char c; | |
| 196 | + while( (c = *(z++))>='A' && c<='Z' ){ | |
| 197 | + m |= 1 << (c - 'A'); | |
| 198 | + } | |
| 199 | + return m; | |
| 200 | +} | |
| 201 | + | |
| 202 | +/* | |
| 203 | +** Given an integer mask representing letters A-Z, return the | |
| 204 | +** letter which is the first bit set in the mask. Example: | |
| 205 | +** 0x03520 gives 'F' since the F-bit is the lowest. | |
| 206 | +*/ | |
| 207 | +static char maskToType(unsigned int x){ | |
| 208 | + char c = 'A'; | |
| 209 | + if( x==0 ) return '?'; | |
| 210 | + while( (x&1)==0 ){ x >>= 1; c++; } | |
| 211 | + return c; | |
| 212 | +} | |
| 152 | 213 | |
| 153 | 214 | /* |
| 154 | 215 | ** Add an element to the manifest cache using LRU replacement. |
| 155 | 216 | */ |
| 156 | 217 | void manifest_cache_insert(Manifest *p){ |
| @@ -352,11 +413,10 @@ | ||
| 352 | 413 | ** The card type determines the other parameters to the card. |
| 353 | 414 | ** Cards must occur in lexicographical order. |
| 354 | 415 | */ |
| 355 | 416 | Manifest *manifest_parse(Blob *pContent, int rid, Blob *pErr){ |
| 356 | 417 | Manifest *p; |
| 357 | - int seenZ = 0; | |
| 358 | 418 | int i, lineNo=0; |
| 359 | 419 | ManifestText x; |
| 360 | 420 | char cPrevType = 0; |
| 361 | 421 | char cType; |
| 362 | 422 | char *z; |
| @@ -364,10 +424,13 @@ | ||
| 364 | 424 | char *zUuid; |
| 365 | 425 | int sz = 0; |
| 366 | 426 | int isRepeat, hasSelfRefTag = 0; |
| 367 | 427 | static Bag seen; |
| 368 | 428 | const char *zErr = 0; |
| 429 | + unsigned int m; | |
| 430 | + unsigned int seenCard = 0; /* Which card types have been seen */ | |
| 431 | + char zErrBuf[100]; /* Write error messages here */ | |
| 369 | 432 | |
| 370 | 433 | if( rid==0 ){ |
| 371 | 434 | isRepeat = 1; |
| 372 | 435 | }else if( bag_find(&seen, rid) ){ |
| 373 | 436 | isRepeat = 1; |
| @@ -422,10 +485,12 @@ | ||
| 422 | 485 | x.z = z; |
| 423 | 486 | x.zEnd = &z[n]; |
| 424 | 487 | x.atEol = 1; |
| 425 | 488 | while( (cType = next_card(&x))!=0 && cType>=cPrevType ){ |
| 426 | 489 | lineNo++; |
| 490 | + if( cType<'A' || cType>'Z' ) SYNTAX("bad card type"); | |
| 491 | + seenCard |= 1 << (cType-'A'); | |
| 427 | 492 | switch( cType ){ |
| 428 | 493 | /* |
| 429 | 494 | ** A <filename> <target> ?<source>? |
| 430 | 495 | ** |
| 431 | 496 | ** Identifies an attachment to either a wiki page or a ticket. |
| @@ -454,10 +519,11 @@ | ||
| 454 | 519 | SYNTAX("invalid source on A-card"); |
| 455 | 520 | } |
| 456 | 521 | p->zAttachName = (char*)file_tail(zName); |
| 457 | 522 | p->zAttachSrc = zSrc; |
| 458 | 523 | p->zAttachTarget = zTarget; |
| 524 | + p->type = CFTYPE_ATTACHMENT; | |
| 459 | 525 | break; |
| 460 | 526 | } |
| 461 | 527 | |
| 462 | 528 | /* |
| 463 | 529 | ** B <uuid> |
| @@ -469,10 +535,11 @@ | ||
| 469 | 535 | p->zBaseline = next_token(&x, &sz); |
| 470 | 536 | if( p->zBaseline==0 ) SYNTAX("missing hash on B-card"); |
| 471 | 537 | if( !hname_validate(p->zBaseline,sz) ){ |
| 472 | 538 | SYNTAX("invalid hash on B-card"); |
| 473 | 539 | } |
| 540 | + p->type = CFTYPE_MANIFEST; | |
| 474 | 541 | break; |
| 475 | 542 | } |
| 476 | 543 | |
| 477 | 544 | |
| 478 | 545 | /* |
| @@ -520,10 +587,11 @@ | ||
| 520 | 587 | if( p->rEventDate<=0.0 ) SYNTAX("malformed date on E-card"); |
| 521 | 588 | p->zEventId = next_token(&x, &sz); |
| 522 | 589 | if( !hname_validate(p->zEventId, sz) ){ |
| 523 | 590 | SYNTAX("malformed hash on E-card"); |
| 524 | 591 | } |
| 592 | + p->type = CFTYPE_EVENT; | |
| 525 | 593 | break; |
| 526 | 594 | } |
| 527 | 595 | |
| 528 | 596 | /* |
| 529 | 597 | ** F <filename> ?<uuid>? ?<permissions>? ?<old-name>? |
| @@ -565,10 +633,59 @@ | ||
| 565 | 633 | p->aFile[i].zPerm = zPerm; |
| 566 | 634 | p->aFile[i].zPrior = zPriorName; |
| 567 | 635 | if( i>0 && fossil_strcmp(p->aFile[i-1].zName, zName)>=0 ){ |
| 568 | 636 | SYNTAX("incorrect F-card sort order"); |
| 569 | 637 | } |
| 638 | + p->type = CFTYPE_MANIFEST; | |
| 639 | + break; | |
| 640 | + } | |
| 641 | + | |
| 642 | + /* | |
| 643 | + ** G <hash> | |
| 644 | + ** | |
| 645 | + ** A G-card identifies the initial root forum post for the thread | |
| 646 | + ** of which this post is a part. Forum posts only. | |
| 647 | + */ | |
| 648 | + case 'G': { | |
| 649 | + if( p->zThreadRoot!=0 ) SYNTAX("more than one G-card"); | |
| 650 | + p->zThreadRoot = next_token(&x, &sz); | |
| 651 | + if( p->zThreadRoot==0 ) SYNTAX("missing hash on G-card"); | |
| 652 | + if( !hname_validate(p->zThreadRoot,sz) ){ | |
| 653 | + SYNTAX("Invalid hash on G-card"); | |
| 654 | + } | |
| 655 | + p->type = CFTYPE_FORUM; | |
| 656 | + break; | |
| 657 | + } | |
| 658 | + | |
| 659 | + /* | |
| 660 | + ** H <threadtitle> | |
| 661 | + ** | |
| 662 | + ** The title for a forum thread. | |
| 663 | + */ | |
| 664 | + case 'H': { | |
| 665 | + if( p->zThreadTitle!=0 ) SYNTAX("more than one H-card"); | |
| 666 | + p->zThreadTitle = next_token(&x,0); | |
| 667 | + if( p->zThreadTitle==0 ) SYNTAX("missing title on H-card"); | |
| 668 | + defossilize(p->zThreadTitle); | |
| 669 | + p->type = CFTYPE_FORUM; | |
| 670 | + break; | |
| 671 | + } | |
| 672 | + | |
| 673 | + /* | |
| 674 | + ** I <hash> | |
| 675 | + ** | |
| 676 | + ** A I-card identifies another forum post that the current forum post | |
| 677 | + ** is in reply to. | |
| 678 | + */ | |
| 679 | + case 'I': { | |
| 680 | + if( p->zInReplyTo!=0 ) SYNTAX("more than one I-card"); | |
| 681 | + p->zInReplyTo = next_token(&x, &sz); | |
| 682 | + if( p->zInReplyTo==0 ) SYNTAX("missing hash on I-card"); | |
| 683 | + if( !hname_validate(p->zInReplyTo,sz) ){ | |
| 684 | + SYNTAX("Invalid hash on I-card"); | |
| 685 | + } | |
| 686 | + p->type = CFTYPE_FORUM; | |
| 570 | 687 | break; |
| 571 | 688 | } |
| 572 | 689 | |
| 573 | 690 | /* |
| 574 | 691 | ** J <name> ?<value>? |
| @@ -594,10 +711,11 @@ | ||
| 594 | 711 | p->aField[i].zName = zName; |
| 595 | 712 | p->aField[i].zValue = zValue; |
| 596 | 713 | if( i>0 && fossil_strcmp(p->aField[i-1].zName, zName)>=0 ){ |
| 597 | 714 | SYNTAX("incorrect J-card sort order"); |
| 598 | 715 | } |
| 716 | + p->type = CFTYPE_TICKET; | |
| 599 | 717 | break; |
| 600 | 718 | } |
| 601 | 719 | |
| 602 | 720 | |
| 603 | 721 | /* |
| @@ -611,10 +729,11 @@ | ||
| 611 | 729 | p->zTicketUuid = next_token(&x, &sz); |
| 612 | 730 | if( sz!=HNAME_LEN_SHA1 ) SYNTAX("K-card UUID is the wrong size"); |
| 613 | 731 | if( !validate16(p->zTicketUuid, sz) ){ |
| 614 | 732 | SYNTAX("invalid K-card UUID"); |
| 615 | 733 | } |
| 734 | + p->type = CFTYPE_TICKET; | |
| 616 | 735 | break; |
| 617 | 736 | } |
| 618 | 737 | |
| 619 | 738 | /* |
| 620 | 739 | ** L <wikititle> |
| @@ -628,10 +747,11 @@ | ||
| 628 | 747 | if( p->zWikiTitle==0 ) SYNTAX("missing title on L-card"); |
| 629 | 748 | defossilize(p->zWikiTitle); |
| 630 | 749 | if( !wiki_name_is_wellformed((const unsigned char *)p->zWikiTitle) ){ |
| 631 | 750 | SYNTAX("L-card has malformed wiki name"); |
| 632 | 751 | } |
| 752 | + p->type = CFTYPE_WIKI; | |
| 633 | 753 | break; |
| 634 | 754 | } |
| 635 | 755 | |
| 636 | 756 | /* |
| 637 | 757 | ** M <hash> |
| @@ -653,10 +773,11 @@ | ||
| 653 | 773 | i = p->nCChild++; |
| 654 | 774 | p->azCChild[i] = zUuid; |
| 655 | 775 | if( i>0 && fossil_strcmp(p->azCChild[i-1], zUuid)>=0 ){ |
| 656 | 776 | SYNTAX("M-card in the wrong order"); |
| 657 | 777 | } |
| 778 | + p->type = CFTYPE_CLUSTER; | |
| 658 | 779 | break; |
| 659 | 780 | } |
| 660 | 781 | |
| 661 | 782 | /* |
| 662 | 783 | ** N <uuid> |
| @@ -717,10 +838,11 @@ | ||
| 717 | 838 | p->aCherrypick[n].zCPTarget = zUuid; |
| 718 | 839 | p->aCherrypick[n].zCPBase = zUuid = next_token(&x, &sz); |
| 719 | 840 | if( zUuid && !hname_validate(zUuid,sz) ){ |
| 720 | 841 | SYNTAX("invalid second hash on Q-card"); |
| 721 | 842 | } |
| 843 | + p->type = CFTYPE_MANIFEST; | |
| 722 | 844 | break; |
| 723 | 845 | } |
| 724 | 846 | |
| 725 | 847 | /* |
| 726 | 848 | ** R <md5sum> |
| @@ -731,10 +853,11 @@ | ||
| 731 | 853 | case 'R': { |
| 732 | 854 | if( p->zRepoCksum!=0 ) SYNTAX("more than one R-card"); |
| 733 | 855 | p->zRepoCksum = next_token(&x, &sz); |
| 734 | 856 | if( sz!=32 ) SYNTAX("wrong size cksum on R-card"); |
| 735 | 857 | if( !validate16(p->zRepoCksum, 32) ) SYNTAX("malformed R-card cksum"); |
| 858 | + p->type = CFTYPE_MANIFEST; | |
| 736 | 859 | break; |
| 737 | 860 | } |
| 738 | 861 | |
| 739 | 862 | /* |
| 740 | 863 | ** T (+|*|-)<tagname> <uuid> ?<value>? |
| @@ -759,11 +882,11 @@ | ||
| 759 | 882 | if( zUuid==0 ) SYNTAX("missing artifact hash on T-card"); |
| 760 | 883 | zValue = next_token(&x, 0); |
| 761 | 884 | if( zValue ) defossilize(zValue); |
| 762 | 885 | if( hname_validate(zUuid, sz) ){ |
| 763 | 886 | /* A valid artifact hash */ |
| 764 | - if( p->zEventId ) SYNTAX("non-self-referential T-card in event"); | |
| 887 | + if( p->zEventId ) SYNTAX("non-self-referential T-card in technote"); | |
| 765 | 888 | }else if( sz==1 && zUuid[0]=='*' ){ |
| 766 | 889 | zUuid = 0; |
| 767 | 890 | hasSelfRefTag = 1; |
| 768 | 891 | if( p->zEventId && zName[0]!='+' ){ |
| 769 | 892 | SYNTAX("propagating T-card in event"); |
| @@ -858,104 +981,65 @@ | ||
| 858 | 981 | */ |
| 859 | 982 | case 'Z': { |
| 860 | 983 | zUuid = next_token(&x, &sz); |
| 861 | 984 | if( sz!=32 ) SYNTAX("wrong size for Z-card cksum"); |
| 862 | 985 | if( !validate16(zUuid, 32) ) SYNTAX("malformed Z-card cksum"); |
| 863 | - seenZ = 1; | |
| 864 | 986 | break; |
| 865 | 987 | } |
| 866 | 988 | default: { |
| 867 | 989 | SYNTAX("unrecognized card"); |
| 868 | 990 | } |
| 869 | 991 | } |
| 870 | 992 | } |
| 871 | 993 | if( x.z<x.zEnd ) SYNTAX("extra characters at end of card"); |
| 872 | 994 | |
| 873 | - if( p->nCChild>0 ){ | |
| 874 | - if( p->zAttachName | |
| 875 | - || p->zBaseline | |
| 876 | - || p->zComment | |
| 877 | - || p->rDate>0.0 | |
| 878 | - || p->zEventId | |
| 879 | - || p->nFile>0 | |
| 880 | - || p->nField>0 | |
| 881 | - || p->zTicketUuid | |
| 882 | - || p->zWikiTitle | |
| 883 | - || p->zMimetype | |
| 884 | - || p->nParent>0 | |
| 885 | - || p->nCherrypick>0 | |
| 886 | - || p->zRepoCksum | |
| 887 | - || p->nTag>0 | |
| 888 | - || p->zUser | |
| 889 | - || p->zWiki | |
| 890 | - ){ | |
| 891 | - SYNTAX("cluster contains a card other than M- or Z-"); | |
| 892 | - } | |
| 893 | - if( !seenZ ) SYNTAX("missing Z-card on cluster"); | |
| 894 | - p->type = CFTYPE_CLUSTER; | |
| 895 | - }else if( p->zEventId ){ | |
| 896 | - if( p->zAttachName ) SYNTAX("A-card in event"); | |
| 897 | - if( p->zBaseline ) SYNTAX("B-card in event"); | |
| 898 | - if( p->rDate<=0.0 ) SYNTAX("missing date on event"); | |
| 899 | - if( p->nFile>0 ) SYNTAX("F-card in event"); | |
| 900 | - if( p->nField>0 ) SYNTAX("J-card in event"); | |
| 901 | - if( p->zTicketUuid ) SYNTAX("K-card in event"); | |
| 902 | - if( p->zWikiTitle!=0 ) SYNTAX("L-card in event"); | |
| 903 | - if( p->zRepoCksum ) SYNTAX("R-card in event"); | |
| 904 | - if( p->zWiki==0 ) SYNTAX("missing W-card on event"); | |
| 905 | - if( !seenZ ) SYNTAX("missing Z-card on event"); | |
| 906 | - p->type = CFTYPE_EVENT; | |
| 907 | - }else if( p->zWiki!=0 || p->zWikiTitle!=0 ){ | |
| 908 | - if( p->zAttachName ) SYNTAX("A-card in wiki"); | |
| 909 | - if( p->zBaseline ) SYNTAX("B-card in wiki"); | |
| 910 | - if( p->rDate<=0.0 ) SYNTAX("missing date on wiki"); | |
| 911 | - if( p->nFile>0 ) SYNTAX("F-card in wiki"); | |
| 912 | - if( p->nField>0 ) SYNTAX("J-card in wiki"); | |
| 913 | - if( p->zTicketUuid ) SYNTAX("K-card in wiki"); | |
| 914 | - if( p->zWikiTitle==0 ) SYNTAX("missing L-card on wiki"); | |
| 915 | - if( p->zRepoCksum ) SYNTAX("R-card in wiki"); | |
| 916 | - if( p->nTag>0 ) SYNTAX("T-card in wiki"); | |
| 917 | - if( p->zWiki==0 ) SYNTAX("missing W-card on wiki"); | |
| 918 | - if( !seenZ ) SYNTAX("missing Z-card on wiki"); | |
| 919 | - p->type = CFTYPE_WIKI; | |
| 920 | - }else if( hasSelfRefTag || p->nFile>0 || p->zRepoCksum!=0 || p->zBaseline | |
| 921 | - || p->nParent>0 ){ | |
| 922 | - if( p->zAttachName ) SYNTAX("A-card in manifest"); | |
| 923 | - if( p->rDate<=0.0 ) SYNTAX("missing date on manifest"); | |
| 924 | - if( p->nField>0 ) SYNTAX("J-card in manifest"); | |
| 925 | - if( p->zTicketUuid ) SYNTAX("K-card in manifest"); | |
| 926 | - p->type = CFTYPE_MANIFEST; | |
| 927 | - }else if( p->nField>0 || p->zTicketUuid!=0 ){ | |
| 928 | - if( p->zAttachName ) SYNTAX("A-card in ticket"); | |
| 929 | - if( p->rDate<=0.0 ) SYNTAX("missing date on ticket"); | |
| 930 | - if( p->nField==0 ) SYNTAX("missing J-card on ticket"); | |
| 931 | - if( p->zTicketUuid==0 ) SYNTAX("missing K-card on ticket"); | |
| 932 | - if( p->zMimetype) SYNTAX("N-card in ticket"); | |
| 933 | - if( p->nTag>0 ) SYNTAX("T-card in ticket"); | |
| 934 | - if( p->zUser==0 ) SYNTAX("missing U-card on ticket"); | |
| 935 | - if( !seenZ ) SYNTAX("missing Z-card on ticket"); | |
| 936 | - p->type = CFTYPE_TICKET; | |
| 937 | - }else if( p->zAttachName ){ | |
| 938 | - if( p->rDate<=0.0 ) SYNTAX("missing date on attachment"); | |
| 939 | - if( p->nTag>0 ) SYNTAX("T-card in attachment"); | |
| 940 | - if( !seenZ ) SYNTAX("missing Z-card on attachment"); | |
| 941 | - p->type = CFTYPE_ATTACHMENT; | |
| 942 | - }else{ | |
| 943 | - if( p->rDate<=0.0 ) SYNTAX("missing date on control"); | |
| 944 | - if( p->zMimetype ) SYNTAX("N-card in control"); | |
| 945 | - if( !seenZ ) SYNTAX("missing Z-card on control"); | |
| 946 | - p->type = CFTYPE_CONTROL; | |
| 947 | - } | |
| 995 | + /* If the artifact type has not yet been determined, then compute | |
| 996 | + ** it now. */ | |
| 997 | + if( p->type==0 ){ | |
| 998 | + p->type = p->zComment!=0 ? CFTYPE_MANIFEST : CFTYPE_CONTROL; | |
| 999 | + } | |
| 1000 | + | |
| 1001 | + /* Verify that no disallowed cards are present for this artifact type */ | |
| 1002 | + m = manifest_card_mask(manifestCardTypes[p->type-1].zAllowed); | |
| 1003 | + if( seenCard & ~m ){ | |
| 1004 | + sqlite3_snprintf(sizeof(zErrBuf), zErrBuf, "%c-card in %s", | |
| 1005 | + maskToType(seenCard & ~m), | |
| 1006 | + azNameOfMType[p->type-1]); | |
| 1007 | + zErr = zErrBuf; | |
| 1008 | + goto manifest_syntax_error; | |
| 1009 | + } | |
| 1010 | + | |
| 1011 | + /* Verify that all required cards are present for this artifact type */ | |
| 1012 | + m = manifest_card_mask(manifestCardTypes[p->type-1].zRequired); | |
| 1013 | + if( ~seenCard & m ){ | |
| 1014 | + sqlite3_snprintf(sizeof(zErrBuf), zErrBuf, "%c-card missing in %s", | |
| 1015 | + maskToType(~seenCard & m), | |
| 1016 | + azNameOfMType[p->type-1]); | |
| 1017 | + zErr = zErrBuf; | |
| 1018 | + goto manifest_syntax_error; | |
| 1019 | + } | |
| 1020 | + | |
| 1021 | + /* Additional checks based on artifact type */ | |
| 1022 | + switch( p->type ){ | |
| 1023 | + case CFTYPE_FORUM: { | |
| 1024 | + if( p->zThreadTitle && p->zInReplyTo ){ | |
| 1025 | + SYNTAX("cannot have I-card and H-card in a forum post"); | |
| 1026 | + } | |
| 1027 | + if( p->nParent>1 ) SYNTAX("too many arguments to P-card"); | |
| 1028 | + break; | |
| 1029 | + } | |
| 1030 | + } | |
| 1031 | + | |
| 948 | 1032 | md5sum_init(); |
| 949 | 1033 | if( !isRepeat ) g.parseCnt[p->type]++; |
| 950 | 1034 | return p; |
| 951 | 1035 | |
| 952 | 1036 | manifest_syntax_error: |
| 953 | 1037 | { |
| 954 | 1038 | char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 955 | 1039 | if( zUuid ){ |
| 956 | - blob_appendf(pErr, "manifest [%s] ", zUuid); | |
| 1040 | + blob_appendf(pErr, "artifact [%s] ", zUuid); | |
| 957 | 1041 | fossil_free(zUuid); |
| 958 | 1042 | } |
| 959 | 1043 | } |
| 960 | 1044 | if( zErr ){ |
| 961 | 1045 | blob_appendf(pErr, "line %d: %s", lineNo, zErr); |
| 962 | 1046 |
| --- src/manifest.c | |
| +++ src/manifest.c | |
| @@ -34,10 +34,11 @@ | |
| 34 | #define CFTYPE_CONTROL 3 |
| 35 | #define CFTYPE_WIKI 4 |
| 36 | #define CFTYPE_TICKET 5 |
| 37 | #define CFTYPE_ATTACHMENT 6 |
| 38 | #define CFTYPE_EVENT 7 |
| 39 | |
| 40 | /* |
| 41 | ** File permissions used by Fossil internally. |
| 42 | */ |
| 43 | #define PERM_REG 0 /* regular file */ |
| @@ -76,16 +77,19 @@ | |
| 76 | char *zUser; /* Name of the user from the U card. */ |
| 77 | char *zRepoCksum; /* MD5 checksum of the baseline content. R card. */ |
| 78 | char *zWiki; /* Text of the wiki page. W card. */ |
| 79 | char *zWikiTitle; /* Name of the wiki page. L card. */ |
| 80 | char *zMimetype; /* Mime type of wiki or comment text. N card. */ |
| 81 | double rEventDate; /* Date of an event. E card. */ |
| 82 | char *zEventId; /* Artifact hash for an event. E card. */ |
| 83 | char *zTicketUuid; /* UUID for a ticket. K card. */ |
| 84 | char *zAttachName; /* Filename of an attachment. A card. */ |
| 85 | char *zAttachSrc; /* Artifact hash for document being attached. A card. */ |
| 86 | char *zAttachTarget; /* Ticket or wiki that attachment applies to. A card */ |
| 87 | int nFile; /* Number of F cards */ |
| 88 | int nFileAlloc; /* Slots allocated in aFile[] */ |
| 89 | int iFile; /* Index of current file in iterator */ |
| 90 | ManifestFile *aFile; /* One entry for each F-card */ |
| 91 | int nParent; /* Number of parents. */ |
| @@ -112,10 +116,42 @@ | |
| 112 | char *zName; /* Key or field name */ |
| 113 | char *zValue; /* Value of the field */ |
| 114 | } *aField; /* One for each J card */ |
| 115 | }; |
| 116 | #endif |
| 117 | |
| 118 | /* |
| 119 | ** A cache of parsed manifests. This reduces the number of |
| 120 | ** calls to manifest_parse() when doing a rebuild. |
| 121 | */ |
| @@ -147,10 +183,35 @@ | |
| 147 | if( p->pBaseline ) manifest_destroy(p->pBaseline); |
| 148 | memset(p, 0, sizeof(*p)); |
| 149 | fossil_free(p); |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | /* |
| 154 | ** Add an element to the manifest cache using LRU replacement. |
| 155 | */ |
| 156 | void manifest_cache_insert(Manifest *p){ |
| @@ -352,11 +413,10 @@ | |
| 352 | ** The card type determines the other parameters to the card. |
| 353 | ** Cards must occur in lexicographical order. |
| 354 | */ |
| 355 | Manifest *manifest_parse(Blob *pContent, int rid, Blob *pErr){ |
| 356 | Manifest *p; |
| 357 | int seenZ = 0; |
| 358 | int i, lineNo=0; |
| 359 | ManifestText x; |
| 360 | char cPrevType = 0; |
| 361 | char cType; |
| 362 | char *z; |
| @@ -364,10 +424,13 @@ | |
| 364 | char *zUuid; |
| 365 | int sz = 0; |
| 366 | int isRepeat, hasSelfRefTag = 0; |
| 367 | static Bag seen; |
| 368 | const char *zErr = 0; |
| 369 | |
| 370 | if( rid==0 ){ |
| 371 | isRepeat = 1; |
| 372 | }else if( bag_find(&seen, rid) ){ |
| 373 | isRepeat = 1; |
| @@ -422,10 +485,12 @@ | |
| 422 | x.z = z; |
| 423 | x.zEnd = &z[n]; |
| 424 | x.atEol = 1; |
| 425 | while( (cType = next_card(&x))!=0 && cType>=cPrevType ){ |
| 426 | lineNo++; |
| 427 | switch( cType ){ |
| 428 | /* |
| 429 | ** A <filename> <target> ?<source>? |
| 430 | ** |
| 431 | ** Identifies an attachment to either a wiki page or a ticket. |
| @@ -454,10 +519,11 @@ | |
| 454 | SYNTAX("invalid source on A-card"); |
| 455 | } |
| 456 | p->zAttachName = (char*)file_tail(zName); |
| 457 | p->zAttachSrc = zSrc; |
| 458 | p->zAttachTarget = zTarget; |
| 459 | break; |
| 460 | } |
| 461 | |
| 462 | /* |
| 463 | ** B <uuid> |
| @@ -469,10 +535,11 @@ | |
| 469 | p->zBaseline = next_token(&x, &sz); |
| 470 | if( p->zBaseline==0 ) SYNTAX("missing hash on B-card"); |
| 471 | if( !hname_validate(p->zBaseline,sz) ){ |
| 472 | SYNTAX("invalid hash on B-card"); |
| 473 | } |
| 474 | break; |
| 475 | } |
| 476 | |
| 477 | |
| 478 | /* |
| @@ -520,10 +587,11 @@ | |
| 520 | if( p->rEventDate<=0.0 ) SYNTAX("malformed date on E-card"); |
| 521 | p->zEventId = next_token(&x, &sz); |
| 522 | if( !hname_validate(p->zEventId, sz) ){ |
| 523 | SYNTAX("malformed hash on E-card"); |
| 524 | } |
| 525 | break; |
| 526 | } |
| 527 | |
| 528 | /* |
| 529 | ** F <filename> ?<uuid>? ?<permissions>? ?<old-name>? |
| @@ -565,10 +633,59 @@ | |
| 565 | p->aFile[i].zPerm = zPerm; |
| 566 | p->aFile[i].zPrior = zPriorName; |
| 567 | if( i>0 && fossil_strcmp(p->aFile[i-1].zName, zName)>=0 ){ |
| 568 | SYNTAX("incorrect F-card sort order"); |
| 569 | } |
| 570 | break; |
| 571 | } |
| 572 | |
| 573 | /* |
| 574 | ** J <name> ?<value>? |
| @@ -594,10 +711,11 @@ | |
| 594 | p->aField[i].zName = zName; |
| 595 | p->aField[i].zValue = zValue; |
| 596 | if( i>0 && fossil_strcmp(p->aField[i-1].zName, zName)>=0 ){ |
| 597 | SYNTAX("incorrect J-card sort order"); |
| 598 | } |
| 599 | break; |
| 600 | } |
| 601 | |
| 602 | |
| 603 | /* |
| @@ -611,10 +729,11 @@ | |
| 611 | p->zTicketUuid = next_token(&x, &sz); |
| 612 | if( sz!=HNAME_LEN_SHA1 ) SYNTAX("K-card UUID is the wrong size"); |
| 613 | if( !validate16(p->zTicketUuid, sz) ){ |
| 614 | SYNTAX("invalid K-card UUID"); |
| 615 | } |
| 616 | break; |
| 617 | } |
| 618 | |
| 619 | /* |
| 620 | ** L <wikititle> |
| @@ -628,10 +747,11 @@ | |
| 628 | if( p->zWikiTitle==0 ) SYNTAX("missing title on L-card"); |
| 629 | defossilize(p->zWikiTitle); |
| 630 | if( !wiki_name_is_wellformed((const unsigned char *)p->zWikiTitle) ){ |
| 631 | SYNTAX("L-card has malformed wiki name"); |
| 632 | } |
| 633 | break; |
| 634 | } |
| 635 | |
| 636 | /* |
| 637 | ** M <hash> |
| @@ -653,10 +773,11 @@ | |
| 653 | i = p->nCChild++; |
| 654 | p->azCChild[i] = zUuid; |
| 655 | if( i>0 && fossil_strcmp(p->azCChild[i-1], zUuid)>=0 ){ |
| 656 | SYNTAX("M-card in the wrong order"); |
| 657 | } |
| 658 | break; |
| 659 | } |
| 660 | |
| 661 | /* |
| 662 | ** N <uuid> |
| @@ -717,10 +838,11 @@ | |
| 717 | p->aCherrypick[n].zCPTarget = zUuid; |
| 718 | p->aCherrypick[n].zCPBase = zUuid = next_token(&x, &sz); |
| 719 | if( zUuid && !hname_validate(zUuid,sz) ){ |
| 720 | SYNTAX("invalid second hash on Q-card"); |
| 721 | } |
| 722 | break; |
| 723 | } |
| 724 | |
| 725 | /* |
| 726 | ** R <md5sum> |
| @@ -731,10 +853,11 @@ | |
| 731 | case 'R': { |
| 732 | if( p->zRepoCksum!=0 ) SYNTAX("more than one R-card"); |
| 733 | p->zRepoCksum = next_token(&x, &sz); |
| 734 | if( sz!=32 ) SYNTAX("wrong size cksum on R-card"); |
| 735 | if( !validate16(p->zRepoCksum, 32) ) SYNTAX("malformed R-card cksum"); |
| 736 | break; |
| 737 | } |
| 738 | |
| 739 | /* |
| 740 | ** T (+|*|-)<tagname> <uuid> ?<value>? |
| @@ -759,11 +882,11 @@ | |
| 759 | if( zUuid==0 ) SYNTAX("missing artifact hash on T-card"); |
| 760 | zValue = next_token(&x, 0); |
| 761 | if( zValue ) defossilize(zValue); |
| 762 | if( hname_validate(zUuid, sz) ){ |
| 763 | /* A valid artifact hash */ |
| 764 | if( p->zEventId ) SYNTAX("non-self-referential T-card in event"); |
| 765 | }else if( sz==1 && zUuid[0]=='*' ){ |
| 766 | zUuid = 0; |
| 767 | hasSelfRefTag = 1; |
| 768 | if( p->zEventId && zName[0]!='+' ){ |
| 769 | SYNTAX("propagating T-card in event"); |
| @@ -858,104 +981,65 @@ | |
| 858 | */ |
| 859 | case 'Z': { |
| 860 | zUuid = next_token(&x, &sz); |
| 861 | if( sz!=32 ) SYNTAX("wrong size for Z-card cksum"); |
| 862 | if( !validate16(zUuid, 32) ) SYNTAX("malformed Z-card cksum"); |
| 863 | seenZ = 1; |
| 864 | break; |
| 865 | } |
| 866 | default: { |
| 867 | SYNTAX("unrecognized card"); |
| 868 | } |
| 869 | } |
| 870 | } |
| 871 | if( x.z<x.zEnd ) SYNTAX("extra characters at end of card"); |
| 872 | |
| 873 | if( p->nCChild>0 ){ |
| 874 | if( p->zAttachName |
| 875 | || p->zBaseline |
| 876 | || p->zComment |
| 877 | || p->rDate>0.0 |
| 878 | || p->zEventId |
| 879 | || p->nFile>0 |
| 880 | || p->nField>0 |
| 881 | || p->zTicketUuid |
| 882 | || p->zWikiTitle |
| 883 | || p->zMimetype |
| 884 | || p->nParent>0 |
| 885 | || p->nCherrypick>0 |
| 886 | || p->zRepoCksum |
| 887 | || p->nTag>0 |
| 888 | || p->zUser |
| 889 | || p->zWiki |
| 890 | ){ |
| 891 | SYNTAX("cluster contains a card other than M- or Z-"); |
| 892 | } |
| 893 | if( !seenZ ) SYNTAX("missing Z-card on cluster"); |
| 894 | p->type = CFTYPE_CLUSTER; |
| 895 | }else if( p->zEventId ){ |
| 896 | if( p->zAttachName ) SYNTAX("A-card in event"); |
| 897 | if( p->zBaseline ) SYNTAX("B-card in event"); |
| 898 | if( p->rDate<=0.0 ) SYNTAX("missing date on event"); |
| 899 | if( p->nFile>0 ) SYNTAX("F-card in event"); |
| 900 | if( p->nField>0 ) SYNTAX("J-card in event"); |
| 901 | if( p->zTicketUuid ) SYNTAX("K-card in event"); |
| 902 | if( p->zWikiTitle!=0 ) SYNTAX("L-card in event"); |
| 903 | if( p->zRepoCksum ) SYNTAX("R-card in event"); |
| 904 | if( p->zWiki==0 ) SYNTAX("missing W-card on event"); |
| 905 | if( !seenZ ) SYNTAX("missing Z-card on event"); |
| 906 | p->type = CFTYPE_EVENT; |
| 907 | }else if( p->zWiki!=0 || p->zWikiTitle!=0 ){ |
| 908 | if( p->zAttachName ) SYNTAX("A-card in wiki"); |
| 909 | if( p->zBaseline ) SYNTAX("B-card in wiki"); |
| 910 | if( p->rDate<=0.0 ) SYNTAX("missing date on wiki"); |
| 911 | if( p->nFile>0 ) SYNTAX("F-card in wiki"); |
| 912 | if( p->nField>0 ) SYNTAX("J-card in wiki"); |
| 913 | if( p->zTicketUuid ) SYNTAX("K-card in wiki"); |
| 914 | if( p->zWikiTitle==0 ) SYNTAX("missing L-card on wiki"); |
| 915 | if( p->zRepoCksum ) SYNTAX("R-card in wiki"); |
| 916 | if( p->nTag>0 ) SYNTAX("T-card in wiki"); |
| 917 | if( p->zWiki==0 ) SYNTAX("missing W-card on wiki"); |
| 918 | if( !seenZ ) SYNTAX("missing Z-card on wiki"); |
| 919 | p->type = CFTYPE_WIKI; |
| 920 | }else if( hasSelfRefTag || p->nFile>0 || p->zRepoCksum!=0 || p->zBaseline |
| 921 | || p->nParent>0 ){ |
| 922 | if( p->zAttachName ) SYNTAX("A-card in manifest"); |
| 923 | if( p->rDate<=0.0 ) SYNTAX("missing date on manifest"); |
| 924 | if( p->nField>0 ) SYNTAX("J-card in manifest"); |
| 925 | if( p->zTicketUuid ) SYNTAX("K-card in manifest"); |
| 926 | p->type = CFTYPE_MANIFEST; |
| 927 | }else if( p->nField>0 || p->zTicketUuid!=0 ){ |
| 928 | if( p->zAttachName ) SYNTAX("A-card in ticket"); |
| 929 | if( p->rDate<=0.0 ) SYNTAX("missing date on ticket"); |
| 930 | if( p->nField==0 ) SYNTAX("missing J-card on ticket"); |
| 931 | if( p->zTicketUuid==0 ) SYNTAX("missing K-card on ticket"); |
| 932 | if( p->zMimetype) SYNTAX("N-card in ticket"); |
| 933 | if( p->nTag>0 ) SYNTAX("T-card in ticket"); |
| 934 | if( p->zUser==0 ) SYNTAX("missing U-card on ticket"); |
| 935 | if( !seenZ ) SYNTAX("missing Z-card on ticket"); |
| 936 | p->type = CFTYPE_TICKET; |
| 937 | }else if( p->zAttachName ){ |
| 938 | if( p->rDate<=0.0 ) SYNTAX("missing date on attachment"); |
| 939 | if( p->nTag>0 ) SYNTAX("T-card in attachment"); |
| 940 | if( !seenZ ) SYNTAX("missing Z-card on attachment"); |
| 941 | p->type = CFTYPE_ATTACHMENT; |
| 942 | }else{ |
| 943 | if( p->rDate<=0.0 ) SYNTAX("missing date on control"); |
| 944 | if( p->zMimetype ) SYNTAX("N-card in control"); |
| 945 | if( !seenZ ) SYNTAX("missing Z-card on control"); |
| 946 | p->type = CFTYPE_CONTROL; |
| 947 | } |
| 948 | md5sum_init(); |
| 949 | if( !isRepeat ) g.parseCnt[p->type]++; |
| 950 | return p; |
| 951 | |
| 952 | manifest_syntax_error: |
| 953 | { |
| 954 | char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 955 | if( zUuid ){ |
| 956 | blob_appendf(pErr, "manifest [%s] ", zUuid); |
| 957 | fossil_free(zUuid); |
| 958 | } |
| 959 | } |
| 960 | if( zErr ){ |
| 961 | blob_appendf(pErr, "line %d: %s", lineNo, zErr); |
| 962 |
| --- src/manifest.c | |
| +++ src/manifest.c | |
| @@ -34,10 +34,11 @@ | |
| 34 | #define CFTYPE_CONTROL 3 |
| 35 | #define CFTYPE_WIKI 4 |
| 36 | #define CFTYPE_TICKET 5 |
| 37 | #define CFTYPE_ATTACHMENT 6 |
| 38 | #define CFTYPE_EVENT 7 |
| 39 | #define CFTYPE_FORUM 8 |
| 40 | |
| 41 | /* |
| 42 | ** File permissions used by Fossil internally. |
| 43 | */ |
| 44 | #define PERM_REG 0 /* regular file */ |
| @@ -76,16 +77,19 @@ | |
| 77 | char *zUser; /* Name of the user from the U card. */ |
| 78 | char *zRepoCksum; /* MD5 checksum of the baseline content. R card. */ |
| 79 | char *zWiki; /* Text of the wiki page. W card. */ |
| 80 | char *zWikiTitle; /* Name of the wiki page. L card. */ |
| 81 | char *zMimetype; /* Mime type of wiki or comment text. N card. */ |
| 82 | char *zThreadTitle; /* The forum thread title. H card */ |
| 83 | double rEventDate; /* Date of an event. E card. */ |
| 84 | char *zEventId; /* Artifact hash for an event. E card. */ |
| 85 | char *zTicketUuid; /* UUID for a ticket. K card. */ |
| 86 | char *zAttachName; /* Filename of an attachment. A card. */ |
| 87 | char *zAttachSrc; /* Artifact hash for document being attached. A card. */ |
| 88 | char *zAttachTarget; /* Ticket or wiki that attachment applies to. A card */ |
| 89 | char *zThreadRoot; /* Thread root artifact. G card */ |
| 90 | char *zInReplyTo; /* Forum in-reply-to artifact. I card */ |
| 91 | int nFile; /* Number of F cards */ |
| 92 | int nFileAlloc; /* Slots allocated in aFile[] */ |
| 93 | int iFile; /* Index of current file in iterator */ |
| 94 | ManifestFile *aFile; /* One entry for each F-card */ |
| 95 | int nParent; /* Number of parents. */ |
| @@ -112,10 +116,42 @@ | |
| 116 | char *zName; /* Key or field name */ |
| 117 | char *zValue; /* Value of the field */ |
| 118 | } *aField; /* One for each J card */ |
| 119 | }; |
| 120 | #endif |
| 121 | |
| 122 | /* |
| 123 | ** Allowed and required card types in each style of artifact |
| 124 | */ |
| 125 | static struct { |
| 126 | const char *zAllowed; /* Allowed cards. Human-readable */ |
| 127 | const char *zRequired; /* Required cards. Human-readable */ |
| 128 | } manifestCardTypes[] = { |
| 129 | /* Allowed Required */ |
| 130 | /* CFTYPE_MANIFEST 1 */ { "BCDFNPQRTUZ", "CDUZ" }, |
| 131 | /* CFTYPE_CLUSTER 2 */ { "MZ", "MZ" }, |
| 132 | /* CFTYPE_CONTROL 3 */ { "DTUZ", "DTUZ" }, |
| 133 | /* CFTYPE_WIKI 4 */ { "DLNPUWZ", "DLUWZ" }, |
| 134 | /* CFTYPE_TICKET 5 */ { "DJKUZ", "DJKUZ" }, |
| 135 | /* CFTYPE_ATTACHMENT 6 */ { "ACDNUZ", "ADZ" }, |
| 136 | /* CFTYPE_EVENT 7 */ { "CDENPTUWZ", "DEWZ" }, |
| 137 | /* CFTYPE_FORUM 8 */ { "DGHINPUWZ", "DUWZ" }, |
| 138 | }; |
| 139 | |
| 140 | /* |
| 141 | ** Names of manifest types |
| 142 | */ |
| 143 | static const char *azNameOfMType[] = { |
| 144 | "manifest", |
| 145 | "cluster", |
| 146 | "tag", |
| 147 | "wiki", |
| 148 | "ticket", |
| 149 | "attachment", |
| 150 | "technote", |
| 151 | "forum post" |
| 152 | }; |
| 153 | |
| 154 | /* |
| 155 | ** A cache of parsed manifests. This reduces the number of |
| 156 | ** calls to manifest_parse() when doing a rebuild. |
| 157 | */ |
| @@ -147,10 +183,35 @@ | |
| 183 | if( p->pBaseline ) manifest_destroy(p->pBaseline); |
| 184 | memset(p, 0, sizeof(*p)); |
| 185 | fossil_free(p); |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | /* |
| 190 | ** Given a string of upper-case letters, compute a mask of the letters |
| 191 | ** present. For example, "ABC" computes 0x0007. "DE" gives 0x0018". |
| 192 | */ |
| 193 | static unsigned int manifest_card_mask(const char *z){ |
| 194 | unsigned int m = 0; |
| 195 | char c; |
| 196 | while( (c = *(z++))>='A' && c<='Z' ){ |
| 197 | m |= 1 << (c - 'A'); |
| 198 | } |
| 199 | return m; |
| 200 | } |
| 201 | |
| 202 | /* |
| 203 | ** Given an integer mask representing letters A-Z, return the |
| 204 | ** letter which is the first bit set in the mask. Example: |
| 205 | ** 0x03520 gives 'F' since the F-bit is the lowest. |
| 206 | */ |
| 207 | static char maskToType(unsigned int x){ |
| 208 | char c = 'A'; |
| 209 | if( x==0 ) return '?'; |
| 210 | while( (x&1)==0 ){ x >>= 1; c++; } |
| 211 | return c; |
| 212 | } |
| 213 | |
| 214 | /* |
| 215 | ** Add an element to the manifest cache using LRU replacement. |
| 216 | */ |
| 217 | void manifest_cache_insert(Manifest *p){ |
| @@ -352,11 +413,10 @@ | |
| 413 | ** The card type determines the other parameters to the card. |
| 414 | ** Cards must occur in lexicographical order. |
| 415 | */ |
| 416 | Manifest *manifest_parse(Blob *pContent, int rid, Blob *pErr){ |
| 417 | Manifest *p; |
| 418 | int i, lineNo=0; |
| 419 | ManifestText x; |
| 420 | char cPrevType = 0; |
| 421 | char cType; |
| 422 | char *z; |
| @@ -364,10 +424,13 @@ | |
| 424 | char *zUuid; |
| 425 | int sz = 0; |
| 426 | int isRepeat, hasSelfRefTag = 0; |
| 427 | static Bag seen; |
| 428 | const char *zErr = 0; |
| 429 | unsigned int m; |
| 430 | unsigned int seenCard = 0; /* Which card types have been seen */ |
| 431 | char zErrBuf[100]; /* Write error messages here */ |
| 432 | |
| 433 | if( rid==0 ){ |
| 434 | isRepeat = 1; |
| 435 | }else if( bag_find(&seen, rid) ){ |
| 436 | isRepeat = 1; |
| @@ -422,10 +485,12 @@ | |
| 485 | x.z = z; |
| 486 | x.zEnd = &z[n]; |
| 487 | x.atEol = 1; |
| 488 | while( (cType = next_card(&x))!=0 && cType>=cPrevType ){ |
| 489 | lineNo++; |
| 490 | if( cType<'A' || cType>'Z' ) SYNTAX("bad card type"); |
| 491 | seenCard |= 1 << (cType-'A'); |
| 492 | switch( cType ){ |
| 493 | /* |
| 494 | ** A <filename> <target> ?<source>? |
| 495 | ** |
| 496 | ** Identifies an attachment to either a wiki page or a ticket. |
| @@ -454,10 +519,11 @@ | |
| 519 | SYNTAX("invalid source on A-card"); |
| 520 | } |
| 521 | p->zAttachName = (char*)file_tail(zName); |
| 522 | p->zAttachSrc = zSrc; |
| 523 | p->zAttachTarget = zTarget; |
| 524 | p->type = CFTYPE_ATTACHMENT; |
| 525 | break; |
| 526 | } |
| 527 | |
| 528 | /* |
| 529 | ** B <uuid> |
| @@ -469,10 +535,11 @@ | |
| 535 | p->zBaseline = next_token(&x, &sz); |
| 536 | if( p->zBaseline==0 ) SYNTAX("missing hash on B-card"); |
| 537 | if( !hname_validate(p->zBaseline,sz) ){ |
| 538 | SYNTAX("invalid hash on B-card"); |
| 539 | } |
| 540 | p->type = CFTYPE_MANIFEST; |
| 541 | break; |
| 542 | } |
| 543 | |
| 544 | |
| 545 | /* |
| @@ -520,10 +587,11 @@ | |
| 587 | if( p->rEventDate<=0.0 ) SYNTAX("malformed date on E-card"); |
| 588 | p->zEventId = next_token(&x, &sz); |
| 589 | if( !hname_validate(p->zEventId, sz) ){ |
| 590 | SYNTAX("malformed hash on E-card"); |
| 591 | } |
| 592 | p->type = CFTYPE_EVENT; |
| 593 | break; |
| 594 | } |
| 595 | |
| 596 | /* |
| 597 | ** F <filename> ?<uuid>? ?<permissions>? ?<old-name>? |
| @@ -565,10 +633,59 @@ | |
| 633 | p->aFile[i].zPerm = zPerm; |
| 634 | p->aFile[i].zPrior = zPriorName; |
| 635 | if( i>0 && fossil_strcmp(p->aFile[i-1].zName, zName)>=0 ){ |
| 636 | SYNTAX("incorrect F-card sort order"); |
| 637 | } |
| 638 | p->type = CFTYPE_MANIFEST; |
| 639 | break; |
| 640 | } |
| 641 | |
| 642 | /* |
| 643 | ** G <hash> |
| 644 | ** |
| 645 | ** A G-card identifies the initial root forum post for the thread |
| 646 | ** of which this post is a part. Forum posts only. |
| 647 | */ |
| 648 | case 'G': { |
| 649 | if( p->zThreadRoot!=0 ) SYNTAX("more than one G-card"); |
| 650 | p->zThreadRoot = next_token(&x, &sz); |
| 651 | if( p->zThreadRoot==0 ) SYNTAX("missing hash on G-card"); |
| 652 | if( !hname_validate(p->zThreadRoot,sz) ){ |
| 653 | SYNTAX("Invalid hash on G-card"); |
| 654 | } |
| 655 | p->type = CFTYPE_FORUM; |
| 656 | break; |
| 657 | } |
| 658 | |
| 659 | /* |
| 660 | ** H <threadtitle> |
| 661 | ** |
| 662 | ** The title for a forum thread. |
| 663 | */ |
| 664 | case 'H': { |
| 665 | if( p->zThreadTitle!=0 ) SYNTAX("more than one H-card"); |
| 666 | p->zThreadTitle = next_token(&x,0); |
| 667 | if( p->zThreadTitle==0 ) SYNTAX("missing title on H-card"); |
| 668 | defossilize(p->zThreadTitle); |
| 669 | p->type = CFTYPE_FORUM; |
| 670 | break; |
| 671 | } |
| 672 | |
| 673 | /* |
| 674 | ** I <hash> |
| 675 | ** |
| 676 | ** A I-card identifies another forum post that the current forum post |
| 677 | ** is in reply to. |
| 678 | */ |
| 679 | case 'I': { |
| 680 | if( p->zInReplyTo!=0 ) SYNTAX("more than one I-card"); |
| 681 | p->zInReplyTo = next_token(&x, &sz); |
| 682 | if( p->zInReplyTo==0 ) SYNTAX("missing hash on I-card"); |
| 683 | if( !hname_validate(p->zInReplyTo,sz) ){ |
| 684 | SYNTAX("Invalid hash on I-card"); |
| 685 | } |
| 686 | p->type = CFTYPE_FORUM; |
| 687 | break; |
| 688 | } |
| 689 | |
| 690 | /* |
| 691 | ** J <name> ?<value>? |
| @@ -594,10 +711,11 @@ | |
| 711 | p->aField[i].zName = zName; |
| 712 | p->aField[i].zValue = zValue; |
| 713 | if( i>0 && fossil_strcmp(p->aField[i-1].zName, zName)>=0 ){ |
| 714 | SYNTAX("incorrect J-card sort order"); |
| 715 | } |
| 716 | p->type = CFTYPE_TICKET; |
| 717 | break; |
| 718 | } |
| 719 | |
| 720 | |
| 721 | /* |
| @@ -611,10 +729,11 @@ | |
| 729 | p->zTicketUuid = next_token(&x, &sz); |
| 730 | if( sz!=HNAME_LEN_SHA1 ) SYNTAX("K-card UUID is the wrong size"); |
| 731 | if( !validate16(p->zTicketUuid, sz) ){ |
| 732 | SYNTAX("invalid K-card UUID"); |
| 733 | } |
| 734 | p->type = CFTYPE_TICKET; |
| 735 | break; |
| 736 | } |
| 737 | |
| 738 | /* |
| 739 | ** L <wikititle> |
| @@ -628,10 +747,11 @@ | |
| 747 | if( p->zWikiTitle==0 ) SYNTAX("missing title on L-card"); |
| 748 | defossilize(p->zWikiTitle); |
| 749 | if( !wiki_name_is_wellformed((const unsigned char *)p->zWikiTitle) ){ |
| 750 | SYNTAX("L-card has malformed wiki name"); |
| 751 | } |
| 752 | p->type = CFTYPE_WIKI; |
| 753 | break; |
| 754 | } |
| 755 | |
| 756 | /* |
| 757 | ** M <hash> |
| @@ -653,10 +773,11 @@ | |
| 773 | i = p->nCChild++; |
| 774 | p->azCChild[i] = zUuid; |
| 775 | if( i>0 && fossil_strcmp(p->azCChild[i-1], zUuid)>=0 ){ |
| 776 | SYNTAX("M-card in the wrong order"); |
| 777 | } |
| 778 | p->type = CFTYPE_CLUSTER; |
| 779 | break; |
| 780 | } |
| 781 | |
| 782 | /* |
| 783 | ** N <uuid> |
| @@ -717,10 +838,11 @@ | |
| 838 | p->aCherrypick[n].zCPTarget = zUuid; |
| 839 | p->aCherrypick[n].zCPBase = zUuid = next_token(&x, &sz); |
| 840 | if( zUuid && !hname_validate(zUuid,sz) ){ |
| 841 | SYNTAX("invalid second hash on Q-card"); |
| 842 | } |
| 843 | p->type = CFTYPE_MANIFEST; |
| 844 | break; |
| 845 | } |
| 846 | |
| 847 | /* |
| 848 | ** R <md5sum> |
| @@ -731,10 +853,11 @@ | |
| 853 | case 'R': { |
| 854 | if( p->zRepoCksum!=0 ) SYNTAX("more than one R-card"); |
| 855 | p->zRepoCksum = next_token(&x, &sz); |
| 856 | if( sz!=32 ) SYNTAX("wrong size cksum on R-card"); |
| 857 | if( !validate16(p->zRepoCksum, 32) ) SYNTAX("malformed R-card cksum"); |
| 858 | p->type = CFTYPE_MANIFEST; |
| 859 | break; |
| 860 | } |
| 861 | |
| 862 | /* |
| 863 | ** T (+|*|-)<tagname> <uuid> ?<value>? |
| @@ -759,11 +882,11 @@ | |
| 882 | if( zUuid==0 ) SYNTAX("missing artifact hash on T-card"); |
| 883 | zValue = next_token(&x, 0); |
| 884 | if( zValue ) defossilize(zValue); |
| 885 | if( hname_validate(zUuid, sz) ){ |
| 886 | /* A valid artifact hash */ |
| 887 | if( p->zEventId ) SYNTAX("non-self-referential T-card in technote"); |
| 888 | }else if( sz==1 && zUuid[0]=='*' ){ |
| 889 | zUuid = 0; |
| 890 | hasSelfRefTag = 1; |
| 891 | if( p->zEventId && zName[0]!='+' ){ |
| 892 | SYNTAX("propagating T-card in event"); |
| @@ -858,104 +981,65 @@ | |
| 981 | */ |
| 982 | case 'Z': { |
| 983 | zUuid = next_token(&x, &sz); |
| 984 | if( sz!=32 ) SYNTAX("wrong size for Z-card cksum"); |
| 985 | if( !validate16(zUuid, 32) ) SYNTAX("malformed Z-card cksum"); |
| 986 | break; |
| 987 | } |
| 988 | default: { |
| 989 | SYNTAX("unrecognized card"); |
| 990 | } |
| 991 | } |
| 992 | } |
| 993 | if( x.z<x.zEnd ) SYNTAX("extra characters at end of card"); |
| 994 | |
| 995 | /* If the artifact type has not yet been determined, then compute |
| 996 | ** it now. */ |
| 997 | if( p->type==0 ){ |
| 998 | p->type = p->zComment!=0 ? CFTYPE_MANIFEST : CFTYPE_CONTROL; |
| 999 | } |
| 1000 | |
| 1001 | /* Verify that no disallowed cards are present for this artifact type */ |
| 1002 | m = manifest_card_mask(manifestCardTypes[p->type-1].zAllowed); |
| 1003 | if( seenCard & ~m ){ |
| 1004 | sqlite3_snprintf(sizeof(zErrBuf), zErrBuf, "%c-card in %s", |
| 1005 | maskToType(seenCard & ~m), |
| 1006 | azNameOfMType[p->type-1]); |
| 1007 | zErr = zErrBuf; |
| 1008 | goto manifest_syntax_error; |
| 1009 | } |
| 1010 | |
| 1011 | /* Verify that all required cards are present for this artifact type */ |
| 1012 | m = manifest_card_mask(manifestCardTypes[p->type-1].zRequired); |
| 1013 | if( ~seenCard & m ){ |
| 1014 | sqlite3_snprintf(sizeof(zErrBuf), zErrBuf, "%c-card missing in %s", |
| 1015 | maskToType(~seenCard & m), |
| 1016 | azNameOfMType[p->type-1]); |
| 1017 | zErr = zErrBuf; |
| 1018 | goto manifest_syntax_error; |
| 1019 | } |
| 1020 | |
| 1021 | /* Additional checks based on artifact type */ |
| 1022 | switch( p->type ){ |
| 1023 | case CFTYPE_FORUM: { |
| 1024 | if( p->zThreadTitle && p->zInReplyTo ){ |
| 1025 | SYNTAX("cannot have I-card and H-card in a forum post"); |
| 1026 | } |
| 1027 | if( p->nParent>1 ) SYNTAX("too many arguments to P-card"); |
| 1028 | break; |
| 1029 | } |
| 1030 | } |
| 1031 | |
| 1032 | md5sum_init(); |
| 1033 | if( !isRepeat ) g.parseCnt[p->type]++; |
| 1034 | return p; |
| 1035 | |
| 1036 | manifest_syntax_error: |
| 1037 | { |
| 1038 | char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 1039 | if( zUuid ){ |
| 1040 | blob_appendf(pErr, "artifact [%s] ", zUuid); |
| 1041 | fossil_free(zUuid); |
| 1042 | } |
| 1043 | } |
| 1044 | if( zErr ){ |
| 1045 | blob_appendf(pErr, "line %d: %s", lineNo, zErr); |
| 1046 |
+11
| --- www/fileformat.wiki | ||
| +++ www/fileformat.wiki | ||
| @@ -692,10 +692,21 @@ | ||
| 692 | 692 | <td> </td> |
| 693 | 693 | <td> </td> |
| 694 | 694 | <td> </td> |
| 695 | 695 | <td> </td> |
| 696 | 696 | </tr> |
| 697 | +<tr> | |
| 698 | +<td><b>G</b> <i>thread-root</i></td> | |
| 699 | +<td> </td> | |
| 700 | +<td> </td> | |
| 701 | +<td> </td> | |
| 702 | +<td> </td> | |
| 703 | +<td> </td> | |
| 704 | +<td> </td> | |
| 705 | +<td> </td> | |
| 706 | +<td align=center><b>0-1</b></td> | |
| 707 | +</tr> | |
| 697 | 708 | <tr> |
| 698 | 709 | <td><b>H</b> <i>thread-title</i></td> |
| 699 | 710 | <td> </td> |
| 700 | 711 | <td> </td> |
| 701 | 712 | <td> </td> |
| 702 | 713 |
| --- www/fileformat.wiki | |
| +++ www/fileformat.wiki | |
| @@ -692,10 +692,21 @@ | |
| 692 | <td> </td> |
| 693 | <td> </td> |
| 694 | <td> </td> |
| 695 | <td> </td> |
| 696 | </tr> |
| 697 | <tr> |
| 698 | <td><b>H</b> <i>thread-title</i></td> |
| 699 | <td> </td> |
| 700 | <td> </td> |
| 701 | <td> </td> |
| 702 |
| --- www/fileformat.wiki | |
| +++ www/fileformat.wiki | |
| @@ -692,10 +692,21 @@ | |
| 692 | <td> </td> |
| 693 | <td> </td> |
| 694 | <td> </td> |
| 695 | <td> </td> |
| 696 | </tr> |
| 697 | <tr> |
| 698 | <td><b>G</b> <i>thread-root</i></td> |
| 699 | <td> </td> |
| 700 | <td> </td> |
| 701 | <td> </td> |
| 702 | <td> </td> |
| 703 | <td> </td> |
| 704 | <td> </td> |
| 705 | <td> </td> |
| 706 | <td align=center><b>0-1</b></td> |
| 707 | </tr> |
| 708 | <tr> |
| 709 | <td><b>H</b> <i>thread-title</i></td> |
| 710 | <td> </td> |
| 711 | <td> </td> |
| 712 | <td> </td> |
| 713 |