Fossil SCM

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

Keyboard Shortcuts

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