5cf1206dfa 2008-05-15 drh: /* 5cf1206dfa 2008-05-15 drh: ** Copyright (c) 2007 D. Richard Hipp 5cf1206dfa 2008-05-15 drh: ** 5cf1206dfa 2008-05-15 drh: ** This program is free software; you can redistribute it and/or 5cf1206dfa 2008-05-15 drh: ** modify it under the terms of the GNU General Public 5cf1206dfa 2008-05-15 drh: ** License version 2 as published by the Free Software Foundation. 5cf1206dfa 2008-05-15 drh: ** 5cf1206dfa 2008-05-15 drh: ** This program is distributed in the hope that it will be useful, 5cf1206dfa 2008-05-15 drh: ** but WITHOUT ANY WARRANTY; without even the implied warranty of 5cf1206dfa 2008-05-15 drh: ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 5cf1206dfa 2008-05-15 drh: ** General Public License for more details. 5cf1206dfa 2008-05-15 drh: ** 5cf1206dfa 2008-05-15 drh: ** You should have received a copy of the GNU General Public 5cf1206dfa 2008-05-15 drh: ** License along with this library; if not, write to the 5cf1206dfa 2008-05-15 drh: ** Free Software Foundation, Inc., 59 Temple Place - Suite 330, 5cf1206dfa 2008-05-15 drh: ** Boston, MA 02111-1307, USA. 5cf1206dfa 2008-05-15 drh: ** 5cf1206dfa 2008-05-15 drh: ** Author contact information: 5cf1206dfa 2008-05-15 drh: ** drh@hwaci.com 5cf1206dfa 2008-05-15 drh: ** http://www.hwaci.com/drh/ 5cf1206dfa 2008-05-15 drh: ** 5cf1206dfa 2008-05-15 drh: ******************************************************************************* 5cf1206dfa 2008-05-15 drh: ** 5cf1206dfa 2008-05-15 drh: ** This file contains code to implement the "/doc" web page and related 5cf1206dfa 2008-05-15 drh: ** pages. 5cf1206dfa 2008-05-15 drh: */ 5cf1206dfa 2008-05-15 drh: #include "config.h" 5cf1206dfa 2008-05-15 drh: #include "doc.h" 5cf1206dfa 2008-05-15 drh: #include <assert.h> 5cf1206dfa 2008-05-15 drh: 5cf1206dfa 2008-05-15 drh: /* 5cf1206dfa 2008-05-15 drh: ** Guess the mime-type of a document based on its name. 5cf1206dfa 2008-05-15 drh: */ 5cf1206dfa 2008-05-15 drh: const char *mimetype_from_name(const char *zName){ 5cf1206dfa 2008-05-15 drh: const char *z; 5cf1206dfa 2008-05-15 drh: int i; 5cf1206dfa 2008-05-15 drh: char zSuffix[20]; 5cf1206dfa 2008-05-15 drh: static const struct { 5cf1206dfa 2008-05-15 drh: const char *zSuffix; 5cf1206dfa 2008-05-15 drh: const char *zMimetype; 5cf1206dfa 2008-05-15 drh: } aMime[] = { 5cf1206dfa 2008-05-15 drh: { "html", "text/html" }, 5cf1206dfa 2008-05-15 drh: { "htm", "text/html" }, 5cf1206dfa 2008-05-15 drh: { "wiki", "application/x-fossil-wiki" }, 5cf1206dfa 2008-05-15 drh: { "txt", "text/plain" }, 5cf1206dfa 2008-05-15 drh: { "jpg", "image/jpeg" }, 5cf1206dfa 2008-05-15 drh: { "jpeg", "image/jpeg" }, 5cf1206dfa 2008-05-15 drh: { "gif", "image/gif" }, 5cf1206dfa 2008-05-15 drh: { "png", "image/png" }, 5cf1206dfa 2008-05-15 drh: { "css", "text/css" }, 5cf1206dfa 2008-05-15 drh: }; 5cf1206dfa 2008-05-15 drh: 5cf1206dfa 2008-05-15 drh: z = zName; 5cf1206dfa 2008-05-15 drh: for(i=0; zName[i]; i++){ 5cf1206dfa 2008-05-15 drh: if( zName[i]=='.' ) z = &zName[i+1]; 5cf1206dfa 2008-05-15 drh: } 5cf1206dfa 2008-05-15 drh: i = strlen(z); 5cf1206dfa 2008-05-15 drh: if( i<sizeof(zSuffix)-1 ){ 5cf1206dfa 2008-05-15 drh: strcpy(zSuffix, z); 5cf1206dfa 2008-05-15 drh: for(i=0; zSuffix[i]; i++) zSuffix[i] = tolower(zSuffix[i]); 5cf1206dfa 2008-05-15 drh: for(i=0; i<sizeof(aMime)/sizeof(aMime[0]); i++){ 5cf1206dfa 2008-05-15 drh: if( strcmp(zSuffix, aMime[i].zSuffix)==0 ){ 5cf1206dfa 2008-05-15 drh: return aMime[i].zMimetype; 5cf1206dfa 2008-05-15 drh: } 5cf1206dfa 2008-05-15 drh: } 5cf1206dfa 2008-05-15 drh: } 5cf1206dfa 2008-05-15 drh: return "application/x-fossil-artifact"; 5cf1206dfa 2008-05-15 drh: } 5cf1206dfa 2008-05-15 drh: 5cf1206dfa 2008-05-15 drh: /* 5cf1206dfa 2008-05-15 drh: ** WEBPAGE: doc 5cf1206dfa 2008-05-15 drh: ** URL: /doc?name=BASELINE/PATH 5cf1206dfa 2008-05-15 drh: ** 5cf1206dfa 2008-05-15 drh: ** BASELINE can be either a baseline uuid prefix or magic words "tip" 5cf1206dfa 2008-05-15 drh: ** to me the most recently checked in baseline or "ckout" to mean the 5cf1206dfa 2008-05-15 drh: ** content of the local checkout, if any. PATH is the relative pathname 5cf1206dfa 2008-05-15 drh: ** of some file. This method returns the file content. 5cf1206dfa 2008-05-15 drh: ** 5cf1206dfa 2008-05-15 drh: ** If PATH matches the patterns *.wiki or *.txt then formatting content 5cf1206dfa 2008-05-15 drh: ** is added before returning the file. For all other names, the content 5cf1206dfa 2008-05-15 drh: ** is returned straight without any interpretation or processing. 5cf1206dfa 2008-05-15 drh: */ 5cf1206dfa 2008-05-15 drh: void doc_page(void){ 5cf1206dfa 2008-05-15 drh: const char *zName; /* Argument to the /doc page */ 5cf1206dfa 2008-05-15 drh: const char *zMime; /* Document MIME type */ 5cf1206dfa 2008-05-15 drh: int vid = 0; /* Artifact of baseline */ 5cf1206dfa 2008-05-15 drh: int rid = 0; /* Artifact of file */ 5cf1206dfa 2008-05-15 drh: int i; /* Loop counter */ 5cf1206dfa 2008-05-15 drh: Blob filebody; /* Content of the documentation file */ 5cf1206dfa 2008-05-15 drh: char zBaseline[UUID_SIZE+1]; /* Baseline UUID */ 5cf1206dfa 2008-05-15 drh: 5cf1206dfa 2008-05-15 drh: login_check_credentials(); 5cf1206dfa 2008-05-15 drh: if( !g.okRead ){ login_needed(); return; } 5cf1206dfa 2008-05-15 drh: zName = PD("name", "tip/index.wiki"); 5cf1206dfa 2008-05-15 drh: for(i=0; zName[i] && zName[i]!='/'; i++){} 5cf1206dfa 2008-05-15 drh: if( zName[i]==0 || i>UUID_SIZE ){ 5cf1206dfa 2008-05-15 drh: goto doc_not_found; 5cf1206dfa 2008-05-15 drh: } 5cf1206dfa 2008-05-15 drh: memcpy(zBaseline, zName, i); 5cf1206dfa 2008-05-15 drh: zBaseline[i] = 0; 5cf1206dfa 2008-05-15 drh: zName += i; 5cf1206dfa 2008-05-15 drh: while( zName[0]=='/' ){ zName++; } 5cf1206dfa 2008-05-15 drh: if( !file_is_simple_pathname(zName) ){ 5cf1206dfa 2008-05-15 drh: goto doc_not_found; 5cf1206dfa 2008-05-15 drh: } 5cf1206dfa 2008-05-15 drh: if( strcmp(zBaseline,"ckout")==0 ){ 5cf1206dfa 2008-05-15 drh: /* Read from the local checkout */ 5cf1206dfa 2008-05-15 drh: char *zFullpath; 5cf1206dfa 2008-05-15 drh: db_must_be_within_tree(); 5cf1206dfa 2008-05-15 drh: zFullpath = mprintf("%s/%s", g.zLocalRoot, zName); 5cf1206dfa 2008-05-15 drh: if( !file_isfile(zFullpath) ){ 5cf1206dfa 2008-05-15 drh: goto doc_not_found; 5cf1206dfa 2008-05-15 drh: } 5cf1206dfa 2008-05-15 drh: if( blob_read_from_file(&filebody, zFullpath)<0 ){ 5cf1206dfa 2008-05-15 drh: goto doc_not_found; 5cf1206dfa 2008-05-15 drh: } 5cf1206dfa 2008-05-15 drh: }else{ 5cf1206dfa 2008-05-15 drh: db_begin_transaction(); 5cf1206dfa 2008-05-15 drh: if( strcmp(zBaseline,"tip")==0 ){ 5cf1206dfa 2008-05-15 drh: vid = db_int(0, "SELECT objid FROM event WHERE type='ci'" 5cf1206dfa 2008-05-15 drh: " ORDER BY mtime DESC LIMIT 1"); 5cf1206dfa 2008-05-15 drh: }else{ 5cf1206dfa 2008-05-15 drh: vid = name_to_rid(zBaseline); 5cf1206dfa 2008-05-15 drh: } 5cf1206dfa 2008-05-15 drh: 5cf1206dfa 2008-05-15 drh: /* Create the baseline cache if it does not already exist */ 5cf1206dfa 2008-05-15 drh: db_multi_exec( 5cf1206dfa 2008-05-15 drh: "CREATE TABLE IF NOT EXISTS vcache(\n" 5cf1206dfa 2008-05-15 drh: " vid INTEGER, -- baseline ID\n" 5cf1206dfa 2008-05-15 drh: " fname TEXT, -- filename\n" 5cf1206dfa 2008-05-15 drh: " rid INTEGER, -- artifact ID\n" 5cf1206dfa 2008-05-15 drh: " UNIQUE(vid,fname,rid)\n" 5cf1206dfa 2008-05-15 drh: ")" 5cf1206dfa 2008-05-15 drh: ); 5cf1206dfa 2008-05-15 drh: 5cf1206dfa 2008-05-15 drh: /* Check to see if the documentation file artifact ID is contained 5cf1206dfa 2008-05-15 drh: ** in the baseline cache */ 5cf1206dfa 2008-05-15 drh: rid = db_int(0, "SELECT rid FROM vcache" 5cf1206dfa 2008-05-15 drh: " WHERE vid=%d AND fname=%Q", vid, zName); 5cf1206dfa 2008-05-15 drh: if( rid==0 && db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){ 5cf1206dfa 2008-05-15 drh: goto doc_not_found; 5cf1206dfa 2008-05-15 drh: } 5cf1206dfa 2008-05-15 drh: 5cf1206dfa 2008-05-15 drh: if( rid==0 ){ 5cf1206dfa 2008-05-15 drh: Stmt s; 5cf1206dfa 2008-05-15 drh: Blob baseline; 5cf1206dfa 2008-05-15 drh: Manifest m; 5cf1206dfa 2008-05-15 drh: 5cf1206dfa 2008-05-15 drh: /* Add the vid baseline to the cache */ 5cf1206dfa 2008-05-15 drh: if( db_int(0, "SELECT count(*) FROM vcache")>10000 ){ 5cf1206dfa 2008-05-15 drh: db_multi_exec("DELETE FROM vcache"); 5cf1206dfa 2008-05-15 drh: } 5cf1206dfa 2008-05-15 drh: if( content_get(vid, &baseline)==0 ){ 5cf1206dfa 2008-05-15 drh: goto doc_not_found; 5cf1206dfa 2008-05-15 drh: } 5cf1206dfa 2008-05-15 drh: if( manifest_parse(&m, &baseline)==0 || m.type!=CFTYPE_MANIFEST ){ 5cf1206dfa 2008-05-15 drh: goto doc_not_found; 5cf1206dfa 2008-05-15 drh: } 5cf1206dfa 2008-05-15 drh: db_prepare(&s, 5cf1206dfa 2008-05-15 drh: "INSERT INTO vcache(vid,fname,rid)" 5cf1206dfa 2008-05-15 drh: " SELECT %d, :fname, rid FROM blob" 5cf1206dfa 2008-05-15 drh: " WHERE uuid=:uuid", 5cf1206dfa 2008-05-15 drh: vid 5cf1206dfa 2008-05-15 drh: ); 5cf1206dfa 2008-05-15 drh: for(i=0; i<m.nFile; i++){ 5cf1206dfa 2008-05-15 drh: db_bind_text(&s, ":fname", m.aFile[i].zName); 5cf1206dfa 2008-05-15 drh: db_bind_text(&s, ":uuid", m.aFile[i].zUuid); 5cf1206dfa 2008-05-15 drh: db_step(&s); 5cf1206dfa 2008-05-15 drh: db_reset(&s); 5cf1206dfa 2008-05-15 drh: } 5cf1206dfa 2008-05-15 drh: db_finalize(&s); 5cf1206dfa 2008-05-15 drh: manifest_clear(&m); 5cf1206dfa 2008-05-15 drh: 5cf1206dfa 2008-05-15 drh: /* Try again to find the file */ 5cf1206dfa 2008-05-15 drh: rid = db_int(0, "SELECT rid FROM vcache" 5cf1206dfa 2008-05-15 drh: " WHERE vid=%d AND fname=%Q", vid, zName); 5cf1206dfa 2008-05-15 drh: } 5cf1206dfa 2008-05-15 drh: if( rid==0 ){ 5cf1206dfa 2008-05-15 drh: goto doc_not_found; 5cf1206dfa 2008-05-15 drh: } 5cf1206dfa 2008-05-15 drh: 5cf1206dfa 2008-05-15 drh: /* Get the file content */ 5cf1206dfa 2008-05-15 drh: if( content_get(rid, &filebody)==0 ){ 5cf1206dfa 2008-05-15 drh: goto doc_not_found; 5cf1206dfa 2008-05-15 drh: } 5cf1206dfa 2008-05-15 drh: db_end_transaction(0); 5cf1206dfa 2008-05-15 drh: } 5cf1206dfa 2008-05-15 drh: 5cf1206dfa 2008-05-15 drh: /* The file is now contained in the filebody blob. Deliver the 5cf1206dfa 2008-05-15 drh: ** file to the user 5cf1206dfa 2008-05-15 drh: */ 5cf1206dfa 2008-05-15 drh: zMime = mimetype_from_name(zName); 5cf1206dfa 2008-05-15 drh: if( strcmp(zMime, "application/x-fossil-wiki")==0 ){ 5cf1206dfa 2008-05-15 drh: style_header("Documentation"); 5cf1206dfa 2008-05-15 drh: wiki_convert(&filebody, 0, 0); 5cf1206dfa 2008-05-15 drh: style_footer(); 5cf1206dfa 2008-05-15 drh: }else if( strcmp(zMime, "text/plain")==0 ){ 5cf1206dfa 2008-05-15 drh: style_header("Documentation"); 5cf1206dfa 2008-05-15 drh: @ <blockquote><pre> 5cf1206dfa 2008-05-15 drh: @ %h(blob_str(&filebody)) 5cf1206dfa 2008-05-15 drh: @ </pre></blockquote> 5cf1206dfa 2008-05-15 drh: style_footer(); 5cf1206dfa 2008-05-15 drh: }else{ 5cf1206dfa 2008-05-15 drh: cgi_set_content_type(zMime); 5cf1206dfa 2008-05-15 drh: cgi_set_content(&filebody); 5cf1206dfa 2008-05-15 drh: } 5cf1206dfa 2008-05-15 drh: return; 5cf1206dfa 2008-05-15 drh: 5cf1206dfa 2008-05-15 drh: doc_not_found: 5cf1206dfa 2008-05-15 drh: /* Jump here when unable to locate the document */ 5cf1206dfa 2008-05-15 drh: db_end_transaction(0); 5cf1206dfa 2008-05-15 drh: style_header("Document Not Found"); 5cf1206dfa 2008-05-15 drh: @ <p>No such document: %h(PD("name","tip/index.wiki"))</p> 5cf1206dfa 2008-05-15 drh: style_footer(); 5cf1206dfa 2008-05-15 drh: return; 5cf1206dfa 2008-05-15 drh: }