1 /*
2 ** Copyright (c) 2007 D. Richard Hipp
3 **
4 ** This program is free software; you can redistribute it and/or
5 ** modify it under the terms of the Simplified BSD License (also
6 ** known as the "2-Clause License" or "FreeBSD License".)
7 **
8 ** This program is distributed in the hope that it will be useful,
9 ** but without any warranty; without even the implied warranty of
10 ** merchantability or fitness for a particular purpose.
11 **
12 ** Author contact information:
13 **   drh@hwaci.com
14 **   http://www.hwaci.com/drh/
15 **
16 *******************************************************************************
17 **
18 ** This file contains code to implement the "/doc" web page and related
19 ** pages.
20 */
21 #include "config.h"
22 #include "doc.h"
23 #include <assert.h>
24 
25 /*
26 ** Try to guess the mimetype from content.
27 **
28 ** If the content is pure text, return NULL.
29 **
30 ** For image types, attempt to return an appropriate mimetype
31 ** name like "image/gif" or "image/jpeg".
32 **
33 ** For any other binary type, return "unknown/unknown".
34 */
mimetype_from_content(Blob * pBlob)35 const char *mimetype_from_content(Blob *pBlob){
36   int i;
37   int n;
38   const unsigned char *x;
39 
40   /* A table of mimetypes based on file content prefixes
41   */
42   static const struct {
43     const char *z;             /* Identifying file text */
44     const unsigned char sz1;   /* Length of the prefix */
45     const unsigned char of2;   /* Offset to the second segment */
46     const unsigned char sz2;   /* Size of the second segment */
47     const unsigned char mn;    /* Minimum size of input */
48     const char *zMimetype;     /* The corresponding mimetype */
49   } aMime[] = {
50     { "GIF87a",                  6, 0, 0, 6,  "image/gif"  },
51     { "GIF89a",                  6, 0, 0, 6,  "image/gif"  },
52     { "\211PNG\r\n\032\n",       8, 0, 0, 8,  "image/png"  },
53     { "\377\332\377",            3, 0, 0, 3,  "image/jpeg" },
54     { "\377\330\377",            3, 0, 0, 3,  "image/jpeg" },
55     { "RIFFWAVEfmt",             4, 8, 7, 15, "sound/wav"  },
56   };
57 
58   if( !looks_like_binary(pBlob) ) {
59     return 0;   /* Plain text */
60   }
61   x = (const unsigned char*)blob_buffer(pBlob);
62   n = blob_size(pBlob);
63   for(i=0; i<count(aMime); i++){
64     if( n<aMime[i].mn ) continue;
65     if( memcmp(x, aMime[i].z, aMime[i].sz1)!=0 ) continue;
66     if( aMime[i].sz2
67      && memcmp(x+aMime[i].of2, aMime[i].z+aMime[i].sz1, aMime[i].sz2)!=0
68     ){
69       continue;
70     }
71     return aMime[i].zMimetype;
72   }
73   return "unknown/unknown";
74 }
75 
76 /* A table of mimetypes based on file suffixes.
77 ** Suffixes must be in sorted order so that we can do a binary
78 ** search to find the mimetype.
79 */
80 static const struct {
81   const char *zSuffix;       /* The file suffix */
82   int size;                  /* Length of the suffix */
83   const char *zMimetype;     /* The corresponding mimetype */
84 } aMime[] = {
85   { "ai",         2, "application/postscript"            },
86   { "aif",        3, "audio/x-aiff"                      },
87   { "aifc",       4, "audio/x-aiff"                      },
88   { "aiff",       4, "audio/x-aiff"                      },
89   { "arj",        3, "application/x-arj-compressed"      },
90   { "asc",        3, "text/plain"                        },
91   { "asf",        3, "video/x-ms-asf"                    },
92   { "asx",        3, "video/x-ms-asx"                    },
93   { "au",         2, "audio/ulaw"                        },
94   { "avi",        3, "video/x-msvideo"                   },
95   { "bat",        3, "application/x-msdos-program"       },
96   { "bcpio",      5, "application/x-bcpio"               },
97   { "bin",        3, "application/octet-stream"          },
98   { "bmp",        3, "image/bmp"                         },
99   { "bz2",        3, "application/x-bzip2"               },
100   { "bzip",       4, "application/x-bzip"                },
101   { "c",          1, "text/plain"                        },
102   { "cc",         2, "text/plain"                        },
103   { "ccad",       4, "application/clariscad"             },
104   { "cdf",        3, "application/x-netcdf"              },
105   { "class",      5, "application/octet-stream"          },
106   { "cod",        3, "application/vnd.rim.cod"           },
107   { "com",        3, "application/x-msdos-program"       },
108   { "cpio",       4, "application/x-cpio"                },
109   { "cpt",        3, "application/mac-compactpro"        },
110   { "cs",         2, "text/plain"                        },
111   { "csh",        3, "application/x-csh"                 },
112   { "css",        3, "text/css"                          },
113   { "csv",        3, "text/csv"                          },
114   { "dcr",        3, "application/x-director"            },
115   { "deb",        3, "application/x-debian-package"      },
116   { "dib",        3, "image/bmp"                         },
117   { "dir",        3, "application/x-director"            },
118   { "dl",         2, "video/dl"                          },
119   { "dms",        3, "application/octet-stream"          },
120   { "doc",        3, "application/msword"                },
121   { "docx",       4, "application/vnd.openxmlformats-"
122                      "officedocument.wordprocessingml.document"},
123   { "dot",        3, "application/msword"                },
124   { "dotx",       4, "application/vnd.openxmlformats-"
125                      "officedocument.wordprocessingml.template"},
126   { "drw",        3, "application/drafting"              },
127   { "dvi",        3, "application/x-dvi"                 },
128   { "dwg",        3, "application/acad"                  },
129   { "dxf",        3, "application/dxf"                   },
130   { "dxr",        3, "application/x-director"            },
131   { "eps",        3, "application/postscript"            },
132   { "etx",        3, "text/x-setext"                     },
133   { "exe",        3, "application/octet-stream"          },
134   { "ez",         2, "application/andrew-inset"          },
135   { "f",          1, "text/plain"                        },
136   { "f90",        3, "text/plain"                        },
137   { "fli",        3, "video/fli"                         },
138   { "flv",        3, "video/flv"                         },
139   { "gif",        3, "image/gif"                         },
140   { "gl",         2, "video/gl"                          },
141   { "gtar",       4, "application/x-gtar"                },
142   { "gz",         2, "application/x-gzip"                },
143   { "h",          1, "text/plain"                        },
144   { "hdf",        3, "application/x-hdf"                 },
145   { "hh",         2, "text/plain"                        },
146   { "hqx",        3, "application/mac-binhex40"          },
147   { "htm",        3, "text/html"                         },
148   { "html",       4, "text/html"                         },
149   { "ice",        3, "x-conference/x-cooltalk"           },
150   { "ico",        3, "image/vnd.microsoft.icon"          },
151   { "ief",        3, "image/ief"                         },
152   { "iges",       4, "model/iges"                        },
153   { "igs",        3, "model/iges"                        },
154   { "ips",        3, "application/x-ipscript"            },
155   { "ipx",        3, "application/x-ipix"                },
156   { "jad",        3, "text/vnd.sun.j2me.app-descriptor"  },
157   { "jar",        3, "application/java-archive"          },
158   { "jpe",        3, "image/jpeg"                        },
159   { "jpeg",       4, "image/jpeg"                        },
160   { "jpg",        3, "image/jpeg"                        },
161   { "js",         2, "application/javascript"            },
162   { "kar",        3, "audio/midi"                        },
163   { "latex",      5, "application/x-latex"               },
164   { "lha",        3, "application/octet-stream"          },
165   { "lsp",        3, "application/x-lisp"                },
166   { "lzh",        3, "application/octet-stream"          },
167   { "m",          1, "text/plain"                        },
168   { "m3u",        3, "audio/x-mpegurl"                   },
169   { "man",        3, "text/plain"                        },
170   { "markdown",   8, "text/x-markdown"                   },
171   { "md",         2, "text/x-markdown"                   },
172   { "me",         2, "application/x-troff-me"            },
173   { "mesh",       4, "model/mesh"                        },
174   { "mid",        3, "audio/midi"                        },
175   { "midi",       4, "audio/midi"                        },
176   { "mif",        3, "application/x-mif"                 },
177   { "mime",       4, "www/mime"                          },
178   { "mkd",        3, "text/x-markdown"                   },
179   { "mov",        3, "video/quicktime"                   },
180   { "movie",      5, "video/x-sgi-movie"                 },
181   { "mp2",        3, "audio/mpeg"                        },
182   { "mp3",        3, "audio/mpeg"                        },
183   { "mp4",        3, "video/mp4"                         },
184   { "mpe",        3, "video/mpeg"                        },
185   { "mpeg",       4, "video/mpeg"                        },
186   { "mpg",        3, "video/mpeg"                        },
187   { "mpga",       4, "audio/mpeg"                        },
188   { "ms",         2, "application/x-troff-ms"            },
189   { "msh",        3, "model/mesh"                        },
190   { "n",          1, "text/plain"                        },
191   { "nc",         2, "application/x-netcdf"              },
192   { "oda",        3, "application/oda"                   },
193   { "odp",        3, "application/vnd.oasis.opendocument.presentation" },
194   { "ods",        3, "application/vnd.oasis.opendocument.spreadsheet" },
195   { "odt",        3, "application/vnd.oasis.opendocument.text" },
196   { "ogg",        3, "application/ogg"                   },
197   { "ogm",        3, "application/ogg"                   },
198   { "pbm",        3, "image/x-portable-bitmap"           },
199   { "pdb",        3, "chemical/x-pdb"                    },
200   { "pdf",        3, "application/pdf"                   },
201   { "pgm",        3, "image/x-portable-graymap"          },
202   { "pgn",        3, "application/x-chess-pgn"           },
203   { "pgp",        3, "application/pgp"                   },
204   { "pikchr",     6, "text/x-pikchr"                     },
205   { "pl",         2, "application/x-perl"                },
206   { "pm",         2, "application/x-perl"                },
207   { "png",        3, "image/png"                         },
208   { "pnm",        3, "image/x-portable-anymap"           },
209   { "pot",        3, "application/mspowerpoint"          },
210   { "potx",       4, "application/vnd.openxmlformats-"
211                      "officedocument.presentationml.template"},
212   { "ppm",        3, "image/x-portable-pixmap"           },
213   { "pps",        3, "application/mspowerpoint"          },
214   { "ppsx",       4, "application/vnd.openxmlformats-"
215                      "officedocument.presentationml.slideshow"},
216   { "ppt",        3, "application/mspowerpoint"          },
217   { "pptx",       4, "application/vnd.openxmlformats-"
218                      "officedocument.presentationml.presentation"},
219   { "ppz",        3, "application/mspowerpoint"          },
220   { "pre",        3, "application/x-freelance"           },
221   { "prt",        3, "application/pro_eng"               },
222   { "ps",         2, "application/postscript"            },
223   { "qt",         2, "video/quicktime"                   },
224   { "ra",         2, "audio/x-realaudio"                 },
225   { "ram",        3, "audio/x-pn-realaudio"              },
226   { "rar",        3, "application/x-rar-compressed"      },
227   { "ras",        3, "image/cmu-raster"                  },
228   { "rgb",        3, "image/x-rgb"                       },
229   { "rm",         2, "audio/x-pn-realaudio"              },
230   { "roff",       4, "application/x-troff"               },
231   { "rpm",        3, "audio/x-pn-realaudio-plugin"       },
232   { "rtf",        3, "text/rtf"                          },
233   { "rtx",        3, "text/richtext"                     },
234   { "scm",        3, "application/x-lotusscreencam"      },
235   { "set",        3, "application/set"                   },
236   { "sgm",        3, "text/sgml"                         },
237   { "sgml",       4, "text/sgml"                         },
238   { "sh",         2, "application/x-sh"                  },
239   { "shar",       4, "application/x-shar"                },
240   { "silo",       4, "model/mesh"                        },
241   { "sit",        3, "application/x-stuffit"             },
242   { "skd",        3, "application/x-koan"                },
243   { "skm",        3, "application/x-koan"                },
244   { "skp",        3, "application/x-koan"                },
245   { "skt",        3, "application/x-koan"                },
246   { "smi",        3, "application/smil"                  },
247   { "smil",       4, "application/smil"                  },
248   { "snd",        3, "audio/basic"                       },
249   { "sol",        3, "application/solids"                },
250   { "spl",        3, "application/x-futuresplash"        },
251   { "src",        3, "application/x-wais-source"         },
252   { "step",       4, "application/STEP"                  },
253   { "stl",        3, "application/SLA"                   },
254   { "stp",        3, "application/STEP"                  },
255   { "sv4cpio",    7, "application/x-sv4cpio"             },
256   { "sv4crc",     6, "application/x-sv4crc"              },
257   { "svg",        3, "image/svg+xml"                     },
258   { "swf",        3, "application/x-shockwave-flash"     },
259   { "t",          1, "application/x-troff"               },
260   { "tar",        3, "application/x-tar"                 },
261   { "tcl",        3, "application/x-tcl"                 },
262   { "tex",        3, "application/x-tex"                 },
263   { "texi",       4, "application/x-texinfo"             },
264   { "texinfo",    7, "application/x-texinfo"             },
265   { "tgz",        3, "application/x-tar-gz"              },
266   { "th1",        3, "application/x-th1"                 },
267   { "tif",        3, "image/tiff"                        },
268   { "tiff",       4, "image/tiff"                        },
269   { "tr",         2, "application/x-troff"               },
270   { "tsi",        3, "audio/TSP-audio"                   },
271   { "tsp",        3, "application/dsptype"               },
272   { "tsv",        3, "text/tab-separated-values"         },
273   { "txt",        3, "text/plain"                        },
274   { "unv",        3, "application/i-deas"                },
275   { "ustar",      5, "application/x-ustar"               },
276   { "vb",         2, "text/plain"                        },
277   { "vcd",        3, "application/x-cdlink"              },
278   { "vda",        3, "application/vda"                   },
279   { "viv",        3, "video/vnd.vivo"                    },
280   { "vivo",       4, "video/vnd.vivo"                    },
281   { "vrml",       4, "model/vrml"                        },
282   { "wav",        3, "audio/x-wav"                       },
283   { "wax",        3, "audio/x-ms-wax"                    },
284   { "webp",       4, "image/webp"                        },
285   { "wiki",       4, "text/x-fossil-wiki"                },
286   { "wma",        3, "audio/x-ms-wma"                    },
287   { "wmv",        3, "video/x-ms-wmv"                    },
288   { "wmx",        3, "video/x-ms-wmx"                    },
289   { "wrl",        3, "model/vrml"                        },
290   { "wvx",        3, "video/x-ms-wvx"                    },
291   { "xbm",        3, "image/x-xbitmap"                   },
292   { "xlc",        3, "application/vnd.ms-excel"          },
293   { "xll",        3, "application/vnd.ms-excel"          },
294   { "xlm",        3, "application/vnd.ms-excel"          },
295   { "xls",        3, "application/vnd.ms-excel"          },
296   { "xlsx",       4, "application/vnd.openxmlformats-"
297                      "officedocument.spreadsheetml.sheet"},
298   { "xlw",        3, "application/vnd.ms-excel"          },
299   { "xml",        3, "text/xml"                          },
300   { "xpm",        3, "image/x-xpixmap"                   },
301   { "xwd",        3, "image/x-xwindowdump"               },
302   { "xyz",        3, "chemical/x-pdb"                    },
303   { "zip",        3, "application/zip"                   },
304 };
305 
306 /*
307 ** Verify that all entries in the aMime[] table are in sorted order.
308 ** Abort with a fatal error if any is out-of-order.
309 */
mimetype_verify(void)310 static void mimetype_verify(void){
311   int i;
312   for(i=1; i<count(aMime); i++){
313     if( fossil_strcmp(aMime[i-1].zSuffix,aMime[i].zSuffix)>=0 ){
314       fossil_panic("mimetypes out of sequence: %s before %s",
315                    aMime[i-1].zSuffix, aMime[i].zSuffix);
316     }
317   }
318 }
319 
320 /*
321 ** Looks in the contents of the "mimetypes" setting for a suffix
322 ** matching zSuffix. If found, it returns the configured value
323 ** in memory owned by the app (i.e. do not free() it), else it
324 ** returns 0.
325 **
326 ** The mimetypes setting is expected to be a list of file extensions
327 ** and mimetypes, with one such mapping per line. A leading '.'  on
328 ** extensions is permitted for compatibility with lists imported from
329 ** other tools which require them.
330 */
mimetype_from_name_custom(const char * zSuffix)331 static const char *mimetype_from_name_custom(const char *zSuffix){
332   static char * zList = 0;
333   static char const * zEnd = 0;
334   static int once = 0;
335   char * z;
336   int tokenizerState /* 0=expecting a key, 1=skip next token,
337                      ** 2=accept next token */;
338   if(once==0){
339     once = 1;
340     zList = db_get("mimetypes",0);
341     if(zList==0){
342       return 0;
343     }
344     /* Transform zList to simplify the main loop:
345        replace non-newline spaces with NUL bytes. */
346     zEnd = zList + strlen(zList);
347     for(z = zList; z<zEnd; ++z){
348       if('\n'==*z) continue;
349       else if(fossil_isspace(*z)){
350         *z = 0;
351       }
352     }
353   }else if(zList==0){
354     return 0;
355   }
356   tokenizerState = 0;
357   z = zList;
358   while( z<zEnd ){
359     if(*z==0){
360       ++z;
361       continue;
362     }
363     else if('\n'==*z){
364       if(2==tokenizerState){
365         /* We were expecting a value for a successful match
366            here, but got no value. Bail out. */
367         break;
368       }else{
369         /* May happen on malformed inputs. Skip this record. */
370         tokenizerState = 0;
371         ++z;
372         continue;
373       }
374     }
375     switch(tokenizerState){
376       case 0:{ /* This is a file extension */
377         static char * zCase = 0;
378         if('.'==*z){
379           /*ignore an optional leading dot, for compatibility
380             with some external mimetype lists*/;
381           if(++z==zEnd){
382             break;
383           }
384         }
385         if(zCase<z){
386           /*we have not yet case-folded this section: lower-case it*/
387           for(zCase = z; zCase<zEnd && *zCase!=0; ++zCase){
388             if(!(0x80 & *zCase)){
389               *zCase = (char)fossil_tolower(*zCase);
390             }
391           }
392         }
393         if(strcmp(z,zSuffix)==0){
394           tokenizerState = 2 /* Match: accept the next value. */;
395         }else{
396           tokenizerState = 1 /* No match: skip the next value. */;
397         }
398         z += strlen(z);
399         break;
400       }
401       case 1: /* This is a value, but not a match. Skip it. */
402         z += strlen(z);
403         break;
404       case 2: /* This is the value which matched the previous key. */;
405         return z;
406       default:
407         assert(!"cannot happen - invalid tokenizerState value.");
408     }
409   }
410   return 0;
411 }
412 
413 /*
414 ** Emit Javascript which applies (or optionally can apply) to both the
415 ** /doc and /wiki pages. None of this implements required
416 ** functionality, just nice-to-haves. Any calls after the first are
417 ** no-ops.
418 */
document_emit_js(void)419 void document_emit_js(void){
420   static int once = 0;
421   if(0==once++){
422     builtin_fossil_js_bundle_or("pikchr", NULL);
423     style_script_begin(__FILE__,__LINE__);
424     CX("window.addEventListener('load', "
425        "()=>window.fossil.pikchr.addSrcView(), "
426        "false);\n");
427     style_script_end();
428   }
429 }
430 
431 /*
432 ** Guess the mimetype of a document based on its name.
433 */
mimetype_from_name(const char * zName)434 const char *mimetype_from_name(const char *zName){
435   const char *z;
436   int i;
437   int first, last;
438   int len;
439   char zSuffix[20];
440 
441 
442 #ifdef FOSSIL_DEBUG
443   /* This is test code to make sure the table above is in the correct
444   ** order
445   */
446   if( fossil_strcmp(zName, "mimetype-test")==0 ){
447     mimetype_verify();
448     return "ok";
449   }
450 #endif
451 
452   z = zName;
453   for(i=0; zName[i]; i++){
454     if( zName[i]=='.' ) z = &zName[i+1];
455   }
456   len = strlen(z);
457   if( len<sizeof(zSuffix)-1 ){
458     sqlite3_snprintf(sizeof(zSuffix), zSuffix, "%s", z);
459     for(i=0; zSuffix[i]; i++) zSuffix[i] = fossil_tolower(zSuffix[i]);
460     z = mimetype_from_name_custom(zSuffix);
461     if(z!=0){
462       return z;
463     }
464     first = 0;
465     last = count(aMime) - 1;
466     while( first<=last ){
467       int c;
468       i = (first+last)/2;
469       c = fossil_strcmp(zSuffix, aMime[i].zSuffix);
470       if( c==0 ) return aMime[i].zMimetype;
471       if( c<0 ){
472         last = i-1;
473       }else{
474         first = i+1;
475       }
476     }
477   }
478   return "application/x-fossil-artifact";
479 }
480 
481 /*
482 ** COMMAND: test-mimetype
483 **
484 ** Usage: %fossil test-mimetype FILENAME...
485 **
486 ** Return the deduced mimetype for each file listed.
487 **
488 ** If Fossil is compiled with -DFOSSIL_DEBUG then the "mimetype-test"
489 ** filename is special and verifies the integrity of the mimetype table.
490 ** It should return "ok".
491 */
mimetype_test_cmd(void)492 void mimetype_test_cmd(void){
493   int i;
494   mimetype_verify();
495   db_find_and_open_repository(0, 0);
496   for(i=2; i<g.argc; i++){
497     fossil_print("%-20s -> %s\n", g.argv[i], mimetype_from_name(g.argv[i]));
498   }
499 }
500 
501 /*
502 ** WEBPAGE: mimetype_list
503 **
504 ** Show the built-in table used to guess embedded document mimetypes
505 ** from file suffixes.
506 */
mimetype_list_page(void)507 void mimetype_list_page(void){
508   int i;
509   char *zCustomList = 0;    /* value of the mimetypes setting */
510   int nCustomEntries = 0;   /* number of entries in the mimetypes
511                             ** setting */
512   mimetype_verify();
513   style_header("Mimetype List");
514   @ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename
515   @ suffixes and the following tables to guess at the appropriate mimetype
516   @ for each document. Mimetypes may be customized and overridden using
517   @ <a href="%R/help?cmd=mimetypes">the mimetypes config setting</a>.</p>
518   zCustomList = db_get("mimetypes",0);
519   if( zCustomList!=0 ){
520     Blob list, entry, key, val;
521     @ <h1>Repository-specific mimetypes</h1>
522     @ <p>The following extension-to-mimetype mappings are defined via
523     @ the <a href="%R/help?cmd=mimetypes">mimetypes setting</a>.</p>
524     @ <table class='sortable mimetypetable' border=1 cellpadding=0 \
525     @ data-column-types='tt' data-init-sort='0'>
526     @ <thead>
527     @ <tr><th>Suffix<th>Mimetype
528     @ </thead>
529     @ <tbody>
530     blob_set(&list, zCustomList);
531     while( blob_line(&list, &entry)>0 ){
532       const char *zKey;
533       if( blob_token(&entry, &key)==0 ) continue;
534       if( blob_token(&entry, &val)==0 ) continue;
535       zKey = blob_str(&key);
536       if( zKey[0]=='.' ) zKey++;
537       @ <tr><td>%h(zKey)<td>%h(blob_str(&val))</tr>
538       nCustomEntries++;
539     }
540     fossil_free(zCustomList);
541     if( nCustomEntries==0 ){
542       /* This can happen if the option is set to an empty/space-only
543       ** value. */
544       @ <tr><td colspan="2"><em>none</em></tr>
545     }
546     @ </tbody></table>
547   }
548   @ <h1>Default built-in mimetypes</h1>
549   if(nCustomEntries>0){
550     @ <p>Entries starting with an exclamation mark <em><strong>!</strong></em>
551     @ are overwritten by repository-specific settings.</p>
552   }
553   @ <table class='sortable mimetypetable' border=1 cellpadding=0 \
554   @ data-column-types='tt' data-init-sort='1'>
555   @ <thead>
556   @ <tr><th>Suffix<th>Mimetype
557   @ </thead>
558   @ <tbody>
559   for(i=0; i<count(aMime); i++){
560     const char *zFlag = "";
561     if(nCustomEntries>0 &&
562        mimetype_from_name_custom(aMime[i].zSuffix)!=0){
563       zFlag = "<em><strong>!</strong></em> ";
564     }
565     @ <tr><td>%s(zFlag)%h(aMime[i].zSuffix)<td>%h(aMime[i].zMimetype)</tr>
566   }
567   @ </tbody></table>
568   style_table_sorter();
569   style_finish_page();
570 }
571 
572 /*
573 ** Check to see if the file in the pContent blob is "embedded HTML".  Return
574 ** true if it is, and fill pTitle with the document title.
575 **
576 ** An "embedded HTML" file is HTML that lacks a header and a footer.  The
577 ** standard Fossil header is prepended and the standard Fossil footer is
578 ** appended.  Otherwise, the file is displayed without change.
579 **
580 ** Embedded HTML must be contained in a <div class='fossil-doc'> element.
581 ** If that <div> also contains a data-title attribute, then the
582 ** value of that attribute is extracted into pTitle and becomes the title
583 ** of the document.
584 */
doc_is_embedded_html(Blob * pContent,Blob * pTitle)585 int doc_is_embedded_html(Blob *pContent, Blob *pTitle){
586   const char *zIn = blob_str(pContent);
587   const char *zAttr;
588   const char *zValue;
589   int nAttr, nValue;
590   int seenClass = 0;
591   int seenTitle = 0;
592 
593   while( fossil_isspace(zIn[0]) ) zIn++;
594   if( fossil_strnicmp(zIn,"<div",4)!=0 ) return 0;
595   zIn += 4;
596   while( zIn[0] ){
597     if( fossil_isspace(zIn[0]) ) zIn++;
598     if( zIn[0]=='>' ) break;
599     zAttr = zIn;
600     while( fossil_isalnum(zIn[0]) || zIn[0]=='-' ) zIn++;
601     nAttr = (int)(zIn - zAttr);
602     while( fossil_isspace(zIn[0]) ) zIn++;
603     if( zIn[0]!='=' ) continue;
604     zIn++;
605     while( fossil_isspace(zIn[0]) ) zIn++;
606     if( zIn[0]=='"' || zIn[0]=='\'' ){
607       char cDelim = zIn[0];
608       zIn++;
609       zValue = zIn;
610       while( zIn[0] && zIn[0]!=cDelim ) zIn++;
611       if( zIn[0]==0 ) return 0;
612       nValue = (int)(zIn - zValue);
613       zIn++;
614     }else{
615       zValue = zIn;
616       while( zIn[0]!=0 && zIn[0]!='>' && zIn[0]!='/'
617             && !fossil_isspace(zIn[0]) ) zIn++;
618       if( zIn[0]==0 ) return 0;
619       nValue = (int)(zIn - zValue);
620     }
621     if( nAttr==5 && fossil_strnicmp(zAttr,"class",5)==0 ){
622       if( nValue!=10 || fossil_strnicmp(zValue,"fossil-doc",10)!=0 ) return 0;
623       seenClass = 1;
624       if( seenTitle ) return 1;
625     }
626     if( nAttr==10 && fossil_strnicmp(zAttr,"data-title",10)==0 ){
627       /* The text argument to data-title="" will have had any characters that
628       ** are special to HTML encoded.  We need to decode these before turning
629       ** the text into a title, as the title text will be reencoded later */
630       char *zTitle = mprintf("%.*s", nValue, zValue);
631       int i;
632       for(i=0; fossil_isspace(zTitle[i]); i++){}
633       html_to_plaintext(zTitle+i, pTitle);
634       fossil_free(zTitle);
635       seenTitle = 1;
636       if( seenClass ) return 1;
637     }
638   }
639   return seenClass;
640 }
641 
642 /*
643 ** Look for a file named zName in the check-in with RID=vid.  Load the content
644 ** of that file into pContent and return the RID for the file.  Or return 0
645 ** if the file is not found or could not be loaded.
646 */
doc_load_content(int vid,const char * zName,Blob * pContent)647 int doc_load_content(int vid, const char *zName, Blob *pContent){
648   int writable = db_is_writeable("repository");
649   int rid;   /* The RID of the file being loaded */
650   if( writable ){
651     db_end_transaction(0);
652     db_begin_write();
653   }
654   if( !db_table_exists("repository", "vcache") || !writable ){
655     db_multi_exec(
656       "CREATE %s TABLE IF NOT EXISTS vcache(\n"
657       "  vid INTEGER,         -- check-in ID\n"
658       "  fname TEXT,          -- filename\n"
659       "  rid INTEGER,         -- artifact ID\n"
660       "  PRIMARY KEY(vid,fname)\n"
661       ") WITHOUT ROWID", writable ? "" : "TEMPORARY"
662     );
663   }
664   if( !db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){
665     db_multi_exec(
666       "DELETE FROM vcache;\n"
667       "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;\n"
668       "INSERT INTO vcache(vid,fname,rid)"
669       "  SELECT checkinID, filename, blob.rid FROM foci, blob"
670       "   WHERE blob.uuid=foci.uuid"
671       "     AND foci.checkinID=%d;",
672       vid
673     );
674   }
675   rid = db_int(0, "SELECT rid FROM vcache"
676                   " WHERE vid=%d AND fname=%Q", vid, zName);
677   if( rid && content_get(rid, pContent)==0 ){
678     rid = 0;
679   }
680   return rid;
681 }
682 
683 /*
684 ** Check to verify that z[i] is contained within HTML markup.
685 **
686 ** This works by looking backwards in the string for the most recent
687 ** '<' or '>' character.  If a '<' is found first, then we assume that
688 ** z[i] is within markup.  If a '>' is seen or neither character is seen,
689 ** then z[i] is not within markup.
690 */
isWithinHtmlMarkup(const char * z,int i)691 static int isWithinHtmlMarkup(const char *z, int i){
692   while( i>=0 && z[i]!='>' && z[i]!='<' ){ i--; }
693   return z[i]=='<';
694 }
695 
696 /*
697 ** Check to see if z[i] is contained within an href='...' of markup.
698 */
isWithinHref(const char * z,int i)699 static int isWithinHref(const char *z, int i){
700   while( i>5
701      && !fossil_isspace(z[i])
702      && z[i]!='\'' && z[i]!='"'
703      && z[i]!='>'
704   ){ i--; }
705   if( i<=6 ) return 0;
706   if( z[i]!='\'' && z[i]!='\"' ) return 0;
707   if( strncmp(&z[i-5],"href=",5)!=0 ) return 0;
708   if( !fossil_isspace(z[i-6]) ) return 0;
709   return 1;
710 }
711 
712 /*
713 ** Transfer content to the output.  During the transfer, when text of
714 ** the following form is seen:
715 **
716 **       href="$ROOT/..."
717 **       action="$ROOT/..."
718 **       href=".../doc/$CURRENT/..."
719 **
720 ** Convert $ROOT to the root URI of the repository, and $CURRENT to the
721 ** version number of the /doc/ document currently being displayed (if any).
722 ** Allow ' in place of " and any case for href or action.
723 **
724 ** Efforts are made to limit this translation to cases where the text is
725 ** fully contained with an HTML markup element.
726 */
convert_href_and_output(Blob * pIn)727 void convert_href_and_output(Blob *pIn){
728   int i, base;
729   int n = blob_size(pIn);
730   char *z = blob_buffer(pIn);
731   for(base=0, i=7; i<n; i++){
732     if( z[i]=='$'
733      && strncmp(&z[i],"$ROOT/", 6)==0
734      && (z[i-1]=='\'' || z[i-1]=='"')
735      && i-base>=9
736      && ((fossil_strnicmp(&z[i-6],"href=",5)==0 && fossil_isspace(z[i-7])) ||
737          (fossil_strnicmp(&z[i-8],"action=",7)==0 && fossil_isspace(z[i-9])) )
738      && isWithinHtmlMarkup(z, i-6)
739     ){
740       blob_append(cgi_output_blob(), &z[base], i-base);
741       blob_appendf(cgi_output_blob(), "%R");
742       base = i+5;
743     }else
744     if( z[i]=='$'
745      && strncmp(&z[i-5],"/doc/$CURRENT/", 11)==0
746      && isWithinHref(z,i-5)
747      && isWithinHtmlMarkup(z, i-5)
748      && strncmp(g.zPath, "doc/",4)==0
749     ){
750       int j;
751       for(j=7; g.zPath[j] && g.zPath[j]!='/'; j++){}
752       blob_append(cgi_output_blob(), &z[base], i-base);
753       blob_appendf(cgi_output_blob(), "%.*s", j-4, g.zPath+4);
754       base = i+8;
755     }
756   }
757   blob_append(cgi_output_blob(), &z[base], i-base);
758 }
759 
760 /*
761 ** Render a document as the reply to the HTTP request.  The body
762 ** of the document is contained in pBody.  The body might be binary.
763 ** The mimetype is in zMimetype.
764 */
document_render(Blob * pBody,const char * zMime,const char * zDefaultTitle,const char * zFilename)765 void document_render(
766   Blob *pBody,                  /* Document content */
767   const char *zMime,            /* MIME-type */
768   const char *zDefaultTitle,    /* Default title */
769   const char *zFilename         /* Name of the file being rendered */
770 ){
771   Blob title;
772   int isPopup = P("popup")!=0;
773   blob_init(&title,0,0);
774   if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0 ){
775     Blob tail;
776     style_adunit_config(ADUNIT_RIGHT_OK);
777     if( wiki_find_title(pBody, &title, &tail) ){
778       if( !isPopup ) style_header("%s", blob_str(&title));
779       wiki_convert(&tail, 0, WIKI_BUTTONS);
780     }else{
781       if( !isPopup ) style_header("%s", zDefaultTitle);
782       wiki_convert(pBody, 0, WIKI_BUTTONS);
783     }
784     if( !isPopup ){
785       document_emit_js();
786       style_finish_page();
787     }
788   }else if( fossil_strcmp(zMime, "text/x-markdown")==0 ){
789     Blob tail = BLOB_INITIALIZER;
790     markdown_to_html(pBody, &title, &tail);
791     if( !isPopup ){
792       if( blob_size(&title)>0 ){
793         style_header("%s", blob_str(&title));
794       }else{
795         style_header("%s", zDefaultTitle);
796       }
797     }
798     convert_href_and_output(&tail);
799     if( !isPopup ){
800       document_emit_js();
801       style_finish_page();
802     }
803   }else if( fossil_strcmp(zMime, "text/plain")==0 ){
804     style_header("%s", zDefaultTitle);
805     @ <blockquote><pre>
806     @ %h(blob_str(pBody))
807     @ </pre></blockquote>
808     document_emit_js();
809     style_finish_page();
810   }else if( fossil_strcmp(zMime, "text/html")==0
811             && doc_is_embedded_html(pBody, &title) ){
812     if( blob_size(&title)==0 ) blob_append(&title,zFilename,-1);
813     if( !isPopup ) style_header("%s", blob_str(&title));
814     convert_href_and_output(pBody);
815     if( !isPopup ){
816       document_emit_js();
817       style_finish_page();
818     }
819   }else if( fossil_strcmp(zMime, "text/x-pikchr")==0 ){
820     style_adunit_config(ADUNIT_RIGHT_OK);
821     style_header("%s", zDefaultTitle);
822     wiki_render_by_mimetype(pBody, zMime);
823     style_finish_page();
824 #ifdef FOSSIL_ENABLE_TH1_DOCS
825   }else if( Th_AreDocsEnabled() &&
826             fossil_strcmp(zMime, "application/x-th1")==0 ){
827     int raw = P("raw")!=0;
828     if( !raw ){
829       Blob tail;
830       blob_zero(&tail);
831       if( wiki_find_title(pBody, &title, &tail) ){
832         style_header("%s", blob_str(&title));
833         Th_Render(blob_str(&tail));
834         blob_reset(&tail);
835       }else{
836         style_header("%h", zFilename);
837         Th_Render(blob_str(pBody));
838       }
839     }else{
840       Th_Render(blob_str(pBody));
841     }
842     if( !raw ){
843       document_emit_js();
844       style_finish_page();
845     }
846 #endif
847   }else{
848     fossil_free(style_csp(1));
849     cgi_set_content_type(zMime);
850     cgi_set_content(pBody);
851   }
852 }
853 
854 
855 /*
856 ** WEBPAGE: uv
857 ** WEBPAGE: doc
858 ** URL: /uv/FILE
859 ** URL: /doc/CHECKIN/FILE
860 **
861 ** CHECKIN can be either tag or hash prefix or timestamp identifying a
862 ** particular check-in, or the name of a branch (meaning the most recent
863 ** check-in on that branch) or one of various magic words:
864 **
865 **     "tip"      means the most recent check-in
866 **
867 **     "ckout"    means the current check-out, if the server is run from
868 **                within a check-out, otherwise it is the same as "tip"
869 **
870 **     "latest"   means use the most recent check-in for the document
871 **                regardless of what branch it occurs on.
872 **
873 ** FILE is the name of a file to delivered up as a webpage.  FILE is relative
874 ** to the root of the source tree of the repository. The FILE must
875 ** be a part of CHECKIN, except when CHECKIN=="ckout" when FILE is read
876 ** directly from disk and need not be a managed file.  For /uv, FILE
877 ** can also be the hash of the unversioned file.
878 **
879 ** The "ckout" CHECKIN is intended for development - to provide a mechanism
880 ** for looking at what a file will look like using the /doc webpage after
881 ** it gets checked in.  Some commands like "fossil ui", "fossil server",
882 ** and "fossil http" accept an argument "--ckout-alias NAME" when allows
883 ** NAME to be understood as an alias for "ckout".  On a site with many
884 ** embedded hyperlinks to /doc/trunk/... one can run with "--ckout-alias trunk"
885 ** to simulate what the pending changes will look like after they are
886 ** checked in.  The NAME alias is stored in g.zCkoutAlias.
887 **
888 ** The file extension is used to decide how to render the file.
889 **
890 ** If FILE ends in "/" then the names "FILE/index.html", "FILE/index.wiki",
891 ** and "FILE/index.md" are tried in that order.  If the binary was compiled
892 ** with TH1 embedded documentation support and the "th1-docs" setting is
893 ** enabled, the name "FILE/index.th1" is also tried.  If none of those are
894 ** found, then FILE is completely replaced by "404.md" and tried.  If that
895 ** is not found, then a default 404 screen is generated.
896 **
897 ** If the file's mimetype is "text/x-fossil-wiki" or "text/x-markdown"
898 ** then headers and footers are added. If the document has mimetype
899 ** text/html then headers and footers are usually not added.  However,
900 ** if a "text/html" document begins with the following div:
901 **
902 **       <div class='fossil-doc' data-title='TEXT'>
903 **
904 ** then headers and footers are supplied.  The optional data-title field
905 ** specifies the title of the document in that case.
906 **
907 ** For fossil-doc documents and for markdown documents, text of the
908 ** form:  "href='$ROOT/" or "action='$ROOT" has the $ROOT name expanded
909 ** to the top-level of the repository.
910 */
doc_page(void)911 void doc_page(void){
912   const char *zName = 0;            /* Argument to the /doc page */
913   const char *zOrigName = "?";      /* Original document name */
914   const char *zMime;                /* Document MIME type */
915   char *zCheckin = "tip";           /* The check-in holding the document */
916   char *zPathSuffix = "";           /* Text to append to g.zPath */
917   int vid = 0;                      /* Artifact of check-in */
918   int rid = 0;                      /* Artifact of file */
919   int i;                            /* Loop counter */
920   Blob filebody;                    /* Content of the documentation file */
921   Blob title;                       /* Document title */
922   int nMiss = (-1);                 /* Failed attempts to find the document */
923   int isUV = g.zPath[0]=='u';       /* True for /uv.  False for /doc */
924   const char *zDfltTitle;
925   static const char *const azSuffix[] = {
926      "index.html", "index.wiki", "index.md"
927 #ifdef FOSSIL_ENABLE_TH1_DOCS
928       , "index.th1"
929 #endif
930   };
931 
932   login_check_credentials();
933   if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
934   style_set_current_feature("doc");
935   blob_init(&title, 0, 0);
936   zDfltTitle = isUV ? "" : "Documentation";
937   db_begin_transaction();
938   while( rid==0 && (++nMiss)<=count(azSuffix) ){
939     zName = P("name");
940     if( isUV ){
941       if( zName==0 ) zName = "index.wiki";
942       i = 0;
943     }else{
944       if( zName==0 || zName[0]==0 ) zName = "tip/index.wiki";
945       for(i=0; zName[i] && zName[i]!='/'; i++){}
946       zCheckin = mprintf("%.*s", i, zName);
947       if( fossil_strcmp(zCheckin,"ckout")==0 && g.localOpen==0 ){
948         zCheckin = "tip";
949       }else if( fossil_strcmp(zCheckin,"latest")==0 ){
950         char *zNewCkin = db_text(0,
951           "SELECT uuid FROM blob, mlink, event, filename"
952           " WHERE filename.name=%Q"
953           "   AND mlink.fnid=filename.fnid"
954           "   AND blob.rid=mlink.mid"
955           "   AND event.objid=mlink.mid"
956           " ORDER BY event.mtime DESC LIMIT 1",
957           zName + i + 1);
958         if( zNewCkin ) zCheckin = zNewCkin;
959       }
960     }
961     if( nMiss==count(azSuffix) ){
962       zName = "404.md";
963       zDfltTitle = "Not Found";
964     }else if( zName[i]==0 ){
965       assert( nMiss>=0 && nMiss<count(azSuffix) );
966       zName = azSuffix[nMiss];
967     }else if( !isUV ){
968       zName += i;
969     }
970     while( zName[0]=='/' ){ zName++; }
971     if( isUV ){
972       zPathSuffix = fossil_strdup(zName);
973     }else{
974       zPathSuffix = mprintf("%s/%s", zCheckin, zName);
975     }
976     if( nMiss==0 ) zOrigName = zName;
977     if( !file_is_simple_pathname(zName, 1) ){
978       if( sqlite3_strglob("*/", zName)==0 ){
979         assert( nMiss>=0 && nMiss<count(azSuffix) );
980         zName = mprintf("%s%s", zName, azSuffix[nMiss]);
981         if( !file_is_simple_pathname(zName, 1) ){
982           goto doc_not_found;
983         }
984       }else{
985         goto doc_not_found;
986       }
987     }
988     if( isUV ){
989       if( db_table_exists("repository","unversioned") ){
990         rid = unversioned_content(zName, &filebody);
991         if( rid==1 ){
992           Stmt q;
993           db_prepare(&q, "SELECT hash, mtime FROM unversioned"
994                          " WHERE name=%Q", zName);
995           if( db_step(&q)==SQLITE_ROW ){
996             etag_check(ETAG_HASH, db_column_text(&q,0));
997             etag_last_modified(db_column_int64(&q,1));
998           }
999           db_finalize(&q);
1000         }else if( rid==2 ){
1001           zName = db_text(zName,
1002              "SELECT name FROM unversioned WHERE hash=%Q", zName);
1003           g.isConst = 1;
1004         }
1005         zDfltTitle = zName;
1006       }
1007     }else if( fossil_strcmp(zCheckin,"ckout")==0
1008            || fossil_strcmp(zCheckin,g.zCkoutAlias)==0
1009     ){
1010       /* Read from the local checkout */
1011       char *zFullpath;
1012       db_must_be_within_tree();
1013       zFullpath = mprintf("%s/%s", g.zLocalRoot, zName);
1014       if( file_isfile(zFullpath, RepoFILE)
1015        && blob_read_from_file(&filebody, zFullpath, RepoFILE)>0 ){
1016         rid = 1;  /* Fake RID just to get the loop to end */
1017       }
1018       fossil_free(zFullpath);
1019     }else{
1020       vid = symbolic_name_to_rid(zCheckin, "ci");
1021       rid = vid>0 ? doc_load_content(vid, zName, &filebody) : 0;
1022     }
1023   }
1024   g.zPath = mprintf("%s/%s", g.zPath, zPathSuffix);
1025   if( rid==0 ) goto doc_not_found;
1026   blob_to_utf8_no_bom(&filebody, 0);
1027 
1028   /* The file is now contained in the filebody blob.  Deliver the
1029   ** file to the user
1030   */
1031   zMime = nMiss==0 ? P("mimetype") : 0;
1032   if( zMime==0 ){
1033     zMime = mimetype_from_name(zName);
1034   }
1035   Th_Store("doc_name", zName);
1036   if( vid ){
1037     Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'"
1038                                        "  FROM blob WHERE rid=%d", vid));
1039     Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event"
1040                                     " WHERE objid=%d AND type='ci'", vid));
1041   }
1042   document_render(&filebody, zMime, zDfltTitle, zName);
1043   if( nMiss>=count(azSuffix) ) cgi_set_status(404, "Not Found");
1044   db_end_transaction(0);
1045   return;
1046 
1047   /* Jump here when unable to locate the document */
1048 doc_not_found:
1049   db_end_transaction(0);
1050   if( isUV && P("name")==0 ){
1051     uvlist_page();
1052     return;
1053   }
1054   cgi_set_status(404, "Not Found");
1055   style_header("Not Found");
1056   @ <p>Document %h(zOrigName) not found
1057   if( fossil_strcmp(zCheckin,"ckout")!=0 ){
1058     @ in %z(href("%R/tree?ci=%T",zCheckin))%h(zCheckin)</a>
1059   }
1060   style_finish_page();
1061   return;
1062 }
1063 
1064 /*
1065 ** The default logo.
1066 */
1067 static const unsigned char aLogo[] = {
1068     71,  73,  70,  56,  55,  97,  62,   0,  71,   0, 244,   0,   0,  85,
1069    129, 149,  95, 136, 155,  99, 139, 157, 106, 144, 162, 113, 150, 166,
1070    116, 152, 168, 127, 160, 175, 138, 168, 182, 148, 176, 188, 159, 184,
1071    195, 170, 192, 202, 180, 199, 208, 184, 202, 210, 191, 207, 215, 201,
1072    215, 221, 212, 223, 228, 223, 231, 235, 226, 227, 226, 226, 234, 237,
1073    233, 239, 241, 240, 244, 246, 244, 247, 248, 255, 255, 255,   0,   0,
1074      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
1075      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  44,   0,   0,
1076      0,   0,  62,   0,  71,   0,   0,   5, 255,  96, 100, 141, 100, 105,
1077    158, 168,  37,  41, 132, 192, 164, 112,  44, 207, 102,  99,   0,  56,
1078     16,  84, 116, 239, 199, 141,  65, 110, 232, 248,  25, 141, 193, 161,
1079     82, 113, 108, 202,  32,  55, 229, 210,  73,  61,  41, 164,  88, 102,
1080    181,  10,  41,  96, 179,  91, 106,  35, 240,   5, 135, 143, 137, 242,
1081     87, 123, 246,  33, 190,  81, 108, 163, 237, 198,  14,  30, 113, 233,
1082    131,  78, 115,  72,  11, 115,  87, 101,  19, 124,  51,  66,  74,   8,
1083     19,  16,  67, 100,  74, 133,  50,  15, 101, 135,  56,  11,  74,   6,
1084    143,  49, 126, 106,  56,   8, 145,  67,   9, 152,  48, 139, 155,   5,
1085     22,  13,  74, 115, 161,  41, 147, 101,  13, 130,  57, 132, 170,  40,
1086    167, 155,   0,  94,  57,   3, 178,  48, 183, 181,  57, 160, 186,  40,
1087     19, 141, 189,   0,  69, 192,  40,  16, 195, 155, 185, 199,  41, 201,
1088    189, 191, 205, 193, 188, 131, 210,  49, 175,  88, 209, 214,  38,  19,
1089      3,  11,  19, 111, 127,  60, 219,  39,  55, 204,  19,  11,   6, 100,
1090      5,  10, 227, 228,  37, 163,   0, 239, 117,  56, 238, 243,  49, 195,
1091    177, 247,  48, 158,  56, 251,  50, 216, 254, 197,  56, 128, 107, 158,
1092      2, 125, 171, 114,  92, 218, 246,  96,  66,   3,   4,  50, 134, 176,
1093    145,   6,  97,  64, 144,  24,  19, 136, 108,  91, 177, 160,   0, 194,
1094     19, 253,   0, 216, 107, 214, 224, 192, 129,   5,  16,  83, 255, 244,
1095     43, 213, 195,  24, 159,  27, 169,  64, 230,  88, 208, 227, 129, 182,
1096     54,   4,  89, 158,  24, 181, 163, 199,   1, 155,  52, 233,   8, 130,
1097    176,  83,  24, 128, 137,  50,  18,  32,  48,  48, 114,  11, 173, 137,
1098     19, 110,   4,  64, 105,   1, 194,  30, 140,  68,  15,  24,  24, 224,
1099     50,  76,  70,   0,  11, 171,  54,  26, 160, 181, 194, 149, 148,  40,
1100    174, 148, 122,  64, 180, 208, 161,  17, 207, 112, 164,   1, 128,  96,
1101    148,  78,  18,  21, 194,  33, 229,  51, 247,  65, 133,  97,   5, 250,
1102     69, 229, 100,  34, 220, 128, 166, 116, 190,  62,   8, 167, 195, 170,
1103     47, 163,   0, 130,  90, 152,  11, 160, 173, 170,  27, 154,  26,  91,
1104    232, 151, 171,  18,  14, 162, 253,  98, 170,  18,  70, 171,  64, 219,
1105     10,  67, 136, 134, 187, 116,  75, 180,  46, 179, 174, 135,   4, 189,
1106    229, 231,  78,  40,  10,  62, 226, 164, 172,  64, 240, 167, 170,  10,
1107     18, 124, 188,  10, 107,  65, 193,  94,  11,  93, 171,  28, 248,  17,
1108    239,  46, 140,  78,  97,  34,  25, 153,  36,  99,  65, 130,   7, 203,
1109    183, 168,  51,  34, 136,  25, 140,  10,   6,  16,  28, 255, 145, 241,
1110    230, 140,  10,  66, 178, 167, 112,  48, 192, 128, 129,   9,  31, 141,
1111     84, 138,  63, 163, 162,   2, 203, 206, 240,  56,  55,  98, 192, 188,
1112     15, 185,  50, 160,   6,   0, 125,  62,  33, 214, 195,  33,   5,  24,
1113    184,  25, 231,  14, 201, 245, 144,  23, 126, 104, 228,   0, 145,   2,
1114     13, 140, 244, 212,  17,  21,  20, 176, 159,  17,  95, 225, 160, 128,
1115     16,   1,  32, 224, 142,  32, 227, 125,  87,  64,   0,  16,  54, 129,
1116    205,   2, 141,  76,  53, 130, 103,  37, 166,  64, 144, 107,  78, 196,
1117      5, 192,   0,  54,  50, 229,   9, 141,  49,  84, 194,  35,  12, 196,
1118    153,  48, 192, 137,  57,  84,  24,   7,  87, 159, 249, 240, 215, 143,
1119    105, 241, 118, 149,   9, 139,   4,  64, 203, 141,  35, 140, 129, 131,
1120     16, 222, 125, 231, 128,   2, 238,  17, 152,  66,   3,   5,  56, 224,
1121    159, 103,  16,  76,  25,  75,   5,  11, 164, 215,  96,   9,  14,  16,
1122     36, 225,  15,  11,  40, 144, 192, 156,  41,  10, 178, 199,   3,  66,
1123     64,  80, 193,   3, 124,  90,  48, 129, 129, 102, 177,  18, 192, 154,
1124     49,  84, 240, 208,  92,  22, 149,  96,  39,   9,  31,  74,  17,  94,
1125      3,   8, 177, 199,  72,  59,  85,  76,  25, 216,   8, 139, 194, 197,
1126    138, 163,  69,  96, 115,   0, 147,  72,  72,  84,  28,  14,  79,  86,
1127    233, 230,  23, 113,  26, 160, 128,   3,  10,  58, 129, 103,  14, 159,
1128    214, 163, 146, 117, 238, 213, 154, 128, 151, 109,  84,  64, 217,  13,
1129     27,  10, 228,  39,   2, 235, 164, 168,  74,   8,   0,  59,
1130 };
1131 
1132 /*
1133 ** WEBPAGE: logo
1134 **
1135 ** Return the logo image.  This image is available to anybody who can see
1136 ** the login page.  It is designed for use in the upper left-hand corner
1137 ** of the header.
1138 */
logo_page(void)1139 void logo_page(void){
1140   Blob logo;
1141   char *zMime;
1142 
1143   etag_check(ETAG_CONFIG, 0);
1144   zMime = db_get("logo-mimetype", "image/gif");
1145   blob_zero(&logo);
1146   db_blob(&logo, "SELECT value FROM config WHERE name='logo-image'");
1147   if( blob_size(&logo)==0 ){
1148     blob_init(&logo, (char*)aLogo, sizeof(aLogo));
1149   }
1150   cgi_set_content_type(zMime);
1151   cgi_set_content(&logo);
1152 }
1153 
1154 /*
1155 ** The default background image:  a 16x16 white GIF
1156 */
1157 static const unsigned char aBackground[] = {
1158     71,  73,  70,  56,  57,  97,  16,   0,  16,   0,
1159    240,   0,   0, 255, 255, 255,   0,   0,   0,  33,
1160    254,   4, 119, 105, 115, 104,   0,  44,   0,   0,
1161      0,   0,  16,   0,  16,   0,   0,   2,  14, 132,
1162    143, 169, 203, 237,  15, 163, 156, 180, 218, 139,
1163    179,  62,   5,   0,  59,
1164 };
1165 
1166 
1167 /*
1168 ** WEBPAGE: background
1169 **
1170 ** Return the background image.  If no background image is defined, a
1171 ** built-in 16x16 pixel white GIF is returned.
1172 */
background_page(void)1173 void background_page(void){
1174   Blob bgimg;
1175   char *zMime;
1176 
1177   etag_check(ETAG_CONFIG, 0);
1178   zMime = db_get("background-mimetype", "image/gif");
1179   blob_zero(&bgimg);
1180   db_blob(&bgimg, "SELECT value FROM config WHERE name='background-image'");
1181   if( blob_size(&bgimg)==0 ){
1182     blob_init(&bgimg, (char*)aBackground, sizeof(aBackground));
1183   }
1184   cgi_set_content_type(zMime);
1185   cgi_set_content(&bgimg);
1186 }
1187 
1188 
1189 /*
1190 ** WEBPAGE: favicon.ico
1191 **
1192 ** Return the configured "favicon.ico" image.  If no "favicon.ico" image
1193 ** is defined, the returned image is for the Fossil lizard icon.
1194 **
1195 ** The intended use case here is to supply an icon for the "fossil ui"
1196 ** command.  For a permanent website, the recommended process is for
1197 ** the admin to set up a project-specific icon and reference that icon
1198 ** in the HTML header using a line like:
1199 **
1200 **   <link rel="icon" href="URL-FOR-YOUR-ICON" type="MIMETYPE"/>
1201 **
1202 */
favicon_page(void)1203 void favicon_page(void){
1204   Blob icon;
1205   char *zMime;
1206 
1207   etag_check(ETAG_CONFIG, 0);
1208   zMime = db_get("icon-mimetype", "image/gif");
1209   blob_zero(&icon);
1210   db_blob(&icon, "SELECT value FROM config WHERE name='icon-image'");
1211   if( blob_size(&icon)==0 ){
1212     blob_init(&icon, (char*)aLogo, sizeof(aLogo));
1213   }
1214   cgi_set_content_type(zMime);
1215   cgi_set_content(&icon);
1216 }
1217 
1218 /*
1219 ** WEBPAGE: docsrch
1220 **
1221 ** Search for documents that match a user-supplied full-text search pattern.
1222 ** If no pattern is specified (by the s= query parameter) then the user
1223 ** is prompted to enter a search string.
1224 **
1225 ** Query parameters:
1226 **
1227 **     s=PATTERN             Search for PATTERN
1228 */
doc_search_page(void)1229 void doc_search_page(void){
1230   const int isSearch = P("s")!=0;
1231   login_check_credentials();
1232   style_header("Document Search%s", isSearch ? " Results" : "");
1233   search_screen(SRCH_DOC, 0);
1234   style_finish_page();
1235 }
1236