| | @@ -304,10 +304,103 @@ |
| 304 | 304 | fossil_panic("mimetypes out of sequence: %s before %s", |
| 305 | 305 | aMime[i-1].zSuffix, aMime[i].zSuffix); |
| 306 | 306 | } |
| 307 | 307 | } |
| 308 | 308 | } |
| 309 | + |
| 310 | +/* |
| 311 | +** Looks in the contents of the "mimetypes" setting for a suffix |
| 312 | +** matching zSuffix. If found, it returns the configured value |
| 313 | +** in memory owned by the app (i.e. do not free() it), else it |
| 314 | +** returns 0. |
| 315 | +** |
| 316 | +** The mimetypes setting is expected to be a list of file extensions |
| 317 | +** and mimetypes, with one such mapping per line. A leading '.' on |
| 318 | +** extensions is permitted for compatibility with lists imported from |
| 319 | +** other tools which require them. |
| 320 | +*/ |
| 321 | +static const char *mimetype_from_name_custom(const char *zSuffix){ |
| 322 | + static char * zList = 0; |
| 323 | + static char const * zEnd = 0; |
| 324 | + static int once = 0; |
| 325 | + char * z; |
| 326 | + int tokenizerState /* 0=expecting a key, 1=skip next token, |
| 327 | + ** 2=accept next token */; |
| 328 | + if(once==0){ |
| 329 | + once = 1; |
| 330 | + zList = db_get("mimetypes",0); |
| 331 | + if(zList==0){ |
| 332 | + return 0; |
| 333 | + } |
| 334 | + /* Transform zList to simplify the main loop: |
| 335 | + replace non-newline spaces with NUL bytes. */ |
| 336 | + zEnd = zList + strlen(zList); |
| 337 | + for(z = zList; z<zEnd; ++z){ |
| 338 | + if('\n'==*z) continue; |
| 339 | + else if(fossil_isspace(*z)){ |
| 340 | + *z = 0; |
| 341 | + } |
| 342 | + } |
| 343 | + }else if(zList==0){ |
| 344 | + return 0; |
| 345 | + } |
| 346 | + tokenizerState = 0; |
| 347 | + z = zList; |
| 348 | + while( z<zEnd ){ |
| 349 | + if(*z==0){ |
| 350 | + ++z; |
| 351 | + continue; |
| 352 | + } |
| 353 | + else if('\n'==*z){ |
| 354 | + if(2==tokenizerState){ |
| 355 | + /* We were expecting a value for a successful match |
| 356 | + here, but got no value. Bail out. */ |
| 357 | + break; |
| 358 | + }else{ |
| 359 | + /* May happen on malformed inputs. Skip this record. */ |
| 360 | + tokenizerState = 0; |
| 361 | + ++z; |
| 362 | + continue; |
| 363 | + } |
| 364 | + } |
| 365 | + switch(tokenizerState){ |
| 366 | + case 0:{ /* This is a file extension */ |
| 367 | + static char * zCase = 0; |
| 368 | + if('.'==*z){ |
| 369 | + /*ignore an optional leading dot, for compatibility |
| 370 | + with some external mimetype lists*/; |
| 371 | + if(++z==zEnd){ |
| 372 | + break; |
| 373 | + } |
| 374 | + } |
| 375 | + if(zCase<z){ |
| 376 | + /*we have not yet case-folded this section: lower-case it*/ |
| 377 | + for(zCase = z; zCase<zEnd && *zCase!=0; ++zCase){ |
| 378 | + if(!(0x80 & *zCase)){ |
| 379 | + *zCase = (char)fossil_tolower(*zCase); |
| 380 | + } |
| 381 | + } |
| 382 | + } |
| 383 | + if(strcmp(z,zSuffix)==0){ |
| 384 | + tokenizerState = 2 /* Match: accept the next value. */; |
| 385 | + }else{ |
| 386 | + tokenizerState = 1 /* No match: skip the next value. */; |
| 387 | + } |
| 388 | + z += strlen(z); |
| 389 | + break; |
| 390 | + } |
| 391 | + case 1: /* This is a value, but not a match. Skip it. */ |
| 392 | + z += strlen(z); |
| 393 | + break; |
| 394 | + case 2: /* This is the value which matched the previous key. */; |
| 395 | + return z; |
| 396 | + default: |
| 397 | + assert(!"cannot happen - invalid tokenizerState value."); |
| 398 | + } |
| 399 | + } |
| 400 | + return 0; |
| 401 | +} |
| 309 | 402 | |
| 310 | 403 | /* |
| 311 | 404 | ** Guess the mime-type of a document based on its name. |
| 312 | 405 | */ |
| 313 | 406 | const char *mimetype_from_name(const char *zName){ |
| | @@ -334,10 +427,14 @@ |
| 334 | 427 | } |
| 335 | 428 | len = strlen(z); |
| 336 | 429 | if( len<sizeof(zSuffix)-1 ){ |
| 337 | 430 | sqlite3_snprintf(sizeof(zSuffix), zSuffix, "%s", z); |
| 338 | 431 | for(i=0; zSuffix[i]; i++) zSuffix[i] = fossil_tolower(zSuffix[i]); |
| 432 | + z = mimetype_from_name_custom(zSuffix); |
| 433 | + if(z!=0){ |
| 434 | + return z; |
| 435 | + } |
| 339 | 436 | first = 0; |
| 340 | 437 | last = count(aMime) - 1; |
| 341 | 438 | while( first<=last ){ |
| 342 | 439 | int c; |
| 343 | 440 | i = (first+last)/2; |
| | @@ -365,10 +462,11 @@ |
| 365 | 462 | ** It should return "ok". |
| 366 | 463 | */ |
| 367 | 464 | void mimetype_test_cmd(void){ |
| 368 | 465 | int i; |
| 369 | 466 | mimetype_verify(); |
| 467 | + db_find_and_open_repository(0, 0); |
| 370 | 468 | for(i=2; i<g.argc; i++){ |
| 371 | 469 | fossil_print("%-20s -> %s\n", g.argv[i], mimetype_from_name(g.argv[i])); |
| 372 | 470 | } |
| 373 | 471 | } |
| 374 | 472 | |
| | @@ -378,23 +476,67 @@ |
| 378 | 476 | ** Show the built-in table used to guess embedded document mimetypes |
| 379 | 477 | ** from file suffixes. |
| 380 | 478 | */ |
| 381 | 479 | void mimetype_list_page(void){ |
| 382 | 480 | int i; |
| 481 | + char *zCustomList = 0; /* value of the mimetypes setting */ |
| 482 | + int nCustomEntries = 0; /* number of entries in the mimetypes |
| 483 | + ** setting */ |
| 383 | 484 | mimetype_verify(); |
| 384 | 485 | style_header("Mimetype List"); |
| 385 | 486 | @ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename |
| 386 | | - @ suffixes and the following table to guess at the appropriate mimetype |
| 387 | | - @ for each document.</p> |
| 487 | + @ suffixes and the following tables to guess at the appropriate mimetype |
| 488 | + @ for each document. Mimetypes may be customized and overridden using |
| 489 | + @ <a href="%R/help?cmd=mimetypes">the mimetypes config setting</a>.</p> |
| 490 | + zCustomList = db_get("mimetypes",0); |
| 491 | + if( zCustomList!=0 ){ |
| 492 | + Blob list, entry, key, val; |
| 493 | + @ <h1>Repository-specific mimetypes</h1> |
| 494 | + @ <p>The following extension-to-mimetype mappings are defined via |
| 495 | + @ the <a href="%R/help?cmd=mimetypes">mimetypes setting</a>.</p> |
| 496 | + @ <table class='sortable mimetypetable' border=1 cellpadding=0 \ |
| 497 | + @ data-column-types='tt' data-init-sort='0'> |
| 498 | + @ <thead> |
| 499 | + @ <tr><th>Suffix<th>Mimetype |
| 500 | + @ </thead> |
| 501 | + @ <tbody> |
| 502 | + blob_set(&list, zCustomList); |
| 503 | + while( blob_line(&list, &entry)>0 ){ |
| 504 | + const char *zKey; |
| 505 | + if( blob_token(&entry, &key)==0 ) continue; |
| 506 | + if( blob_token(&entry, &val)==0 ) continue; |
| 507 | + zKey = blob_str(&key); |
| 508 | + if( zKey[0]=='.' ) zKey++; |
| 509 | + @ <tr><td>%h(zKey)<td>%h(blob_str(&val))</tr> |
| 510 | + nCustomEntries++; |
| 511 | + } |
| 512 | + fossil_free(zCustomList); |
| 513 | + if( nCustomEntries==0 ){ |
| 514 | + /* This can happen if the option is set to an empty/space-only |
| 515 | + ** value. */ |
| 516 | + @ <tr><td colspan="2"><em>none</em></tr> |
| 517 | + } |
| 518 | + @ </tbody></table> |
| 519 | + } |
| 520 | + @ <h1>Default built-in mimetypes</h1> |
| 521 | + if(nCustomEntries>0){ |
| 522 | + @ <p>Entries starting with an exclamation mark <em><strong>!</strong></em> |
| 523 | + @ are overwritten by repository-specific settings.</p> |
| 524 | + } |
| 388 | 525 | @ <table class='sortable mimetypetable' border=1 cellpadding=0 \ |
| 389 | 526 | @ data-column-types='tt' data-init-sort='1'> |
| 390 | 527 | @ <thead> |
| 391 | 528 | @ <tr><th>Suffix<th>Mimetype |
| 392 | 529 | @ </thead> |
| 393 | 530 | @ <tbody> |
| 394 | 531 | for(i=0; i<count(aMime); i++){ |
| 395 | | - @ <tr><td>%h(aMime[i].zSuffix)<td>%h(aMime[i].zMimetype)</tr> |
| 532 | + const char *zFlag = ""; |
| 533 | + if(nCustomEntries>0 && |
| 534 | + mimetype_from_name_custom(aMime[i].zSuffix)!=0){ |
| 535 | + zFlag = "<em><strong>!</strong></em> "; |
| 536 | + } |
| 537 | + @ <tr><td>%s(zFlag)%h(aMime[i].zSuffix)<td>%h(aMime[i].zMimetype)</tr> |
| 396 | 538 | } |
| 397 | 539 | @ </tbody></table> |
| 398 | 540 | style_table_sorter(); |
| 399 | 541 | style_footer(); |
| 400 | 542 | } |
| 401 | 543 | |