Fossil SCM
If path given to /ext is a directory and it contains an index file (of the same names supported by the /doc path), render that index file.
Commit
f28cea2b4375507d262492db3575c54ccb43a2cbb5b489dda7446c5d49fb1330
Parent
5590fb9e0f64194…
2 files changed
+61
-11
+8
+61
-11
| --- src/doc.c | ||
| +++ src/doc.c | ||
| @@ -612,10 +612,64 @@ | ||
| 612 | 612 | cgi_set_content_type(zMime); |
| 613 | 613 | cgi_set_content(pBody); |
| 614 | 614 | } |
| 615 | 615 | } |
| 616 | 616 | |
| 617 | +/* | |
| 618 | +** Returns a list of possible index page names, in the order of their | |
| 619 | +** priority, and writes the number of entries in the list to *pCount | |
| 620 | +** (which must not be NULL). The returned memory is static but the | |
| 621 | +** number of entries depends on compile-time options. | |
| 622 | +*/ | |
| 623 | +const char * const * document_index_list( int * pCount ){ | |
| 624 | + static const char *const azSuffix[] = { | |
| 625 | + "index.html", "index.wiki", "index.md" | |
| 626 | +#ifdef FOSSIL_ENABLE_TH1_DOCS | |
| 627 | + , "index.th1" | |
| 628 | +#endif | |
| 629 | + }; | |
| 630 | + *pCount = (int)count(azSuffix); | |
| 631 | + return azSuffix; | |
| 632 | +} | |
| 633 | + | |
| 634 | +/* | |
| 635 | +** If the given NUL-terminated directory name contains one of the | |
| 636 | +** index files defined by document_index_list(), the path to that file | |
| 637 | +** is returned in the form zDirName/FILE_NAME, else NULL is | |
| 638 | +** returned. Ownership of the returned memory is transfered to the | |
| 639 | +** caller. | |
| 640 | +** | |
| 641 | +** If pNResult is not NULL and non-NULL is returned, the length of the | |
| 642 | +** returned string is written to *pNResult, otherwise pNResult is not | |
| 643 | +** dereferenced. | |
| 644 | +*/ | |
| 645 | +char * document_dir_has_index(const char * zDirName, int * pNResult){ | |
| 646 | + Blob bName = BLOB_INITIALIZER; /* File path */ | |
| 647 | + int i; /* Loop counter */ | |
| 648 | + int nIndex; /* Number of index entries. */ | |
| 649 | + const char * const * azIndex = document_index_list(&nIndex); | |
| 650 | + unsigned int nDirName = strlen(zDirName) | |
| 651 | + /* Cursor for the end of the initial | |
| 652 | + path+separator part of bName */; | |
| 653 | + blob_appendf( &bName, "%s%s", zDirName, | |
| 654 | + zDirName[nDirName-1]=='/' ? "" : "/" ); | |
| 655 | + nDirName = bName.nUsed; | |
| 656 | + for(i=0; i<nIndex; ++i){ | |
| 657 | + bName.nUsed = nDirName; | |
| 658 | + blob_append(&bName, azIndex[i], strlen(azIndex[i])); | |
| 659 | + if(file_isfile(bName.aData, ExtFILE)!=0){ | |
| 660 | + break; | |
| 661 | + } | |
| 662 | + } | |
| 663 | + if(i<nIndex){ | |
| 664 | + if(pNResult) *pNResult = (int)bName.nUsed; | |
| 665 | + return bName.aData; | |
| 666 | + }else{ | |
| 667 | + blob_reset(&bName); | |
| 668 | + return 0; | |
| 669 | + } | |
| 670 | +} | |
| 617 | 671 | |
| 618 | 672 | /* |
| 619 | 673 | ** WEBPAGE: uv |
| 620 | 674 | ** WEBPAGE: doc |
| 621 | 675 | ** URL: /uv/FILE |
| @@ -674,23 +728,19 @@ | ||
| 674 | 728 | Blob filebody; /* Content of the documentation file */ |
| 675 | 729 | Blob title; /* Document title */ |
| 676 | 730 | int nMiss = (-1); /* Failed attempts to find the document */ |
| 677 | 731 | int isUV = g.zPath[0]=='u'; /* True for /uv. False for /doc */ |
| 678 | 732 | const char *zDfltTitle; |
| 679 | - static const char *const azSuffix[] = { | |
| 680 | - "index.html", "index.wiki", "index.md" | |
| 681 | -#ifdef FOSSIL_ENABLE_TH1_DOCS | |
| 682 | - , "index.th1" | |
| 683 | -#endif | |
| 684 | - }; | |
| 733 | + int nSuffix; /* Number of entries in azSuffix */ | |
| 734 | + const char *const * azSuffix = document_index_list(&nSuffix); | |
| 685 | 735 | |
| 686 | 736 | login_check_credentials(); |
| 687 | 737 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 688 | 738 | blob_init(&title, 0, 0); |
| 689 | 739 | zDfltTitle = isUV ? "" : "Documentation"; |
| 690 | 740 | db_begin_transaction(); |
| 691 | - while( rid==0 && (++nMiss)<=count(azSuffix) ){ | |
| 741 | + while( rid==0 && (++nMiss)<=nSuffix ){ | |
| 692 | 742 | zName = P("name"); |
| 693 | 743 | if( isUV ){ |
| 694 | 744 | if( zName==0 ) zName = "index.wiki"; |
| 695 | 745 | i = 0; |
| 696 | 746 | }else{ |
| @@ -699,15 +749,15 @@ | ||
| 699 | 749 | zCheckin = mprintf("%.*s", i, zName); |
| 700 | 750 | if( fossil_strcmp(zCheckin,"ckout")==0 && g.localOpen==0 ){ |
| 701 | 751 | zCheckin = "tip"; |
| 702 | 752 | } |
| 703 | 753 | } |
| 704 | - if( nMiss==count(azSuffix) ){ | |
| 754 | + if( nMiss==nSuffix ){ | |
| 705 | 755 | zName = "404.md"; |
| 706 | 756 | zDfltTitle = "Not Found"; |
| 707 | 757 | }else if( zName[i]==0 ){ |
| 708 | - assert( nMiss>=0 && nMiss<count(azSuffix) ); | |
| 758 | + assert( nMiss>=0 && nMiss<nSuffix ); | |
| 709 | 759 | zName = azSuffix[nMiss]; |
| 710 | 760 | }else if( !isUV ){ |
| 711 | 761 | zName += i; |
| 712 | 762 | } |
| 713 | 763 | while( zName[0]=='/' ){ zName++; } |
| @@ -717,11 +767,11 @@ | ||
| 717 | 767 | zPathSuffix = mprintf("%s/%s", zCheckin, zName); |
| 718 | 768 | } |
| 719 | 769 | if( nMiss==0 ) zOrigName = zName; |
| 720 | 770 | if( !file_is_simple_pathname(zName, 1) ){ |
| 721 | 771 | if( sqlite3_strglob("*/", zName)==0 ){ |
| 722 | - assert( nMiss>=0 && nMiss<count(azSuffix) ); | |
| 772 | + assert( nMiss>=0 && nMiss<nSuffix ); | |
| 723 | 773 | zName = mprintf("%s%s", zName, azSuffix[nMiss]); |
| 724 | 774 | if( !file_is_simple_pathname(zName, 1) ){ |
| 725 | 775 | goto doc_not_found; |
| 726 | 776 | } |
| 727 | 777 | }else{ |
| @@ -775,11 +825,11 @@ | ||
| 775 | 825 | " FROM blob WHERE rid=%d", vid)); |
| 776 | 826 | Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event" |
| 777 | 827 | " WHERE objid=%d AND type='ci'", vid)); |
| 778 | 828 | } |
| 779 | 829 | document_render(&filebody, zMime, zDfltTitle, zName); |
| 780 | - if( nMiss>=count(azSuffix) ) cgi_set_status(404, "Not Found"); | |
| 830 | + if( nMiss>=nSuffix ) cgi_set_status(404, "Not Found"); | |
| 781 | 831 | db_end_transaction(0); |
| 782 | 832 | return; |
| 783 | 833 | |
| 784 | 834 | /* Jump here when unable to locate the document */ |
| 785 | 835 | doc_not_found: |
| 786 | 836 |
| --- src/doc.c | |
| +++ src/doc.c | |
| @@ -612,10 +612,64 @@ | |
| 612 | cgi_set_content_type(zMime); |
| 613 | cgi_set_content(pBody); |
| 614 | } |
| 615 | } |
| 616 | |
| 617 | |
| 618 | /* |
| 619 | ** WEBPAGE: uv |
| 620 | ** WEBPAGE: doc |
| 621 | ** URL: /uv/FILE |
| @@ -674,23 +728,19 @@ | |
| 674 | Blob filebody; /* Content of the documentation file */ |
| 675 | Blob title; /* Document title */ |
| 676 | int nMiss = (-1); /* Failed attempts to find the document */ |
| 677 | int isUV = g.zPath[0]=='u'; /* True for /uv. False for /doc */ |
| 678 | const char *zDfltTitle; |
| 679 | static const char *const azSuffix[] = { |
| 680 | "index.html", "index.wiki", "index.md" |
| 681 | #ifdef FOSSIL_ENABLE_TH1_DOCS |
| 682 | , "index.th1" |
| 683 | #endif |
| 684 | }; |
| 685 | |
| 686 | login_check_credentials(); |
| 687 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 688 | blob_init(&title, 0, 0); |
| 689 | zDfltTitle = isUV ? "" : "Documentation"; |
| 690 | db_begin_transaction(); |
| 691 | while( rid==0 && (++nMiss)<=count(azSuffix) ){ |
| 692 | zName = P("name"); |
| 693 | if( isUV ){ |
| 694 | if( zName==0 ) zName = "index.wiki"; |
| 695 | i = 0; |
| 696 | }else{ |
| @@ -699,15 +749,15 @@ | |
| 699 | zCheckin = mprintf("%.*s", i, zName); |
| 700 | if( fossil_strcmp(zCheckin,"ckout")==0 && g.localOpen==0 ){ |
| 701 | zCheckin = "tip"; |
| 702 | } |
| 703 | } |
| 704 | if( nMiss==count(azSuffix) ){ |
| 705 | zName = "404.md"; |
| 706 | zDfltTitle = "Not Found"; |
| 707 | }else if( zName[i]==0 ){ |
| 708 | assert( nMiss>=0 && nMiss<count(azSuffix) ); |
| 709 | zName = azSuffix[nMiss]; |
| 710 | }else if( !isUV ){ |
| 711 | zName += i; |
| 712 | } |
| 713 | while( zName[0]=='/' ){ zName++; } |
| @@ -717,11 +767,11 @@ | |
| 717 | zPathSuffix = mprintf("%s/%s", zCheckin, zName); |
| 718 | } |
| 719 | if( nMiss==0 ) zOrigName = zName; |
| 720 | if( !file_is_simple_pathname(zName, 1) ){ |
| 721 | if( sqlite3_strglob("*/", zName)==0 ){ |
| 722 | assert( nMiss>=0 && nMiss<count(azSuffix) ); |
| 723 | zName = mprintf("%s%s", zName, azSuffix[nMiss]); |
| 724 | if( !file_is_simple_pathname(zName, 1) ){ |
| 725 | goto doc_not_found; |
| 726 | } |
| 727 | }else{ |
| @@ -775,11 +825,11 @@ | |
| 775 | " FROM blob WHERE rid=%d", vid)); |
| 776 | Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event" |
| 777 | " WHERE objid=%d AND type='ci'", vid)); |
| 778 | } |
| 779 | document_render(&filebody, zMime, zDfltTitle, zName); |
| 780 | if( nMiss>=count(azSuffix) ) cgi_set_status(404, "Not Found"); |
| 781 | db_end_transaction(0); |
| 782 | return; |
| 783 | |
| 784 | /* Jump here when unable to locate the document */ |
| 785 | doc_not_found: |
| 786 |
| --- src/doc.c | |
| +++ src/doc.c | |
| @@ -612,10 +612,64 @@ | |
| 612 | cgi_set_content_type(zMime); |
| 613 | cgi_set_content(pBody); |
| 614 | } |
| 615 | } |
| 616 | |
| 617 | /* |
| 618 | ** Returns a list of possible index page names, in the order of their |
| 619 | ** priority, and writes the number of entries in the list to *pCount |
| 620 | ** (which must not be NULL). The returned memory is static but the |
| 621 | ** number of entries depends on compile-time options. |
| 622 | */ |
| 623 | const char * const * document_index_list( int * pCount ){ |
| 624 | static const char *const azSuffix[] = { |
| 625 | "index.html", "index.wiki", "index.md" |
| 626 | #ifdef FOSSIL_ENABLE_TH1_DOCS |
| 627 | , "index.th1" |
| 628 | #endif |
| 629 | }; |
| 630 | *pCount = (int)count(azSuffix); |
| 631 | return azSuffix; |
| 632 | } |
| 633 | |
| 634 | /* |
| 635 | ** If the given NUL-terminated directory name contains one of the |
| 636 | ** index files defined by document_index_list(), the path to that file |
| 637 | ** is returned in the form zDirName/FILE_NAME, else NULL is |
| 638 | ** returned. Ownership of the returned memory is transfered to the |
| 639 | ** caller. |
| 640 | ** |
| 641 | ** If pNResult is not NULL and non-NULL is returned, the length of the |
| 642 | ** returned string is written to *pNResult, otherwise pNResult is not |
| 643 | ** dereferenced. |
| 644 | */ |
| 645 | char * document_dir_has_index(const char * zDirName, int * pNResult){ |
| 646 | Blob bName = BLOB_INITIALIZER; /* File path */ |
| 647 | int i; /* Loop counter */ |
| 648 | int nIndex; /* Number of index entries. */ |
| 649 | const char * const * azIndex = document_index_list(&nIndex); |
| 650 | unsigned int nDirName = strlen(zDirName) |
| 651 | /* Cursor for the end of the initial |
| 652 | path+separator part of bName */; |
| 653 | blob_appendf( &bName, "%s%s", zDirName, |
| 654 | zDirName[nDirName-1]=='/' ? "" : "/" ); |
| 655 | nDirName = bName.nUsed; |
| 656 | for(i=0; i<nIndex; ++i){ |
| 657 | bName.nUsed = nDirName; |
| 658 | blob_append(&bName, azIndex[i], strlen(azIndex[i])); |
| 659 | if(file_isfile(bName.aData, ExtFILE)!=0){ |
| 660 | break; |
| 661 | } |
| 662 | } |
| 663 | if(i<nIndex){ |
| 664 | if(pNResult) *pNResult = (int)bName.nUsed; |
| 665 | return bName.aData; |
| 666 | }else{ |
| 667 | blob_reset(&bName); |
| 668 | return 0; |
| 669 | } |
| 670 | } |
| 671 | |
| 672 | /* |
| 673 | ** WEBPAGE: uv |
| 674 | ** WEBPAGE: doc |
| 675 | ** URL: /uv/FILE |
| @@ -674,23 +728,19 @@ | |
| 728 | Blob filebody; /* Content of the documentation file */ |
| 729 | Blob title; /* Document title */ |
| 730 | int nMiss = (-1); /* Failed attempts to find the document */ |
| 731 | int isUV = g.zPath[0]=='u'; /* True for /uv. False for /doc */ |
| 732 | const char *zDfltTitle; |
| 733 | int nSuffix; /* Number of entries in azSuffix */ |
| 734 | const char *const * azSuffix = document_index_list(&nSuffix); |
| 735 | |
| 736 | login_check_credentials(); |
| 737 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 738 | blob_init(&title, 0, 0); |
| 739 | zDfltTitle = isUV ? "" : "Documentation"; |
| 740 | db_begin_transaction(); |
| 741 | while( rid==0 && (++nMiss)<=nSuffix ){ |
| 742 | zName = P("name"); |
| 743 | if( isUV ){ |
| 744 | if( zName==0 ) zName = "index.wiki"; |
| 745 | i = 0; |
| 746 | }else{ |
| @@ -699,15 +749,15 @@ | |
| 749 | zCheckin = mprintf("%.*s", i, zName); |
| 750 | if( fossil_strcmp(zCheckin,"ckout")==0 && g.localOpen==0 ){ |
| 751 | zCheckin = "tip"; |
| 752 | } |
| 753 | } |
| 754 | if( nMiss==nSuffix ){ |
| 755 | zName = "404.md"; |
| 756 | zDfltTitle = "Not Found"; |
| 757 | }else if( zName[i]==0 ){ |
| 758 | assert( nMiss>=0 && nMiss<nSuffix ); |
| 759 | zName = azSuffix[nMiss]; |
| 760 | }else if( !isUV ){ |
| 761 | zName += i; |
| 762 | } |
| 763 | while( zName[0]=='/' ){ zName++; } |
| @@ -717,11 +767,11 @@ | |
| 767 | zPathSuffix = mprintf("%s/%s", zCheckin, zName); |
| 768 | } |
| 769 | if( nMiss==0 ) zOrigName = zName; |
| 770 | if( !file_is_simple_pathname(zName, 1) ){ |
| 771 | if( sqlite3_strglob("*/", zName)==0 ){ |
| 772 | assert( nMiss>=0 && nMiss<nSuffix ); |
| 773 | zName = mprintf("%s%s", zName, azSuffix[nMiss]); |
| 774 | if( !file_is_simple_pathname(zName, 1) ){ |
| 775 | goto doc_not_found; |
| 776 | } |
| 777 | }else{ |
| @@ -775,11 +825,11 @@ | |
| 825 | " FROM blob WHERE rid=%d", vid)); |
| 826 | Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event" |
| 827 | " WHERE objid=%d AND type='ci'", vid)); |
| 828 | } |
| 829 | document_render(&filebody, zMime, zDfltTitle, zName); |
| 830 | if( nMiss>=nSuffix ) cgi_set_status(404, "Not Found"); |
| 831 | db_end_transaction(0); |
| 832 | return; |
| 833 | |
| 834 | /* Jump here when unable to locate the document */ |
| 835 | doc_not_found: |
| 836 |
+8
| --- src/extcgi.c | ||
| +++ src/extcgi.c | ||
| @@ -188,10 +188,16 @@ | ||
| 188 | 188 | break; |
| 189 | 189 | } |
| 190 | 190 | } |
| 191 | 191 | } |
| 192 | 192 | } |
| 193 | + | |
| 194 | + if( nScript==0 && file_isdir(zPath,ExtFILE)==1 ){ | |
| 195 | + /* Check for an index.* file */ | |
| 196 | + zScript = document_dir_has_index(zPath, &nScript); | |
| 197 | + } | |
| 198 | + | |
| 193 | 199 | if( nScript==0 ){ |
| 194 | 200 | zFailReason = "path does not match any file or script"; |
| 195 | 201 | goto ext_not_found; |
| 196 | 202 | } |
| 197 | 203 | assert( nScript>=nRoot+1 ); |
| @@ -204,10 +210,11 @@ | ||
| 204 | 210 | if( zPath[nScript]!=0 ){ |
| 205 | 211 | zFailReason = "extra path elements after filename"; |
| 206 | 212 | goto ext_not_found; |
| 207 | 213 | } |
| 208 | 214 | blob_read_from_file(&reply, zScript, ExtFILE); |
| 215 | + fossil_free(zScript); | |
| 209 | 216 | document_render(&reply, zMime, zName, zName); |
| 210 | 217 | return; |
| 211 | 218 | } |
| 212 | 219 | |
| 213 | 220 | /* If we reach this point, that means we are dealing with an executable |
| @@ -238,10 +245,11 @@ | ||
| 238 | 245 | const char *zVal = P(azCgiEnv[i]); |
| 239 | 246 | if( zVal ) fossil_setenv(azCgiEnv[i], zVal); |
| 240 | 247 | } |
| 241 | 248 | fossil_setenv("HTTP_ACCEPT_ENCODING",""); |
| 242 | 249 | rc = popen2(zScript, &fdFromChild, &toChild, &pidChild, 1); |
| 250 | + fossil_free(zScript); | |
| 243 | 251 | if( rc ){ |
| 244 | 252 | zFailReason = "cannot exec CGI child process"; |
| 245 | 253 | goto ext_not_found; |
| 246 | 254 | } |
| 247 | 255 | fromChild = fdopen(fdFromChild, "rb"); |
| 248 | 256 |
| --- src/extcgi.c | |
| +++ src/extcgi.c | |
| @@ -188,10 +188,16 @@ | |
| 188 | break; |
| 189 | } |
| 190 | } |
| 191 | } |
| 192 | } |
| 193 | if( nScript==0 ){ |
| 194 | zFailReason = "path does not match any file or script"; |
| 195 | goto ext_not_found; |
| 196 | } |
| 197 | assert( nScript>=nRoot+1 ); |
| @@ -204,10 +210,11 @@ | |
| 204 | if( zPath[nScript]!=0 ){ |
| 205 | zFailReason = "extra path elements after filename"; |
| 206 | goto ext_not_found; |
| 207 | } |
| 208 | blob_read_from_file(&reply, zScript, ExtFILE); |
| 209 | document_render(&reply, zMime, zName, zName); |
| 210 | return; |
| 211 | } |
| 212 | |
| 213 | /* If we reach this point, that means we are dealing with an executable |
| @@ -238,10 +245,11 @@ | |
| 238 | const char *zVal = P(azCgiEnv[i]); |
| 239 | if( zVal ) fossil_setenv(azCgiEnv[i], zVal); |
| 240 | } |
| 241 | fossil_setenv("HTTP_ACCEPT_ENCODING",""); |
| 242 | rc = popen2(zScript, &fdFromChild, &toChild, &pidChild, 1); |
| 243 | if( rc ){ |
| 244 | zFailReason = "cannot exec CGI child process"; |
| 245 | goto ext_not_found; |
| 246 | } |
| 247 | fromChild = fdopen(fdFromChild, "rb"); |
| 248 |
| --- src/extcgi.c | |
| +++ src/extcgi.c | |
| @@ -188,10 +188,16 @@ | |
| 188 | break; |
| 189 | } |
| 190 | } |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | if( nScript==0 && file_isdir(zPath,ExtFILE)==1 ){ |
| 195 | /* Check for an index.* file */ |
| 196 | zScript = document_dir_has_index(zPath, &nScript); |
| 197 | } |
| 198 | |
| 199 | if( nScript==0 ){ |
| 200 | zFailReason = "path does not match any file or script"; |
| 201 | goto ext_not_found; |
| 202 | } |
| 203 | assert( nScript>=nRoot+1 ); |
| @@ -204,10 +210,11 @@ | |
| 210 | if( zPath[nScript]!=0 ){ |
| 211 | zFailReason = "extra path elements after filename"; |
| 212 | goto ext_not_found; |
| 213 | } |
| 214 | blob_read_from_file(&reply, zScript, ExtFILE); |
| 215 | fossil_free(zScript); |
| 216 | document_render(&reply, zMime, zName, zName); |
| 217 | return; |
| 218 | } |
| 219 | |
| 220 | /* If we reach this point, that means we are dealing with an executable |
| @@ -238,10 +245,11 @@ | |
| 245 | const char *zVal = P(azCgiEnv[i]); |
| 246 | if( zVal ) fossil_setenv(azCgiEnv[i], zVal); |
| 247 | } |
| 248 | fossil_setenv("HTTP_ACCEPT_ENCODING",""); |
| 249 | rc = popen2(zScript, &fdFromChild, &toChild, &pidChild, 1); |
| 250 | fossil_free(zScript); |
| 251 | if( rc ){ |
| 252 | zFailReason = "cannot exec CGI child process"; |
| 253 | goto ext_not_found; |
| 254 | } |
| 255 | fromChild = fdopen(fdFromChild, "rb"); |
| 256 |