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