Fossil SCM

Clean up the /doc webpage. Use newer interfaces that were created since that page was original written. Set the HTTP status to 404 on not found. Try to find a "404.md" page to display and use it if found instead of the default 404 page.

drh 2015-01-23 04:26 trunk
Commit cfcd9b87dcf54a16f206cbeebf7c443b5023532d
1 file changed +76 -93
+76 -93
--- src/doc.c
+++ src/doc.c
@@ -353,147 +353,125 @@
353353
}
354354
}
355355
356356
/*
357357
** WEBPAGE: doc
358
-** URL: /doc?name=BASELINE/PATH
359
-** URL: /doc/BASELINE/PATH
360
-**
361
-** BASELINE can be either a baseline uuid prefix or magic words "tip"
362
-** to mean the most recently checked in baseline or "ckout" to mean the
363
-** content of the local checkout, if any. PATH is the relative pathname
364
-** of some file. This method returns the file content.
365
-**
366
-** If PATH matches the patterns *.wiki or *.txt then formatting content
367
-** is added before returning the file. For all other names, the content
368
-** is returned straight without any interpretation or processing.
358
+** URL: /doc?name=CHECKIN/FILE
359
+** URL: /doc/CHECKIN/FILE
360
+**
361
+** CHECKIN can be either tag or SHA1 hash or timestamp identifying a
362
+** particular check, or the name of a branch (meaning the most recent
363
+** check-in on that branch) or one of various magic words:
364
+**
365
+** "tip" means the most recent check-in
366
+**
367
+** "ckout" means the current check-out, if the server is run from
368
+** within a check-out, otherwise it is the same as "tip"
369
+**
370
+** FILE is the name of a file to delivered up as a webpage. FILE is relative
371
+** to the root of the source tree of the repository. The FILE must
372
+** be a part of CHECKIN, except when CHECKIN=="ckout" when FILE is read
373
+** directly from disk and need not be a managed file.
374
+**
375
+** The "ckout" CHECKIN is intended for development - to provide a mechanism
376
+** for looking at what a file will look like using the /doc webpage after
377
+** it gets checked in.
378
+**
379
+** The file extension is used to decide how to render the file.
369380
*/
370381
void doc_page(void){
371382
const char *zName; /* Argument to the /doc page */
383
+ const char *zOrigName; /* Original document name */
372384
const char *zMime; /* Document MIME type */
373
- int vid = 0; /* Artifact of baseline */
385
+ char *zCheckin; /* The checkin holding the document */
386
+ int vid = 0; /* Artifact of checkin */
374387
int rid = 0; /* Artifact of file */
375388
int i; /* Loop counter */
376389
Blob filebody; /* Content of the documentation file */
377
- char zBaseline[UUID_SIZE+1]; /* Baseline UUID */
390
+ int nMiss = 0; /* Failed attempts to find the document */
378391
379392
login_check_credentials();
380393
if( !g.perm.Read ){ login_needed(); return; }
381394
zName = PD("name", "tip/index.wiki");
382395
for(i=0; zName[i] && zName[i]!='/'; i++){}
383
- if( zName[i]==0 || i>UUID_SIZE ){
384
- zName = "index.html";
385
- goto doc_not_found;
386
- }
387
- g.zPath = mprintf("%s/%s", g.zPath, zName);
388
- memcpy(zBaseline, zName, i);
389
- zBaseline[i] = 0;
390
- zName += i;
396
+ zCheckin = mprintf("%.*s", i, zName);
397
+ if( zName[i]==0 ){
398
+ zName = "index.wiki";
399
+ }else{
400
+ zName += i;
401
+ }
391402
while( zName[0]=='/' ){ zName++; }
403
+ g.zPath = mprintf("%s/%s/%s", g.zPath, zCheckin, zName);
392404
if( !file_is_simple_pathname(zName, 1) ){
393
- int n = strlen(zName);
394
- if( n>0 && zName[n-1]=='/' ){
395
- zName = mprintf("%sindex.html", zName);
405
+ if( sqlite3_strglob("*/", zName)==0 ){
406
+ zName = mprintf("%sindex.wiki", zName);
396407
if( !file_is_simple_pathname(zName, 1) ){
397408
goto doc_not_found;
398409
}
399410
}else{
400411
goto doc_not_found;
401412
}
402413
}
403
- if( fossil_strcmp(zBaseline,"ckout")==0 && db_open_local(0)==0 ){
404
- sqlite3_snprintf(sizeof(zBaseline), zBaseline, "tip");
414
+ zOrigName = zName;
415
+ if( fossil_strcmp(zCheckin,"ckout")==0 && db_open_local(0)==0 ){
416
+ sqlite3_snprintf(sizeof(zCheckin), zCheckin, "tip");
405417
}
406
- if( fossil_strcmp(zBaseline,"ckout")==0 ){
418
+ if( fossil_strcmp(zCheckin,"ckout")==0 ){
407419
/* Read from the local checkout */
408420
char *zFullpath;
409421
db_must_be_within_tree();
410
- zFullpath = mprintf("%s/%s", g.zLocalRoot, zName);
411
- if( !file_isfile(zFullpath) ){
412
- goto doc_not_found;
413
- }
414
- if( blob_read_from_file(&filebody, zFullpath)<0 ){
415
- goto doc_not_found;
422
+ while( rid==0 && nMiss<2 ){
423
+ zFullpath = mprintf("%s/%s", g.zLocalRoot, zName);
424
+ if( file_isfile(zFullpath)
425
+ && blob_read_from_file(&filebody, zFullpath)<0 ){
426
+ rid = 1; /* Fake RID just to get the loop to end */
427
+ }
428
+ fossil_free(zFullpath);
429
+ if( rid ) break;
430
+ nMiss++;
431
+ zName = "404.md";
416432
}
417433
}else{
418434
db_begin_transaction();
419
- if( fossil_strcmp(zBaseline,"tip")==0 ){
420
- vid = db_int(0, "SELECT objid FROM event WHERE type='ci'"
421
- " ORDER BY mtime DESC LIMIT 1");
422
- }else{
423
- vid = name_to_typed_rid(zBaseline, "ci");
424
- }
425
-
426
- /* Create the baseline cache if it does not already exist */
435
+ vid = name_to_typed_rid(zCheckin, "ci");
427436
db_multi_exec(
428437
"CREATE TABLE IF NOT EXISTS vcache(\n"
429
- " vid INTEGER, -- baseline ID\n"
438
+ " vid INTEGER, -- checkin ID\n"
430439
" fname TEXT, -- filename\n"
431440
" rid INTEGER, -- artifact ID\n"
432
- " UNIQUE(vid,fname,rid)\n"
433
- ")"
441
+ " PRIMARY KEY(vid,fname)\n"
442
+ ") WITHOUT ROWID"
434443
);
435
-
436
-
437
-
438
- /* Check to see if the documentation file artifact ID is contained
439
- ** in the baseline cache */
440
- rid = db_int(0, "SELECT rid FROM vcache"
441
- " WHERE vid=%d AND fname=%Q", vid, zName);
442
- if( rid==0 && db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){
443
- goto doc_not_found;
444
- }
445
-
446
- if( rid==0 ){
447
- Stmt s;
448
- Manifest *pM;
449
- ManifestFile *pFile;
450
-
451
- /* Add the vid baseline to the cache */
452
- if( db_int(0, "SELECT count(*) FROM vcache")>10000 ){
453
- db_multi_exec("DELETE FROM vcache");
454
- }
455
- pM = manifest_get(vid, CFTYPE_MANIFEST, 0);
456
- if( pM==0 ){
457
- goto doc_not_found;
458
- }
459
- db_prepare(&s,
444
+ if( !db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){
445
+ db_multi_exec(
446
+ "DELETE FROM vcache;\n"
447
+ "CREATE VIRTUAL TABLE temp.foci USING files_of_checkin;\n"
460448
"INSERT INTO vcache(vid,fname,rid)"
461
- " SELECT %d, :fname, rid FROM blob"
462
- " WHERE uuid=:uuid",
449
+ " SELECT checkinID, filename, blob.rid FROM foci, blob"
450
+ " WHERE blob.uuid=foci.uuid"
451
+ " AND foci.checkinID=%d;",
463452
vid
464453
);
465
- manifest_file_rewind(pM);
466
- while( (pFile = manifest_file_next(pM,0))!=0 ){
467
- db_bind_text(&s, ":fname", pFile->zName);
468
- db_bind_text(&s, ":uuid", pFile->zUuid);
469
- db_step(&s);
470
- db_reset(&s);
471
- }
472
- db_finalize(&s);
473
- manifest_destroy(pM);
474
-
475
- /* Try again to find the file */
454
+ }
455
+ while( rid==0 && nMiss<2 ){
476456
rid = db_int(0, "SELECT rid FROM vcache"
477457
" WHERE vid=%d AND fname=%Q", vid, zName);
478
- }
479
- if( rid==0 ){
480
- goto doc_not_found;
458
+ if( rid ) break;
459
+ nMiss++;
460
+ zName = "404.md";
481461
}
482
-
483
- /* Get the file content */
484
- if( content_get(rid, &filebody)==0 ){
462
+ if( rid==0 || content_get(rid, &filebody)==0 ){
485463
goto doc_not_found;
486464
}
487465
db_end_transaction(0);
488466
}
489467
blob_to_utf8_no_bom(&filebody, 0);
490468
491469
/* The file is now contained in the filebody blob. Deliver the
492470
** file to the user
493471
*/
494
- zMime = P("mimetype");
472
+ zMime = nMiss==0 ? P("mimetype") : 0;
495473
if( zMime==0 ){
496474
zMime = mimetype_from_name(zName);
497475
}
498476
Th_Store("doc_name", zName);
499477
Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'"
@@ -516,11 +494,11 @@
516494
Blob tail = BLOB_INITIALIZER;
517495
markdown_to_html(&filebody, &title, &tail);
518496
if( blob_size(&title)>0 ){
519497
style_header("%s", blob_str(&title));
520498
}else{
521
- style_header("Documentation");
499
+ style_header("%s", nMiss?"Not Found":"Documentation");
522500
}
523501
blob_append(cgi_output_blob(), blob_buffer(&tail), blob_size(&tail));
524502
style_footer();
525503
}else if( fossil_strcmp(zMime, "text/plain")==0 ){
526504
style_header("Documentation");
@@ -537,17 +515,22 @@
537515
#endif
538516
}else{
539517
cgi_set_content_type(zMime);
540518
cgi_set_content(&filebody);
541519
}
520
+ if( nMiss ) cgi_set_status(404, "Not Found");
542521
return;
543522
544
-doc_not_found:
545523
/* Jump here when unable to locate the document */
524
+doc_not_found:
546525
db_end_transaction(0);
547
- style_header("Document Not Found");
548
- @ <p>No such document: %h(zName)</p>
526
+ cgi_set_status(404, "Not Found");
527
+ style_header("Not Found");
528
+ @ <p>Document %h(zOrigName) not found
529
+ if( fossil_strcmp(zCheckin,"ckout")!=0 ){
530
+ @ in %z(href("%R/tree?ci=%T",zCheckin))%h(zCheckin)</a>
531
+ }
549532
style_footer();
550533
return;
551534
}
552535
553536
/*
554537
--- src/doc.c
+++ src/doc.c
@@ -353,147 +353,125 @@
353 }
354 }
355
356 /*
357 ** WEBPAGE: doc
358 ** URL: /doc?name=BASELINE/PATH
359 ** URL: /doc/BASELINE/PATH
360 **
361 ** BASELINE can be either a baseline uuid prefix or magic words "tip"
362 ** to mean the most recently checked in baseline or "ckout" to mean the
363 ** content of the local checkout, if any. PATH is the relative pathname
364 ** of some file. This method returns the file content.
365 **
366 ** If PATH matches the patterns *.wiki or *.txt then formatting content
367 ** is added before returning the file. For all other names, the content
368 ** is returned straight without any interpretation or processing.
 
 
 
 
 
 
 
 
 
 
 
369 */
370 void doc_page(void){
371 const char *zName; /* Argument to the /doc page */
 
372 const char *zMime; /* Document MIME type */
373 int vid = 0; /* Artifact of baseline */
 
374 int rid = 0; /* Artifact of file */
375 int i; /* Loop counter */
376 Blob filebody; /* Content of the documentation file */
377 char zBaseline[UUID_SIZE+1]; /* Baseline UUID */
378
379 login_check_credentials();
380 if( !g.perm.Read ){ login_needed(); return; }
381 zName = PD("name", "tip/index.wiki");
382 for(i=0; zName[i] && zName[i]!='/'; i++){}
383 if( zName[i]==0 || i>UUID_SIZE ){
384 zName = "index.html";
385 goto doc_not_found;
386 }
387 g.zPath = mprintf("%s/%s", g.zPath, zName);
388 memcpy(zBaseline, zName, i);
389 zBaseline[i] = 0;
390 zName += i;
391 while( zName[0]=='/' ){ zName++; }
 
392 if( !file_is_simple_pathname(zName, 1) ){
393 int n = strlen(zName);
394 if( n>0 && zName[n-1]=='/' ){
395 zName = mprintf("%sindex.html", zName);
396 if( !file_is_simple_pathname(zName, 1) ){
397 goto doc_not_found;
398 }
399 }else{
400 goto doc_not_found;
401 }
402 }
403 if( fossil_strcmp(zBaseline,"ckout")==0 && db_open_local(0)==0 ){
404 sqlite3_snprintf(sizeof(zBaseline), zBaseline, "tip");
 
405 }
406 if( fossil_strcmp(zBaseline,"ckout")==0 ){
407 /* Read from the local checkout */
408 char *zFullpath;
409 db_must_be_within_tree();
410 zFullpath = mprintf("%s/%s", g.zLocalRoot, zName);
411 if( !file_isfile(zFullpath) ){
412 goto doc_not_found;
413 }
414 if( blob_read_from_file(&filebody, zFullpath)<0 ){
415 goto doc_not_found;
 
 
 
 
416 }
417 }else{
418 db_begin_transaction();
419 if( fossil_strcmp(zBaseline,"tip")==0 ){
420 vid = db_int(0, "SELECT objid FROM event WHERE type='ci'"
421 " ORDER BY mtime DESC LIMIT 1");
422 }else{
423 vid = name_to_typed_rid(zBaseline, "ci");
424 }
425
426 /* Create the baseline cache if it does not already exist */
427 db_multi_exec(
428 "CREATE TABLE IF NOT EXISTS vcache(\n"
429 " vid INTEGER, -- baseline ID\n"
430 " fname TEXT, -- filename\n"
431 " rid INTEGER, -- artifact ID\n"
432 " UNIQUE(vid,fname,rid)\n"
433 ")"
434 );
435
436
437
438 /* Check to see if the documentation file artifact ID is contained
439 ** in the baseline cache */
440 rid = db_int(0, "SELECT rid FROM vcache"
441 " WHERE vid=%d AND fname=%Q", vid, zName);
442 if( rid==0 && db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){
443 goto doc_not_found;
444 }
445
446 if( rid==0 ){
447 Stmt s;
448 Manifest *pM;
449 ManifestFile *pFile;
450
451 /* Add the vid baseline to the cache */
452 if( db_int(0, "SELECT count(*) FROM vcache")>10000 ){
453 db_multi_exec("DELETE FROM vcache");
454 }
455 pM = manifest_get(vid, CFTYPE_MANIFEST, 0);
456 if( pM==0 ){
457 goto doc_not_found;
458 }
459 db_prepare(&s,
460 "INSERT INTO vcache(vid,fname,rid)"
461 " SELECT %d, :fname, rid FROM blob"
462 " WHERE uuid=:uuid",
 
463 vid
464 );
465 manifest_file_rewind(pM);
466 while( (pFile = manifest_file_next(pM,0))!=0 ){
467 db_bind_text(&s, ":fname", pFile->zName);
468 db_bind_text(&s, ":uuid", pFile->zUuid);
469 db_step(&s);
470 db_reset(&s);
471 }
472 db_finalize(&s);
473 manifest_destroy(pM);
474
475 /* Try again to find the file */
476 rid = db_int(0, "SELECT rid FROM vcache"
477 " WHERE vid=%d AND fname=%Q", vid, zName);
478 }
479 if( rid==0 ){
480 goto doc_not_found;
481 }
482
483 /* Get the file content */
484 if( content_get(rid, &filebody)==0 ){
485 goto doc_not_found;
486 }
487 db_end_transaction(0);
488 }
489 blob_to_utf8_no_bom(&filebody, 0);
490
491 /* The file is now contained in the filebody blob. Deliver the
492 ** file to the user
493 */
494 zMime = P("mimetype");
495 if( zMime==0 ){
496 zMime = mimetype_from_name(zName);
497 }
498 Th_Store("doc_name", zName);
499 Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'"
@@ -516,11 +494,11 @@
516 Blob tail = BLOB_INITIALIZER;
517 markdown_to_html(&filebody, &title, &tail);
518 if( blob_size(&title)>0 ){
519 style_header("%s", blob_str(&title));
520 }else{
521 style_header("Documentation");
522 }
523 blob_append(cgi_output_blob(), blob_buffer(&tail), blob_size(&tail));
524 style_footer();
525 }else if( fossil_strcmp(zMime, "text/plain")==0 ){
526 style_header("Documentation");
@@ -537,17 +515,22 @@
537 #endif
538 }else{
539 cgi_set_content_type(zMime);
540 cgi_set_content(&filebody);
541 }
 
542 return;
543
544 doc_not_found:
545 /* Jump here when unable to locate the document */
 
546 db_end_transaction(0);
547 style_header("Document Not Found");
548 @ <p>No such document: %h(zName)</p>
 
 
 
 
549 style_footer();
550 return;
551 }
552
553 /*
554
--- src/doc.c
+++ src/doc.c
@@ -353,147 +353,125 @@
353 }
354 }
355
356 /*
357 ** WEBPAGE: doc
358 ** URL: /doc?name=CHECKIN/FILE
359 ** URL: /doc/CHECKIN/FILE
360 **
361 ** CHECKIN can be either tag or SHA1 hash or timestamp identifying a
362 ** particular check, or the name of a branch (meaning the most recent
363 ** check-in on that branch) or one of various magic words:
364 **
365 ** "tip" means the most recent check-in
366 **
367 ** "ckout" means the current check-out, if the server is run from
368 ** within a check-out, otherwise it is the same as "tip"
369 **
370 ** FILE is the name of a file to delivered up as a webpage. FILE is relative
371 ** to the root of the source tree of the repository. The FILE must
372 ** be a part of CHECKIN, except when CHECKIN=="ckout" when FILE is read
373 ** directly from disk and need not be a managed file.
374 **
375 ** The "ckout" CHECKIN is intended for development - to provide a mechanism
376 ** for looking at what a file will look like using the /doc webpage after
377 ** it gets checked in.
378 **
379 ** The file extension is used to decide how to render the file.
380 */
381 void doc_page(void){
382 const char *zName; /* Argument to the /doc page */
383 const char *zOrigName; /* Original document name */
384 const char *zMime; /* Document MIME type */
385 char *zCheckin; /* The checkin holding the document */
386 int vid = 0; /* Artifact of checkin */
387 int rid = 0; /* Artifact of file */
388 int i; /* Loop counter */
389 Blob filebody; /* Content of the documentation file */
390 int nMiss = 0; /* Failed attempts to find the document */
391
392 login_check_credentials();
393 if( !g.perm.Read ){ login_needed(); return; }
394 zName = PD("name", "tip/index.wiki");
395 for(i=0; zName[i] && zName[i]!='/'; i++){}
396 zCheckin = mprintf("%.*s", i, zName);
397 if( zName[i]==0 ){
398 zName = "index.wiki";
399 }else{
400 zName += i;
401 }
 
 
402 while( zName[0]=='/' ){ zName++; }
403 g.zPath = mprintf("%s/%s/%s", g.zPath, zCheckin, zName);
404 if( !file_is_simple_pathname(zName, 1) ){
405 if( sqlite3_strglob("*/", zName)==0 ){
406 zName = mprintf("%sindex.wiki", zName);
 
407 if( !file_is_simple_pathname(zName, 1) ){
408 goto doc_not_found;
409 }
410 }else{
411 goto doc_not_found;
412 }
413 }
414 zOrigName = zName;
415 if( fossil_strcmp(zCheckin,"ckout")==0 && db_open_local(0)==0 ){
416 sqlite3_snprintf(sizeof(zCheckin), zCheckin, "tip");
417 }
418 if( fossil_strcmp(zCheckin,"ckout")==0 ){
419 /* Read from the local checkout */
420 char *zFullpath;
421 db_must_be_within_tree();
422 while( rid==0 && nMiss<2 ){
423 zFullpath = mprintf("%s/%s", g.zLocalRoot, zName);
424 if( file_isfile(zFullpath)
425 && blob_read_from_file(&filebody, zFullpath)<0 ){
426 rid = 1; /* Fake RID just to get the loop to end */
427 }
428 fossil_free(zFullpath);
429 if( rid ) break;
430 nMiss++;
431 zName = "404.md";
432 }
433 }else{
434 db_begin_transaction();
435 vid = name_to_typed_rid(zCheckin, "ci");
 
 
 
 
 
 
 
436 db_multi_exec(
437 "CREATE TABLE IF NOT EXISTS vcache(\n"
438 " vid INTEGER, -- checkin ID\n"
439 " fname TEXT, -- filename\n"
440 " rid INTEGER, -- artifact ID\n"
441 " PRIMARY KEY(vid,fname)\n"
442 ") WITHOUT ROWID"
443 );
444 if( !db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){
445 db_multi_exec(
446 "DELETE FROM vcache;\n"
447 "CREATE VIRTUAL TABLE temp.foci USING files_of_checkin;\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
448 "INSERT INTO vcache(vid,fname,rid)"
449 " SELECT checkinID, filename, blob.rid FROM foci, blob"
450 " WHERE blob.uuid=foci.uuid"
451 " AND foci.checkinID=%d;",
452 vid
453 );
454 }
455 while( rid==0 && nMiss<2 ){
 
 
 
 
 
 
 
 
 
456 rid = db_int(0, "SELECT rid FROM vcache"
457 " WHERE vid=%d AND fname=%Q", vid, zName);
458 if( rid ) break;
459 nMiss++;
460 zName = "404.md";
461 }
462 if( rid==0 || content_get(rid, &filebody)==0 ){
 
 
463 goto doc_not_found;
464 }
465 db_end_transaction(0);
466 }
467 blob_to_utf8_no_bom(&filebody, 0);
468
469 /* The file is now contained in the filebody blob. Deliver the
470 ** file to the user
471 */
472 zMime = nMiss==0 ? P("mimetype") : 0;
473 if( zMime==0 ){
474 zMime = mimetype_from_name(zName);
475 }
476 Th_Store("doc_name", zName);
477 Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'"
@@ -516,11 +494,11 @@
494 Blob tail = BLOB_INITIALIZER;
495 markdown_to_html(&filebody, &title, &tail);
496 if( blob_size(&title)>0 ){
497 style_header("%s", blob_str(&title));
498 }else{
499 style_header("%s", nMiss?"Not Found":"Documentation");
500 }
501 blob_append(cgi_output_blob(), blob_buffer(&tail), blob_size(&tail));
502 style_footer();
503 }else if( fossil_strcmp(zMime, "text/plain")==0 ){
504 style_header("Documentation");
@@ -537,17 +515,22 @@
515 #endif
516 }else{
517 cgi_set_content_type(zMime);
518 cgi_set_content(&filebody);
519 }
520 if( nMiss ) cgi_set_status(404, "Not Found");
521 return;
522
 
523 /* Jump here when unable to locate the document */
524 doc_not_found:
525 db_end_transaction(0);
526 cgi_set_status(404, "Not Found");
527 style_header("Not Found");
528 @ <p>Document %h(zOrigName) not found
529 if( fossil_strcmp(zCheckin,"ckout")!=0 ){
530 @ in %z(href("%R/tree?ci=%T",zCheckin))%h(zCheckin)</a>
531 }
532 style_footer();
533 return;
534 }
535
536 /*
537

Keyboard Shortcuts

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