| | @@ -790,10 +790,197 @@ |
| 790 | 790 | @ %h(blob_str(&content)) |
| 791 | 791 | @ </pre></blockquote> |
| 792 | 792 | blob_reset(&content); |
| 793 | 793 | style_footer(); |
| 794 | 794 | } |
| 795 | + |
| 796 | +/* |
| 797 | +** Guess the mime-type of a document based on its name. |
| 798 | +*/ |
| 799 | +const char *mimetype_from_name(const char *zName){ |
| 800 | + const char *z; |
| 801 | + int i; |
| 802 | + char zSuffix[20]; |
| 803 | + static const struct { |
| 804 | + const char *zSuffix; |
| 805 | + const char *zMimetype; |
| 806 | + } aMime[] = { |
| 807 | + { "html", "text/html" }, |
| 808 | + { "htm", "text/html" }, |
| 809 | + { "wiki", "application/x-fossil-wiki" }, |
| 810 | + { "txt", "text/plain" }, |
| 811 | + { "jpg", "image/jpeg" }, |
| 812 | + { "jpeg", "image/jpeg" }, |
| 813 | + { "gif", "image/gif" }, |
| 814 | + { "png", "image/png" }, |
| 815 | + { "css", "text/css" }, |
| 816 | + }; |
| 817 | + |
| 818 | + z = zName; |
| 819 | + for(i=0; zName[i]; i++){ |
| 820 | + if( zName[i]=='.' ) z = &zName[i+1]; |
| 821 | + } |
| 822 | + i = strlen(z); |
| 823 | + if( i<sizeof(zSuffix)-1 ){ |
| 824 | + strcpy(zSuffix, z); |
| 825 | + for(i=0; zSuffix[i]; i++) zSuffix[i] = tolower(zSuffix[i]); |
| 826 | + for(i=0; i<sizeof(aMime)/sizeof(aMime[0]); i++){ |
| 827 | + if( strcmp(zSuffix, aMime[i].zSuffix)==0 ){ |
| 828 | + return aMime[i].zMimetype; |
| 829 | + } |
| 830 | + } |
| 831 | + } |
| 832 | + return "application/x-fossil-artifact"; |
| 833 | +} |
| 834 | + |
| 835 | +/* |
| 836 | +** WEBPAGE: doc |
| 837 | +** URL: /doc?name=BASELINE/PATH |
| 838 | +** |
| 839 | +** BASELINE can be either a baseline uuid prefix or magic words "tip" |
| 840 | +** to me the most recently checked in baseline or "ckout" to mean the |
| 841 | +** content of the local checkout, if any. PATH is the relative pathname |
| 842 | +** of some file. This method returns the file content. |
| 843 | +** |
| 844 | +** If PATH matches the patterns *.wiki or *.txt then formatting content |
| 845 | +** is added before returning the file. For all other names, the content |
| 846 | +** is returned straight without any interpretation or processing. |
| 847 | +*/ |
| 848 | +void doc_page(void){ |
| 849 | + const char *zName; /* Argument to the /doc page */ |
| 850 | + const char *zMime; /* Document MIME type */ |
| 851 | + int vid = 0; /* Artifact of baseline */ |
| 852 | + int rid = 0; /* Artifact of file */ |
| 853 | + int i; /* Loop counter */ |
| 854 | + Blob filebody; /* Content of the documentation file */ |
| 855 | + char zBaseline[UUID_SIZE+1]; /* Baseline UUID */ |
| 856 | + |
| 857 | + login_check_credentials(); |
| 858 | + if( !g.okRead ){ login_needed(); return; } |
| 859 | + zName = PD("name", "tip/index.wiki"); |
| 860 | + for(i=0; zName[i] && zName[i]!='/'; i++){} |
| 861 | + if( zName[i]==0 || i>UUID_SIZE ){ |
| 862 | + goto doc_not_found; |
| 863 | + } |
| 864 | + memcpy(zBaseline, zName, i); |
| 865 | + zBaseline[i] = 0; |
| 866 | + zName += i; |
| 867 | + while( zName[0]=='/' ){ zName++; } |
| 868 | + if( !file_is_simple_pathname(zName) ){ |
| 869 | + goto doc_not_found; |
| 870 | + } |
| 871 | + if( strcmp(zBaseline,"ckout")==0 ){ |
| 872 | + /* Read from the local checkout */ |
| 873 | + char *zFullpath; |
| 874 | + db_must_be_within_tree(); |
| 875 | + zFullpath = mprintf("%s/%s", g.zLocalRoot, zName); |
| 876 | + if( !file_isfile(zFullpath) ){ |
| 877 | + goto doc_not_found; |
| 878 | + } |
| 879 | + if( blob_read_from_file(&filebody, zFullpath)<0 ){ |
| 880 | + goto doc_not_found; |
| 881 | + } |
| 882 | + }else{ |
| 883 | + db_begin_transaction(); |
| 884 | + if( strcmp(zBaseline,"tip")==0 ){ |
| 885 | + vid = db_int(0, "SELECT objid FROM event WHERE type='ci'" |
| 886 | + " ORDER BY mtime DESC LIMIT 1"); |
| 887 | + }else{ |
| 888 | + vid = name_to_rid(zBaseline); |
| 889 | + } |
| 890 | + |
| 891 | + /* Create the baseline cache if it does not already exist */ |
| 892 | + db_multi_exec( |
| 893 | + "CREATE TABLE IF NOT EXISTS vcache(\n" |
| 894 | + " vid INTEGER, -- baseline ID\n" |
| 895 | + " fname TEXT, -- filename\n" |
| 896 | + " rid INTEGER, -- artifact ID\n" |
| 897 | + " UNIQUE(vid,fname,rid)\n" |
| 898 | + ")" |
| 899 | + ); |
| 900 | + |
| 901 | + /* Check to see if the documentation file artifact ID is contained |
| 902 | + ** in the baseline cache */ |
| 903 | + rid = db_int(0, "SELECT rid FROM vcache" |
| 904 | + " WHERE vid=%d AND fname=%Q", vid, zName); |
| 905 | + if( rid==0 && db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){ |
| 906 | + goto doc_not_found; |
| 907 | + } |
| 908 | + |
| 909 | + if( rid==0 ){ |
| 910 | + Stmt s; |
| 911 | + Blob baseline; |
| 912 | + Manifest m; |
| 913 | + |
| 914 | + /* Add the vid baseline to the cache */ |
| 915 | + if( db_int(0, "SELECT count(*) FROM vcache")>10000 ){ |
| 916 | + db_multi_exec("DELETE FROM vcache"); |
| 917 | + } |
| 918 | + if( content_get(vid, &baseline)==0 ){ |
| 919 | + goto doc_not_found; |
| 920 | + } |
| 921 | + if( manifest_parse(&m, &baseline)==0 || m.type!=CFTYPE_MANIFEST ){ |
| 922 | + goto doc_not_found; |
| 923 | + } |
| 924 | + db_prepare(&s, |
| 925 | + "INSERT INTO vcache(vid,fname,rid)" |
| 926 | + " SELECT %d, :fname, rid FROM blob" |
| 927 | + " WHERE uuid=:uuid", |
| 928 | + vid |
| 929 | + ); |
| 930 | + for(i=0; i<m.nFile; i++){ |
| 931 | + db_bind_text(&s, ":fname", m.aFile[i].zName); |
| 932 | + db_bind_text(&s, ":uuid", m.aFile[i].zUuid); |
| 933 | + db_step(&s); |
| 934 | + db_reset(&s); |
| 935 | + } |
| 936 | + db_finalize(&s); |
| 937 | + manifest_clear(&m); |
| 938 | + |
| 939 | + /* Try again to find the file */ |
| 940 | + rid = db_int(0, "SELECT rid FROM vcache" |
| 941 | + " WHERE vid=%d AND fname=%Q", vid, zName); |
| 942 | + } |
| 943 | + if( rid==0 ){ |
| 944 | + goto doc_not_found; |
| 945 | + } |
| 946 | + |
| 947 | + /* Get the file content */ |
| 948 | + if( content_get(rid, &filebody)==0 ){ |
| 949 | + goto doc_not_found; |
| 950 | + } |
| 951 | + db_end_transaction(0); |
| 952 | + } |
| 953 | + |
| 954 | + /* The file is now contained in the filebody blob. Deliver the |
| 955 | + ** file to the user |
| 956 | + */ |
| 957 | + zMime = mimetype_from_name(zName); |
| 958 | + if( strcmp(zMime, "application/x-fossil-wiki")==0 ){ |
| 959 | + style_header("Documentation"); |
| 960 | + wiki_convert(&filebody, 0, 0); |
| 961 | + style_footer(); |
| 962 | + }else if( strcmp(zMime, "text/plain")==0 ){ |
| 963 | + style_header("Documentation"); |
| 964 | + @ <blockquote><pre> |
| 965 | + @ %h(blob_str(&filebody)) |
| 966 | + @ </pre></blockquote> |
| 967 | + style_footer(); |
| 968 | + }else{ |
| 969 | + cgi_set_content_type(zMime); |
| 970 | + cgi_set_content(&filebody); |
| 971 | + } |
| 972 | + return; |
| 973 | + |
| 974 | +doc_not_found: |
| 975 | + /* Jump here when unable to locate the document */ |
| 976 | + db_end_transaction(0); |
| 977 | + style_header("Document Not Found"); |
| 978 | + @ <p>No such document: %h(PD("name","tip/index.wiki"))</p> |
| 979 | + style_footer(); |
| 980 | + return; |
| 981 | +} |
| 795 | 982 | |
| 796 | 983 | /* |
| 797 | 984 | ** WEBPAGE: info |
| 798 | 985 | ** URL: info/UUID |
| 799 | 986 | ** |
| 800 | 987 | |