@@ -790,8 +790,195 @@
@ %h(blob_str(&content))
@ </pre></blockquote>
blob_reset(&content);
style_footer();
+}
+
+/*
+** Guess the mime-type of a document based on its name.
+*/
+const char *mimetype_from_name(const char *zName){
+ const char *z;
+ int i;
+ char zSuffix[20];
+ static const struct {
+ const char *zSuffix;
+ const char *zMimetype;
+ } aMime[] = {
+ { "html", "text/html" },
+ { "htm", "text/html" },
+ { "wiki", "application/x-fossil-wiki" },
+ { "txt", "text/plain" },
+ { "jpg", "image/jpeg" },
+ { "jpeg", "image/jpeg" },
+ { "gif", "image/gif" },
+ { "png", "image/png" },
+ { "css", "text/css" },
+ };
+
+ z = zName;
+ for(i=0; zName[i]; i++){
+ if( zName[i]=='.' ) z = &zName[i+1];
+ }
+ i = strlen(z);
+ if( i<sizeof(zSuffix)-1 ){
+ strcpy(zSuffix, z);
+ for(i=0; zSuffix[i]; i++) zSuffix[i] = tolower(zSuffix[i]);
+ for(i=0; i<sizeof(aMime)/sizeof(aMime[0]); i++){
+ if( strcmp(zSuffix, aMime[i].zSuffix)==0 ){
+ return aMime[i].zMimetype;
+ }
+ }
+ }
+ return "application/x-fossil-artifact";
+}
+
+/*
+** WEBPAGE: doc
+** URL: /doc?name=BASELINE/PATH
+**
+** BASELINE can be either a baseline uuid prefix or magic words "tip"
+** to me the most recently checked in baseline or "ckout" to mean the
+** content of the local checkout, if any. PATH is the relative pathname
+** of some file. This method returns the file content.
+**
+** If PATH matches the patterns *.wiki or *.txt then formatting content
+** is added before returning the file. For all other names, the content
+** is returned straight without any interpretation or processing.
+*/
+void doc_page(void){
+ const char *zName; /* Argument to the /doc page */
+ const char *zMime; /* Document MIME type */
+ int vid = 0; /* Artifact of baseline */
+ int rid = 0; /* Artifact of file */
+ int i; /* Loop counter */
+ Blob filebody; /* Content of the documentation file */
+ char zBaseline[UUID_SIZE+1]; /* Baseline UUID */
+
+ login_check_credentials();
+ if( !g.okRead ){ login_needed(); return; }
+ zName = PD("name", "tip/index.wiki");
+ for(i=0; zName[i] && zName[i]!='/'; i++){}
+ if( zName[i]==0 || i>UUID_SIZE ){
+ goto doc_not_found;
+ }
+ memcpy(zBaseline, zName, i);
+ zBaseline[i] = 0;
+ zName += i;
+ while( zName[0]=='/' ){ zName++; }
+ if( !file_is_simple_pathname(zName) ){
+ goto doc_not_found;
+ }
+ if( strcmp(zBaseline,"ckout")==0 ){
+ /* Read from the local checkout */
+ char *zFullpath;
+ db_must_be_within_tree();
+ zFullpath = mprintf("%s/%s", g.zLocalRoot, zName);
+ if( !file_isfile(zFullpath) ){
+ goto doc_not_found;
+ }
+ if( blob_read_from_file(&filebody, zFullpath)<0 ){
+ goto doc_not_found;
+ }
+ }else{
+ db_begin_transaction();
+ if( strcmp(zBaseline,"tip")==0 ){
+ vid = db_int(0, "SELECT objid FROM event WHERE type='ci'"
+ " ORDER BY mtime DESC LIMIT 1");
+ }else{
+ vid = name_to_rid(zBaseline);
+ }
+
+ /* Create the baseline cache if it does not already exist */
+ db_multi_exec(
+ "CREATE TABLE IF NOT EXISTS vcache(\n"
+ " vid INTEGER, -- baseline ID\n"
+ " fname TEXT, -- filename\n"
+ " rid INTEGER, -- artifact ID\n"
+ " UNIQUE(vid,fname,rid)\n"
+ ")"
+ );
+
+ /* Check to see if the documentation file artifact ID is contained
+ ** in the baseline cache */
+ rid = db_int(0, "SELECT rid FROM vcache"
+ " WHERE vid=%d AND fname=%Q", vid, zName);
+ if( rid==0 && db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){
+ goto doc_not_found;
+ }
+
+ if( rid==0 ){
+ Stmt s;
+ Blob baseline;
+ Manifest m;
+
+ /* Add the vid baseline to the cache */
+ if( db_int(0, "SELECT count(*) FROM vcache")>10000 ){
+ db_multi_exec("DELETE FROM vcache");
+ }
+ if( content_get(vid, &baseline)==0 ){
+ goto doc_not_found;
+ }
+ if( manifest_parse(&m, &baseline)==0 || m.type!=CFTYPE_MANIFEST ){
+ goto doc_not_found;
+ }
+ db_prepare(&s,
+ "INSERT INTO vcache(vid,fname,rid)"
+ " SELECT %d, :fname, rid FROM blob"
+ " WHERE uuid=:uuid",
+ vid
+ );
+ for(i=0; i<m.nFile; i++){
+ db_bind_text(&s, ":fname", m.aFile[i].zName);
+ db_bind_text(&s, ":uuid", m.aFile[i].zUuid);
+ db_step(&s);
+ db_reset(&s);
+ }
+ db_finalize(&s);
+ manifest_clear(&m);
+
+ /* Try again to find the file */
+ rid = db_int(0, "SELECT rid FROM vcache"
+ " WHERE vid=%d AND fname=%Q", vid, zName);
+ }
+ if( rid==0 ){
+ goto doc_not_found;
+ }
+
+ /* Get the file content */
+ if( content_get(rid, &filebody)==0 ){
+ goto doc_not_found;
+ }
+ db_end_transaction(0);
+ }
+
+ /* The file is now contained in the filebody blob. Deliver the
+ ** file to the user
+ */
+ zMime = mimetype_from_name(zName);
+ if( strcmp(zMime, "application/x-fossil-wiki")==0 ){
+ style_header("Documentation");
+ wiki_convert(&filebody, 0, 0);
+ style_footer();
+ }else if( strcmp(zMime, "text/plain")==0 ){
+ style_header("Documentation");
+ @ <blockquote><pre>
+ @ %h(blob_str(&filebody))
+ @ </pre></blockquote>
+ style_footer();
+ }else{
+ cgi_set_content_type(zMime);
+ cgi_set_content(&filebody);
+ }
+ return;
+
+doc_not_found:
+ /* Jump here when unable to locate the document */
+ db_end_transaction(0);
+ style_header("Document Not Found");
+ @ <p>No such document: %h(PD("name","tip/index.wiki"))</p>
+ style_footer();
+ return;
}
/*
** WEBPAGE: info