Fossil SCM
Put all javascript inline using a nonce. Disallow 'unsafe-inline' CSP for javascript.
Commit
89c40851f08a77e67bb6d1ca5e673783fc1d8935a6858db757d8f4dd2057842f
Parent
6022ad49c483f5e…
2 files changed
+1
+48
-6
+1
| --- src/codecheck1.c | ||
| +++ src/codecheck1.c | ||
| @@ -398,10 +398,11 @@ | ||
| 398 | 398 | { "json_warn", 2, 0 }, |
| 399 | 399 | { "mprintf", 1, 0 }, |
| 400 | 400 | { "socket_set_errmsg", 1, 0 }, |
| 401 | 401 | { "ssl_set_errmsg", 1, 0 }, |
| 402 | 402 | { "style_header", 1, FMT_HTML }, |
| 403 | + { "style_js_onload", 1, FMT_HTML }, | |
| 403 | 404 | { "style_set_current_page", 1, FMT_URL }, |
| 404 | 405 | { "style_submenu_element", 2, FMT_URL }, |
| 405 | 406 | { "style_submenu_sql", 3, FMT_SQL }, |
| 406 | 407 | { "webpage_error", 1, FMT_SAFE }, |
| 407 | 408 | { "xhref", 2, FMT_URL }, |
| 408 | 409 |
| --- src/codecheck1.c | |
| +++ src/codecheck1.c | |
| @@ -398,10 +398,11 @@ | |
| 398 | { "json_warn", 2, 0 }, |
| 399 | { "mprintf", 1, 0 }, |
| 400 | { "socket_set_errmsg", 1, 0 }, |
| 401 | { "ssl_set_errmsg", 1, 0 }, |
| 402 | { "style_header", 1, FMT_HTML }, |
| 403 | { "style_set_current_page", 1, FMT_URL }, |
| 404 | { "style_submenu_element", 2, FMT_URL }, |
| 405 | { "style_submenu_sql", 3, FMT_SQL }, |
| 406 | { "webpage_error", 1, FMT_SAFE }, |
| 407 | { "xhref", 2, FMT_URL }, |
| 408 |
| --- src/codecheck1.c | |
| +++ src/codecheck1.c | |
| @@ -398,10 +398,11 @@ | |
| 398 | { "json_warn", 2, 0 }, |
| 399 | { "mprintf", 1, 0 }, |
| 400 | { "socket_set_errmsg", 1, 0 }, |
| 401 | { "ssl_set_errmsg", 1, 0 }, |
| 402 | { "style_header", 1, FMT_HTML }, |
| 403 | { "style_js_onload", 1, FMT_HTML }, |
| 404 | { "style_set_current_page", 1, FMT_URL }, |
| 405 | { "style_submenu_element", 2, FMT_URL }, |
| 406 | { "style_submenu_sql", 3, FMT_SQL }, |
| 407 | { "webpage_error", 1, FMT_SAFE }, |
| 408 | { "xhref", 2, FMT_URL }, |
| 409 |
+48
-6
| --- src/style.c | ||
| +++ src/style.c | ||
| @@ -86,10 +86,16 @@ | ||
| 86 | 86 | */ |
| 87 | 87 | static int needHrefJs = 0; /* href.js */ |
| 88 | 88 | static int needSortJs = 0; /* sorttable.js */ |
| 89 | 89 | static int needGraphJs = 0; /* graph.js */ |
| 90 | 90 | |
| 91 | +/* | |
| 92 | +** Extra JS added to the end of the file. | |
| 93 | +*/ | |
| 94 | +static Blob blobJs = BLOB_INITIALIZER; | |
| 95 | +static Blob blobOnLoad = BLOB_INITIALIZER; | |
| 96 | + | |
| 91 | 97 | /* |
| 92 | 98 | ** Generate and return a anchor tag like this: |
| 93 | 99 | ** |
| 94 | 100 | ** <a href="URL"> |
| 95 | 101 | ** or <a id="ID"> |
| @@ -360,10 +366,24 @@ | ||
| 360 | 366 | char *zConfigName = mprintf("%s-image", zImageName); |
| 361 | 367 | url_var(zVarPrefix, zConfigName, zImageName); |
| 362 | 368 | free(zVarPrefix); |
| 363 | 369 | free(zConfigName); |
| 364 | 370 | } |
| 371 | + | |
| 372 | +/* | |
| 373 | +** Return a random nonce that is stored in static space. For a particular | |
| 374 | +** run, the same nonce is always returned. | |
| 375 | +*/ | |
| 376 | +char *style_nonce(void){ | |
| 377 | + static char zNonce[52]; | |
| 378 | + if( zNonce[0]==0 ){ | |
| 379 | + unsigned char zSeed[24]; | |
| 380 | + sqlite3_randomness(24, zSeed); | |
| 381 | + encode16(zSeed,(unsigned char*)zNonce,24); | |
| 382 | + } | |
| 383 | + return zNonce; | |
| 384 | +} | |
| 365 | 385 | |
| 366 | 386 | /* |
| 367 | 387 | ** Default HTML page header text through <body>. If the repository-specific |
| 368 | 388 | ** header template lacks a <body> tag, then all of the following is |
| 369 | 389 | ** prepended. |
| @@ -371,11 +391,13 @@ | ||
| 371 | 391 | static char zDfltHeader[] = |
| 372 | 392 | @ <html> |
| 373 | 393 | @ <head> |
| 374 | 394 | @ <base href="$baseurl/$current_page" /> |
| 375 | 395 | @ <meta http-equiv="Content-Security-Policy" \ |
| 376 | -@ content="default-src 'self' data: 'unsafe-inline'" /> | |
| 396 | +@ content="default-src 'self' data: ; \ | |
| 397 | +@ script-src 'self' 'nonce-$<nonce>' ;\ | |
| 398 | +@ style-src 'self' 'unsafe-inline'" /> | |
| 377 | 399 | @ <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 378 | 400 | @ <title>$<project_name>: $<title></title> |
| 379 | 401 | @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" \ |
| 380 | 402 | @ href="$home/timeline.rss" /> |
| 381 | 403 | @ <link rel="stylesheet" href="$stylesheet_url" type="text/css" \ |
| @@ -402,10 +424,11 @@ | ||
| 402 | 424 | @ <!DOCTYPE html> |
| 403 | 425 | |
| 404 | 426 | if( g.thTrace ) Th_Trace("BEGIN_HEADER<br />\n", -1); |
| 405 | 427 | |
| 406 | 428 | /* Generate the header up through the main menu */ |
| 429 | + Th_Store("nonce", style_nonce()); | |
| 407 | 430 | Th_Store("project_name", db_get("project-name","Unnamed Fossil Project")); |
| 408 | 431 | Th_Store("project_description", db_get("project-description","")); |
| 409 | 432 | Th_Store("title", zTitle); |
| 410 | 433 | Th_Store("baseurl", g.zBaseURL); |
| 411 | 434 | Th_Store("secureurl", login_wants_https_redirect()? g.zHttpsURL: g.zBaseURL); |
| @@ -523,11 +546,11 @@ | ||
| 523 | 546 | int i; |
| 524 | 547 | for(i=0; i<nJsToLoad; i++){ |
| 525 | 548 | if( fossil_strcmp(zName, azJsToLoad[i])==0 ) return; |
| 526 | 549 | } |
| 527 | 550 | if( nJsToLoad>=sizeof(azJsToLoad)/sizeof(azJsToLoad[0]) ){ |
| 528 | - fossil_panic("too man JS files"); | |
| 551 | + fossil_panic("too many JS files"); | |
| 529 | 552 | } |
| 530 | 553 | azJsToLoad[nJsToLoad++] = zName; |
| 531 | 554 | } |
| 532 | 555 | |
| 533 | 556 | /* |
| @@ -541,21 +564,40 @@ | ||
| 541 | 564 | /* Load up the page data */ |
| 542 | 565 | bMouseover = (!g.isHuman || db_get_boolean("auto-hyperlink-ishuman",0)) |
| 543 | 566 | && db_get_boolean("auto-hyperlink-mouseover",0); |
| 544 | 567 | @ <script id='href-data' type='application/json'>\ |
| 545 | 568 | @ {"delay":%d(nDelay),"mouseover":%d(bMouseover)}</script> |
| 546 | - style_load_one_js_file("href.js"); | |
| 569 | + } | |
| 570 | + @ <script nonce="%h(style_nonce())"> | |
| 571 | + if( needHrefJs ){ | |
| 572 | + cgi_append_content(builtin_text("href.js"),-1); | |
| 547 | 573 | } |
| 548 | 574 | if( needSortJs ){ |
| 549 | - style_load_one_js_file("sorttable.js"); | |
| 575 | + cgi_append_content(builtin_text("sorttable.js"),-1); | |
| 550 | 576 | } |
| 551 | 577 | if( needGraphJs ){ |
| 552 | - style_load_one_js_file("graph.js"); | |
| 578 | + cgi_append_content(builtin_text("graph.js"),-1); | |
| 553 | 579 | } |
| 554 | 580 | for(i=0; i<nJsToLoad; i++){ |
| 555 | - style_load_one_js_file(azJsToLoad[i]); | |
| 581 | + cgi_append_content(builtin_text(azJsToLoad[i]),-1); | |
| 582 | + } | |
| 583 | + if( blob_size(&blobOnLoad)>0 ){ | |
| 584 | + @ window.onload = function(){ | |
| 585 | + cgi_append_content(blob_buffer(&blobOnLoad), blob_size(&blobOnLoad)); | |
| 586 | + cgi_append_content("\n}\n", -1); | |
| 556 | 587 | } |
| 588 | + @ </script> | |
| 589 | +} | |
| 590 | + | |
| 591 | +/* | |
| 592 | +** Extra JS to run after all content is loaded. | |
| 593 | +*/ | |
| 594 | +void style_js_onload(const char *zFormat, ...){ | |
| 595 | + va_list ap; | |
| 596 | + va_start(ap, zFormat); | |
| 597 | + blob_vappendf(&blobOnLoad, zFormat, ap); | |
| 598 | + va_end(ap); | |
| 557 | 599 | } |
| 558 | 600 | |
| 559 | 601 | /* |
| 560 | 602 | ** Draw the footer at the bottom of the page. |
| 561 | 603 | */ |
| 562 | 604 |
| --- src/style.c | |
| +++ src/style.c | |
| @@ -86,10 +86,16 @@ | |
| 86 | */ |
| 87 | static int needHrefJs = 0; /* href.js */ |
| 88 | static int needSortJs = 0; /* sorttable.js */ |
| 89 | static int needGraphJs = 0; /* graph.js */ |
| 90 | |
| 91 | /* |
| 92 | ** Generate and return a anchor tag like this: |
| 93 | ** |
| 94 | ** <a href="URL"> |
| 95 | ** or <a id="ID"> |
| @@ -360,10 +366,24 @@ | |
| 360 | char *zConfigName = mprintf("%s-image", zImageName); |
| 361 | url_var(zVarPrefix, zConfigName, zImageName); |
| 362 | free(zVarPrefix); |
| 363 | free(zConfigName); |
| 364 | } |
| 365 | |
| 366 | /* |
| 367 | ** Default HTML page header text through <body>. If the repository-specific |
| 368 | ** header template lacks a <body> tag, then all of the following is |
| 369 | ** prepended. |
| @@ -371,11 +391,13 @@ | |
| 371 | static char zDfltHeader[] = |
| 372 | @ <html> |
| 373 | @ <head> |
| 374 | @ <base href="$baseurl/$current_page" /> |
| 375 | @ <meta http-equiv="Content-Security-Policy" \ |
| 376 | @ content="default-src 'self' data: 'unsafe-inline'" /> |
| 377 | @ <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 378 | @ <title>$<project_name>: $<title></title> |
| 379 | @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" \ |
| 380 | @ href="$home/timeline.rss" /> |
| 381 | @ <link rel="stylesheet" href="$stylesheet_url" type="text/css" \ |
| @@ -402,10 +424,11 @@ | |
| 402 | @ <!DOCTYPE html> |
| 403 | |
| 404 | if( g.thTrace ) Th_Trace("BEGIN_HEADER<br />\n", -1); |
| 405 | |
| 406 | /* Generate the header up through the main menu */ |
| 407 | Th_Store("project_name", db_get("project-name","Unnamed Fossil Project")); |
| 408 | Th_Store("project_description", db_get("project-description","")); |
| 409 | Th_Store("title", zTitle); |
| 410 | Th_Store("baseurl", g.zBaseURL); |
| 411 | Th_Store("secureurl", login_wants_https_redirect()? g.zHttpsURL: g.zBaseURL); |
| @@ -523,11 +546,11 @@ | |
| 523 | int i; |
| 524 | for(i=0; i<nJsToLoad; i++){ |
| 525 | if( fossil_strcmp(zName, azJsToLoad[i])==0 ) return; |
| 526 | } |
| 527 | if( nJsToLoad>=sizeof(azJsToLoad)/sizeof(azJsToLoad[0]) ){ |
| 528 | fossil_panic("too man JS files"); |
| 529 | } |
| 530 | azJsToLoad[nJsToLoad++] = zName; |
| 531 | } |
| 532 | |
| 533 | /* |
| @@ -541,21 +564,40 @@ | |
| 541 | /* Load up the page data */ |
| 542 | bMouseover = (!g.isHuman || db_get_boolean("auto-hyperlink-ishuman",0)) |
| 543 | && db_get_boolean("auto-hyperlink-mouseover",0); |
| 544 | @ <script id='href-data' type='application/json'>\ |
| 545 | @ {"delay":%d(nDelay),"mouseover":%d(bMouseover)}</script> |
| 546 | style_load_one_js_file("href.js"); |
| 547 | } |
| 548 | if( needSortJs ){ |
| 549 | style_load_one_js_file("sorttable.js"); |
| 550 | } |
| 551 | if( needGraphJs ){ |
| 552 | style_load_one_js_file("graph.js"); |
| 553 | } |
| 554 | for(i=0; i<nJsToLoad; i++){ |
| 555 | style_load_one_js_file(azJsToLoad[i]); |
| 556 | } |
| 557 | } |
| 558 | |
| 559 | /* |
| 560 | ** Draw the footer at the bottom of the page. |
| 561 | */ |
| 562 |
| --- src/style.c | |
| +++ src/style.c | |
| @@ -86,10 +86,16 @@ | |
| 86 | */ |
| 87 | static int needHrefJs = 0; /* href.js */ |
| 88 | static int needSortJs = 0; /* sorttable.js */ |
| 89 | static int needGraphJs = 0; /* graph.js */ |
| 90 | |
| 91 | /* |
| 92 | ** Extra JS added to the end of the file. |
| 93 | */ |
| 94 | static Blob blobJs = BLOB_INITIALIZER; |
| 95 | static Blob blobOnLoad = BLOB_INITIALIZER; |
| 96 | |
| 97 | /* |
| 98 | ** Generate and return a anchor tag like this: |
| 99 | ** |
| 100 | ** <a href="URL"> |
| 101 | ** or <a id="ID"> |
| @@ -360,10 +366,24 @@ | |
| 366 | char *zConfigName = mprintf("%s-image", zImageName); |
| 367 | url_var(zVarPrefix, zConfigName, zImageName); |
| 368 | free(zVarPrefix); |
| 369 | free(zConfigName); |
| 370 | } |
| 371 | |
| 372 | /* |
| 373 | ** Return a random nonce that is stored in static space. For a particular |
| 374 | ** run, the same nonce is always returned. |
| 375 | */ |
| 376 | char *style_nonce(void){ |
| 377 | static char zNonce[52]; |
| 378 | if( zNonce[0]==0 ){ |
| 379 | unsigned char zSeed[24]; |
| 380 | sqlite3_randomness(24, zSeed); |
| 381 | encode16(zSeed,(unsigned char*)zNonce,24); |
| 382 | } |
| 383 | return zNonce; |
| 384 | } |
| 385 | |
| 386 | /* |
| 387 | ** Default HTML page header text through <body>. If the repository-specific |
| 388 | ** header template lacks a <body> tag, then all of the following is |
| 389 | ** prepended. |
| @@ -371,11 +391,13 @@ | |
| 391 | static char zDfltHeader[] = |
| 392 | @ <html> |
| 393 | @ <head> |
| 394 | @ <base href="$baseurl/$current_page" /> |
| 395 | @ <meta http-equiv="Content-Security-Policy" \ |
| 396 | @ content="default-src 'self' data: ; \ |
| 397 | @ script-src 'self' 'nonce-$<nonce>' ;\ |
| 398 | @ style-src 'self' 'unsafe-inline'" /> |
| 399 | @ <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 400 | @ <title>$<project_name>: $<title></title> |
| 401 | @ <link rel="alternate" type="application/rss+xml" title="RSS Feed" \ |
| 402 | @ href="$home/timeline.rss" /> |
| 403 | @ <link rel="stylesheet" href="$stylesheet_url" type="text/css" \ |
| @@ -402,10 +424,11 @@ | |
| 424 | @ <!DOCTYPE html> |
| 425 | |
| 426 | if( g.thTrace ) Th_Trace("BEGIN_HEADER<br />\n", -1); |
| 427 | |
| 428 | /* Generate the header up through the main menu */ |
| 429 | Th_Store("nonce", style_nonce()); |
| 430 | Th_Store("project_name", db_get("project-name","Unnamed Fossil Project")); |
| 431 | Th_Store("project_description", db_get("project-description","")); |
| 432 | Th_Store("title", zTitle); |
| 433 | Th_Store("baseurl", g.zBaseURL); |
| 434 | Th_Store("secureurl", login_wants_https_redirect()? g.zHttpsURL: g.zBaseURL); |
| @@ -523,11 +546,11 @@ | |
| 546 | int i; |
| 547 | for(i=0; i<nJsToLoad; i++){ |
| 548 | if( fossil_strcmp(zName, azJsToLoad[i])==0 ) return; |
| 549 | } |
| 550 | if( nJsToLoad>=sizeof(azJsToLoad)/sizeof(azJsToLoad[0]) ){ |
| 551 | fossil_panic("too many JS files"); |
| 552 | } |
| 553 | azJsToLoad[nJsToLoad++] = zName; |
| 554 | } |
| 555 | |
| 556 | /* |
| @@ -541,21 +564,40 @@ | |
| 564 | /* Load up the page data */ |
| 565 | bMouseover = (!g.isHuman || db_get_boolean("auto-hyperlink-ishuman",0)) |
| 566 | && db_get_boolean("auto-hyperlink-mouseover",0); |
| 567 | @ <script id='href-data' type='application/json'>\ |
| 568 | @ {"delay":%d(nDelay),"mouseover":%d(bMouseover)}</script> |
| 569 | } |
| 570 | @ <script nonce="%h(style_nonce())"> |
| 571 | if( needHrefJs ){ |
| 572 | cgi_append_content(builtin_text("href.js"),-1); |
| 573 | } |
| 574 | if( needSortJs ){ |
| 575 | cgi_append_content(builtin_text("sorttable.js"),-1); |
| 576 | } |
| 577 | if( needGraphJs ){ |
| 578 | cgi_append_content(builtin_text("graph.js"),-1); |
| 579 | } |
| 580 | for(i=0; i<nJsToLoad; i++){ |
| 581 | cgi_append_content(builtin_text(azJsToLoad[i]),-1); |
| 582 | } |
| 583 | if( blob_size(&blobOnLoad)>0 ){ |
| 584 | @ window.onload = function(){ |
| 585 | cgi_append_content(blob_buffer(&blobOnLoad), blob_size(&blobOnLoad)); |
| 586 | cgi_append_content("\n}\n", -1); |
| 587 | } |
| 588 | @ </script> |
| 589 | } |
| 590 | |
| 591 | /* |
| 592 | ** Extra JS to run after all content is loaded. |
| 593 | */ |
| 594 | void style_js_onload(const char *zFormat, ...){ |
| 595 | va_list ap; |
| 596 | va_start(ap, zFormat); |
| 597 | blob_vappendf(&blobOnLoad, zFormat, ap); |
| 598 | va_end(ap); |
| 599 | } |
| 600 | |
| 601 | /* |
| 602 | ** Draw the footer at the bottom of the page. |
| 603 | */ |
| 604 |