Fossil SCM

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

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button