Fossil SCM
Added support for client-configurable mimetypes as a versionable setting.
Commit
322643cac8465e9e62295a8ffa375b378677b09c3a962b1556352d1152599d15
Parent
a6ee6add6348d61…
2 files changed
+5
+91
-1
M
src/db.c
+5
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -3449,10 +3449,15 @@ | ||
| 3449 | 3449 | */ |
| 3450 | 3450 | /* |
| 3451 | 3451 | ** SETTING: max-upload width=25 default=250000 |
| 3452 | 3452 | ** A limit on the size of uplink HTTP requests. |
| 3453 | 3453 | */ |
| 3454 | +/* | |
| 3455 | +** SETTING: mimetypes width=40 versionable block-text | |
| 3456 | +** A list of file extension-to-mimetype mappings, one per line. | |
| 3457 | +** Note that extensions are compared case-insensitively. | |
| 3458 | +*/ | |
| 3454 | 3459 | /* |
| 3455 | 3460 | ** SETTING: mtime-changes boolean default=on |
| 3456 | 3461 | ** Use file modification times (mtimes) to detect when |
| 3457 | 3462 | ** files have been modified. If disabled, all managed files |
| 3458 | 3463 | ** are hashed to detect changes, which can be slow for large |
| 3459 | 3464 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -3449,10 +3449,15 @@ | |
| 3449 | */ |
| 3450 | /* |
| 3451 | ** SETTING: max-upload width=25 default=250000 |
| 3452 | ** A limit on the size of uplink HTTP requests. |
| 3453 | */ |
| 3454 | /* |
| 3455 | ** SETTING: mtime-changes boolean default=on |
| 3456 | ** Use file modification times (mtimes) to detect when |
| 3457 | ** files have been modified. If disabled, all managed files |
| 3458 | ** are hashed to detect changes, which can be slow for large |
| 3459 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -3449,10 +3449,15 @@ | |
| 3449 | */ |
| 3450 | /* |
| 3451 | ** SETTING: max-upload width=25 default=250000 |
| 3452 | ** A limit on the size of uplink HTTP requests. |
| 3453 | */ |
| 3454 | /* |
| 3455 | ** SETTING: mimetypes width=40 versionable block-text |
| 3456 | ** A list of file extension-to-mimetype mappings, one per line. |
| 3457 | ** Note that extensions are compared case-insensitively. |
| 3458 | */ |
| 3459 | /* |
| 3460 | ** SETTING: mtime-changes boolean default=on |
| 3461 | ** Use file modification times (mtimes) to detect when |
| 3462 | ** files have been modified. If disabled, all managed files |
| 3463 | ** are hashed to detect changes, which can be slow for large |
| 3464 |
+91
-1
| --- src/doc.c | ||
| +++ src/doc.c | ||
| @@ -304,10 +304,83 @@ | ||
| 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 | +static const char *mimetype_from_name_custom(const char *zSuffix){ | |
| 317 | + static char * zList = 0; | |
| 318 | + static char const * zEnd = 0; | |
| 319 | + static int once = 0; | |
| 320 | + char * z; | |
| 321 | + int tokenizerState /* 0=expecting a key, 1=skip next token, | |
| 322 | + ** 2=accept next token */; | |
| 323 | + if(once==0){ | |
| 324 | + once = 1; | |
| 325 | + zList = db_get("mimetypes",0); | |
| 326 | + if(zList==0){ | |
| 327 | + return 0; | |
| 328 | + } | |
| 329 | + /* Initialize zList and transform it to simplify | |
| 330 | + the main loop. */ | |
| 331 | + zEnd = zList + strlen(zList); | |
| 332 | + for(z = zList; z<zEnd; ++z){ | |
| 333 | + if('\n'==*z) continue; | |
| 334 | + else if(fossil_isspace(*z)){ | |
| 335 | + *z = 0; | |
| 336 | + }else if(!(0x80 & *z)){ | |
| 337 | + *z = (char)fossil_tolower(*z); | |
| 338 | + } | |
| 339 | + } | |
| 340 | + }else if(zList==0){ | |
| 341 | + return 0; | |
| 342 | + } | |
| 343 | + tokenizerState = 0; | |
| 344 | + z = zList; | |
| 345 | + while( z<zEnd ){ | |
| 346 | + if(*z==0){ | |
| 347 | + ++z; | |
| 348 | + continue; | |
| 349 | + } | |
| 350 | + else if('\n'==*z){ | |
| 351 | + /* May happen on malformed inputs. Skip this record. */ | |
| 352 | + if(2==tokenizerState){ | |
| 353 | + /* We were expecting a value for a successful match | |
| 354 | + here, but got no value. Bail out. */ | |
| 355 | + break; | |
| 356 | + }else{ | |
| 357 | + tokenizerState = 0; | |
| 358 | + ++z; | |
| 359 | + continue; | |
| 360 | + } | |
| 361 | + } | |
| 362 | + switch(tokenizerState){ | |
| 363 | + case 0: /* This is a file extension */ | |
| 364 | + if(strcmp(z,zSuffix)==0){ | |
| 365 | + tokenizerState = 2 /*Match: accept the next value. */; | |
| 366 | + }else{ | |
| 367 | + tokenizerState = 1 /* No match: skip the next value */; | |
| 368 | + } | |
| 369 | + z += strlen(z); | |
| 370 | + break; | |
| 371 | + case 1: /* This is a value, but not a match. Skip it. */ | |
| 372 | + z += strlen(z); | |
| 373 | + break; | |
| 374 | + case 2: /* This is the value which matched the previous key */; | |
| 375 | + return z; | |
| 376 | + default: | |
| 377 | + assert(!"cannot happen - invalid tokenizerState value."); | |
| 378 | + } | |
| 379 | + } | |
| 380 | + return 0; | |
| 381 | +} | |
| 309 | 382 | |
| 310 | 383 | /* |
| 311 | 384 | ** Guess the mime-type of a document based on its name. |
| 312 | 385 | */ |
| 313 | 386 | const char *mimetype_from_name(const char *zName){ |
| @@ -333,10 +406,14 @@ | ||
| 333 | 406 | if( zName[i]=='.' ) z = &zName[i+1]; |
| 334 | 407 | } |
| 335 | 408 | len = strlen(z); |
| 336 | 409 | if( len<sizeof(zSuffix)-1 ){ |
| 337 | 410 | sqlite3_snprintf(sizeof(zSuffix), zSuffix, "%s", z); |
| 411 | + z = mimetype_from_name_custom(zSuffix); | |
| 412 | + if(z!=0){ | |
| 413 | + return z; | |
| 414 | + } | |
| 338 | 415 | for(i=0; zSuffix[i]; i++) zSuffix[i] = fossil_tolower(zSuffix[i]); |
| 339 | 416 | first = 0; |
| 340 | 417 | last = count(aMime) - 1; |
| 341 | 418 | while( first<=last ){ |
| 342 | 419 | int c; |
| @@ -365,10 +442,11 @@ | ||
| 365 | 442 | ** It should return "ok". |
| 366 | 443 | */ |
| 367 | 444 | void mimetype_test_cmd(void){ |
| 368 | 445 | int i; |
| 369 | 446 | mimetype_verify(); |
| 447 | + db_find_and_open_repository(0, 0); | |
| 370 | 448 | for(i=2; i<g.argc; i++){ |
| 371 | 449 | fossil_print("%-20s -> %s\n", g.argv[i], mimetype_from_name(g.argv[i])); |
| 372 | 450 | } |
| 373 | 451 | } |
| 374 | 452 | |
| @@ -378,10 +456,11 @@ | ||
| 378 | 456 | ** Show the built-in table used to guess embedded document mimetypes |
| 379 | 457 | ** from file suffixes. |
| 380 | 458 | */ |
| 381 | 459 | void mimetype_list_page(void){ |
| 382 | 460 | int i; |
| 461 | + char * zCustomList = 0; | |
| 383 | 462 | mimetype_verify(); |
| 384 | 463 | style_header("Mimetype List"); |
| 385 | 464 | @ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename |
| 386 | 465 | @ suffixes and the following table to guess at the appropriate mimetype |
| 387 | 466 | @ for each document.</p> |
| @@ -393,11 +472,22 @@ | ||
| 393 | 472 | @ <tbody> |
| 394 | 473 | for(i=0; i<count(aMime); i++){ |
| 395 | 474 | @ <tr><td>%h(aMime[i].zSuffix)<td>%h(aMime[i].zMimetype)</tr> |
| 396 | 475 | } |
| 397 | 476 | @ </tbody></table> |
| 398 | - style_table_sorter(); | |
| 477 | + zCustomList = db_get("mimetypes",0); | |
| 478 | + if(zCustomList!=0){ | |
| 479 | + /* TODO: render this as a table, rather than a TEXTAREA. That | |
| 480 | + ** requires tokenizing the input, though, duplicating much of the | |
| 481 | + ** work done in mimetype_from_name_custom(). | |
| 482 | + */ | |
| 483 | + @ <h1>Repo-specific mimetypes</h1> | |
| 484 | + @ The following extention-to-mimetype mappings are defined via the | |
| 485 | + @ <a href="%R/help?cmd=mimetypes">mimetypes setting</a>:<br> | |
| 486 | + @ <textarea rows='10' cols='40' readonly>%s(zCustomList)</textarea> | |
| 487 | + fossil_free(zCustomList); | |
| 488 | + } | |
| 399 | 489 | style_footer(); |
| 400 | 490 | } |
| 401 | 491 | |
| 402 | 492 | /* |
| 403 | 493 | ** Check to see if the file in the pContent blob is "embedded HTML". Return |
| 404 | 494 |
| --- src/doc.c | |
| +++ src/doc.c | |
| @@ -304,10 +304,83 @@ | |
| 304 | fossil_panic("mimetypes out of sequence: %s before %s", |
| 305 | aMime[i-1].zSuffix, aMime[i].zSuffix); |
| 306 | } |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | /* |
| 311 | ** Guess the mime-type of a document based on its name. |
| 312 | */ |
| 313 | const char *mimetype_from_name(const char *zName){ |
| @@ -333,10 +406,14 @@ | |
| 333 | if( zName[i]=='.' ) z = &zName[i+1]; |
| 334 | } |
| 335 | len = strlen(z); |
| 336 | if( len<sizeof(zSuffix)-1 ){ |
| 337 | sqlite3_snprintf(sizeof(zSuffix), zSuffix, "%s", z); |
| 338 | for(i=0; zSuffix[i]; i++) zSuffix[i] = fossil_tolower(zSuffix[i]); |
| 339 | first = 0; |
| 340 | last = count(aMime) - 1; |
| 341 | while( first<=last ){ |
| 342 | int c; |
| @@ -365,10 +442,11 @@ | |
| 365 | ** It should return "ok". |
| 366 | */ |
| 367 | void mimetype_test_cmd(void){ |
| 368 | int i; |
| 369 | mimetype_verify(); |
| 370 | for(i=2; i<g.argc; i++){ |
| 371 | fossil_print("%-20s -> %s\n", g.argv[i], mimetype_from_name(g.argv[i])); |
| 372 | } |
| 373 | } |
| 374 | |
| @@ -378,10 +456,11 @@ | |
| 378 | ** Show the built-in table used to guess embedded document mimetypes |
| 379 | ** from file suffixes. |
| 380 | */ |
| 381 | void mimetype_list_page(void){ |
| 382 | int i; |
| 383 | mimetype_verify(); |
| 384 | style_header("Mimetype List"); |
| 385 | @ <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> |
| @@ -393,11 +472,22 @@ | |
| 393 | @ <tbody> |
| 394 | for(i=0; i<count(aMime); i++){ |
| 395 | @ <tr><td>%h(aMime[i].zSuffix)<td>%h(aMime[i].zMimetype)</tr> |
| 396 | } |
| 397 | @ </tbody></table> |
| 398 | style_table_sorter(); |
| 399 | style_footer(); |
| 400 | } |
| 401 | |
| 402 | /* |
| 403 | ** Check to see if the file in the pContent blob is "embedded HTML". Return |
| 404 |
| --- src/doc.c | |
| +++ src/doc.c | |
| @@ -304,10 +304,83 @@ | |
| 304 | fossil_panic("mimetypes out of sequence: %s before %s", |
| 305 | aMime[i-1].zSuffix, aMime[i].zSuffix); |
| 306 | } |
| 307 | } |
| 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 | static const char *mimetype_from_name_custom(const char *zSuffix){ |
| 317 | static char * zList = 0; |
| 318 | static char const * zEnd = 0; |
| 319 | static int once = 0; |
| 320 | char * z; |
| 321 | int tokenizerState /* 0=expecting a key, 1=skip next token, |
| 322 | ** 2=accept next token */; |
| 323 | if(once==0){ |
| 324 | once = 1; |
| 325 | zList = db_get("mimetypes",0); |
| 326 | if(zList==0){ |
| 327 | return 0; |
| 328 | } |
| 329 | /* Initialize zList and transform it to simplify |
| 330 | the main loop. */ |
| 331 | zEnd = zList + strlen(zList); |
| 332 | for(z = zList; z<zEnd; ++z){ |
| 333 | if('\n'==*z) continue; |
| 334 | else if(fossil_isspace(*z)){ |
| 335 | *z = 0; |
| 336 | }else if(!(0x80 & *z)){ |
| 337 | *z = (char)fossil_tolower(*z); |
| 338 | } |
| 339 | } |
| 340 | }else if(zList==0){ |
| 341 | return 0; |
| 342 | } |
| 343 | tokenizerState = 0; |
| 344 | z = zList; |
| 345 | while( z<zEnd ){ |
| 346 | if(*z==0){ |
| 347 | ++z; |
| 348 | continue; |
| 349 | } |
| 350 | else if('\n'==*z){ |
| 351 | /* May happen on malformed inputs. Skip this record. */ |
| 352 | if(2==tokenizerState){ |
| 353 | /* We were expecting a value for a successful match |
| 354 | here, but got no value. Bail out. */ |
| 355 | break; |
| 356 | }else{ |
| 357 | tokenizerState = 0; |
| 358 | ++z; |
| 359 | continue; |
| 360 | } |
| 361 | } |
| 362 | switch(tokenizerState){ |
| 363 | case 0: /* This is a file extension */ |
| 364 | if(strcmp(z,zSuffix)==0){ |
| 365 | tokenizerState = 2 /*Match: accept the next value. */; |
| 366 | }else{ |
| 367 | tokenizerState = 1 /* No match: skip the next value */; |
| 368 | } |
| 369 | z += strlen(z); |
| 370 | break; |
| 371 | case 1: /* This is a value, but not a match. Skip it. */ |
| 372 | z += strlen(z); |
| 373 | break; |
| 374 | case 2: /* This is the value which matched the previous key */; |
| 375 | return z; |
| 376 | default: |
| 377 | assert(!"cannot happen - invalid tokenizerState value."); |
| 378 | } |
| 379 | } |
| 380 | return 0; |
| 381 | } |
| 382 | |
| 383 | /* |
| 384 | ** Guess the mime-type of a document based on its name. |
| 385 | */ |
| 386 | const char *mimetype_from_name(const char *zName){ |
| @@ -333,10 +406,14 @@ | |
| 406 | if( zName[i]=='.' ) z = &zName[i+1]; |
| 407 | } |
| 408 | len = strlen(z); |
| 409 | if( len<sizeof(zSuffix)-1 ){ |
| 410 | sqlite3_snprintf(sizeof(zSuffix), zSuffix, "%s", z); |
| 411 | z = mimetype_from_name_custom(zSuffix); |
| 412 | if(z!=0){ |
| 413 | return z; |
| 414 | } |
| 415 | for(i=0; zSuffix[i]; i++) zSuffix[i] = fossil_tolower(zSuffix[i]); |
| 416 | first = 0; |
| 417 | last = count(aMime) - 1; |
| 418 | while( first<=last ){ |
| 419 | int c; |
| @@ -365,10 +442,11 @@ | |
| 442 | ** It should return "ok". |
| 443 | */ |
| 444 | void mimetype_test_cmd(void){ |
| 445 | int i; |
| 446 | mimetype_verify(); |
| 447 | db_find_and_open_repository(0, 0); |
| 448 | for(i=2; i<g.argc; i++){ |
| 449 | fossil_print("%-20s -> %s\n", g.argv[i], mimetype_from_name(g.argv[i])); |
| 450 | } |
| 451 | } |
| 452 | |
| @@ -378,10 +456,11 @@ | |
| 456 | ** Show the built-in table used to guess embedded document mimetypes |
| 457 | ** from file suffixes. |
| 458 | */ |
| 459 | void mimetype_list_page(void){ |
| 460 | int i; |
| 461 | char * zCustomList = 0; |
| 462 | mimetype_verify(); |
| 463 | style_header("Mimetype List"); |
| 464 | @ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename |
| 465 | @ suffixes and the following table to guess at the appropriate mimetype |
| 466 | @ for each document.</p> |
| @@ -393,11 +472,22 @@ | |
| 472 | @ <tbody> |
| 473 | for(i=0; i<count(aMime); i++){ |
| 474 | @ <tr><td>%h(aMime[i].zSuffix)<td>%h(aMime[i].zMimetype)</tr> |
| 475 | } |
| 476 | @ </tbody></table> |
| 477 | zCustomList = db_get("mimetypes",0); |
| 478 | if(zCustomList!=0){ |
| 479 | /* TODO: render this as a table, rather than a TEXTAREA. That |
| 480 | ** requires tokenizing the input, though, duplicating much of the |
| 481 | ** work done in mimetype_from_name_custom(). |
| 482 | */ |
| 483 | @ <h1>Repo-specific mimetypes</h1> |
| 484 | @ The following extention-to-mimetype mappings are defined via the |
| 485 | @ <a href="%R/help?cmd=mimetypes">mimetypes setting</a>:<br> |
| 486 | @ <textarea rows='10' cols='40' readonly>%s(zCustomList)</textarea> |
| 487 | fossil_free(zCustomList); |
| 488 | } |
| 489 | style_footer(); |
| 490 | } |
| 491 | |
| 492 | /* |
| 493 | ** Check to see if the file in the pContent blob is "embedded HTML". Return |
| 494 |