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.
Commit
cfcd9b87dcf54a16f206cbeebf7c443b5023532d
Parent
1dbd4d0d0bd92e6…
1 file changed
+76
-93
+76
-93
| --- src/doc.c | ||
| +++ src/doc.c | ||
| @@ -353,147 +353,125 @@ | ||
| 353 | 353 | } |
| 354 | 354 | } |
| 355 | 355 | |
| 356 | 356 | /* |
| 357 | 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. | |
| 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. | |
| 369 | 380 | */ |
| 370 | 381 | void doc_page(void){ |
| 371 | 382 | const char *zName; /* Argument to the /doc page */ |
| 383 | + const char *zOrigName; /* Original document name */ | |
| 372 | 384 | 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 */ | |
| 374 | 387 | int rid = 0; /* Artifact of file */ |
| 375 | 388 | int i; /* Loop counter */ |
| 376 | 389 | 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 */ | |
| 378 | 391 | |
| 379 | 392 | login_check_credentials(); |
| 380 | 393 | if( !g.perm.Read ){ login_needed(); return; } |
| 381 | 394 | zName = PD("name", "tip/index.wiki"); |
| 382 | 395 | 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 | + } | |
| 391 | 402 | while( zName[0]=='/' ){ zName++; } |
| 403 | + g.zPath = mprintf("%s/%s/%s", g.zPath, zCheckin, zName); | |
| 392 | 404 | 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); | |
| 396 | 407 | if( !file_is_simple_pathname(zName, 1) ){ |
| 397 | 408 | goto doc_not_found; |
| 398 | 409 | } |
| 399 | 410 | }else{ |
| 400 | 411 | goto doc_not_found; |
| 401 | 412 | } |
| 402 | 413 | } |
| 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"); | |
| 405 | 417 | } |
| 406 | - if( fossil_strcmp(zBaseline,"ckout")==0 ){ | |
| 418 | + if( fossil_strcmp(zCheckin,"ckout")==0 ){ | |
| 407 | 419 | /* Read from the local checkout */ |
| 408 | 420 | char *zFullpath; |
| 409 | 421 | 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"; | |
| 416 | 432 | } |
| 417 | 433 | }else{ |
| 418 | 434 | 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"); | |
| 427 | 436 | db_multi_exec( |
| 428 | 437 | "CREATE TABLE IF NOT EXISTS vcache(\n" |
| 429 | - " vid INTEGER, -- baseline ID\n" | |
| 438 | + " vid INTEGER, -- checkin ID\n" | |
| 430 | 439 | " fname TEXT, -- filename\n" |
| 431 | 440 | " rid INTEGER, -- artifact ID\n" |
| 432 | - " UNIQUE(vid,fname,rid)\n" | |
| 433 | - ")" | |
| 441 | + " PRIMARY KEY(vid,fname)\n" | |
| 442 | + ") WITHOUT ROWID" | |
| 434 | 443 | ); |
| 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" | |
| 460 | 448 | "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;", | |
| 463 | 452 | vid |
| 464 | 453 | ); |
| 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 ){ | |
| 476 | 456 | rid = db_int(0, "SELECT rid FROM vcache" |
| 477 | 457 | " 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"; | |
| 481 | 461 | } |
| 482 | - | |
| 483 | - /* Get the file content */ | |
| 484 | - if( content_get(rid, &filebody)==0 ){ | |
| 462 | + if( rid==0 || content_get(rid, &filebody)==0 ){ | |
| 485 | 463 | goto doc_not_found; |
| 486 | 464 | } |
| 487 | 465 | db_end_transaction(0); |
| 488 | 466 | } |
| 489 | 467 | blob_to_utf8_no_bom(&filebody, 0); |
| 490 | 468 | |
| 491 | 469 | /* The file is now contained in the filebody blob. Deliver the |
| 492 | 470 | ** file to the user |
| 493 | 471 | */ |
| 494 | - zMime = P("mimetype"); | |
| 472 | + zMime = nMiss==0 ? P("mimetype") : 0; | |
| 495 | 473 | if( zMime==0 ){ |
| 496 | 474 | zMime = mimetype_from_name(zName); |
| 497 | 475 | } |
| 498 | 476 | Th_Store("doc_name", zName); |
| 499 | 477 | Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'" |
| @@ -516,11 +494,11 @@ | ||
| 516 | 494 | Blob tail = BLOB_INITIALIZER; |
| 517 | 495 | markdown_to_html(&filebody, &title, &tail); |
| 518 | 496 | if( blob_size(&title)>0 ){ |
| 519 | 497 | style_header("%s", blob_str(&title)); |
| 520 | 498 | }else{ |
| 521 | - style_header("Documentation"); | |
| 499 | + style_header("%s", nMiss?"Not Found":"Documentation"); | |
| 522 | 500 | } |
| 523 | 501 | blob_append(cgi_output_blob(), blob_buffer(&tail), blob_size(&tail)); |
| 524 | 502 | style_footer(); |
| 525 | 503 | }else if( fossil_strcmp(zMime, "text/plain")==0 ){ |
| 526 | 504 | style_header("Documentation"); |
| @@ -537,17 +515,22 @@ | ||
| 537 | 515 | #endif |
| 538 | 516 | }else{ |
| 539 | 517 | cgi_set_content_type(zMime); |
| 540 | 518 | cgi_set_content(&filebody); |
| 541 | 519 | } |
| 520 | + if( nMiss ) cgi_set_status(404, "Not Found"); | |
| 542 | 521 | return; |
| 543 | 522 | |
| 544 | -doc_not_found: | |
| 545 | 523 | /* Jump here when unable to locate the document */ |
| 524 | +doc_not_found: | |
| 546 | 525 | 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 | + } | |
| 549 | 532 | style_footer(); |
| 550 | 533 | return; |
| 551 | 534 | } |
| 552 | 535 | |
| 553 | 536 | /* |
| 554 | 537 |
| --- 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 |