Fossil

Check-in [68542449]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Clarify the origin of JS in the generated HTML.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 6854244949f334722f97d3d8a60ee8adb5c26e09154d5d1437d6331f46964271
User & Date: drh 2020-09-18 03:28:39
Context
2020-09-18
04:10
pikchr js: hide/show the SVG's parent element instead of the SVG, so that output from pikchr print commands is hidden when the source is shown. ... (check-in: 43116c73 user: stephan tags: trunk)
03:28
Clarify the origin of JS in the generated HTML. ... (check-in: 68542449 user: drh tags: trunk)
02:54
Always include a \n after the opening safe_html() nonce when generating Pikchr output, for improved human readability of the generated HTML. ... (check-in: 39b2081e user: drh tags: trunk)
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/ajax.c.

44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
** with the current nonce, else no SCRIPT tag is emitted.
**
** Requires that builtin_emit_script_fossil_bootstrap() has already been
** called in order to initialize the window.fossil.page object.
*/
void ajax_emit_js_preview_modes(int addScriptTag){
  if(addScriptTag){
    style_emit_script_tag(0,0);
    CX("\n");
  }
  CX("fossil.page.previewModes={"
     "guess: %d, %d: 'guess', wiki: %d, %d: 'wiki',"
     "htmlIframe: %d, %d: 'htmlIframe', "
     "htmlInline: %d, %d: 'htmlInline', "
     "text: %d, %d: 'text'"
     "};\n",
     AJAX_RENDER_GUESS, AJAX_RENDER_GUESS,
     AJAX_RENDER_WIKI, AJAX_RENDER_WIKI,
     AJAX_RENDER_HTML_IFRAME, AJAX_RENDER_HTML_IFRAME,
     AJAX_RENDER_HTML_INLINE, AJAX_RENDER_HTML_INLINE,
     AJAX_RENDER_PLAIN_TEXT, AJAX_RENDER_PLAIN_TEXT);
  if(addScriptTag){
    style_emit_script_tag(1,0);
  }
}

/*
** Returns a value from the ajax_render_modes enum, based on the
** given mime type string (which may be NULL), defaulting to
** AJAX_RENDER_PLAIN_TEXT.







|
<













|







44
45
46
47
48
49
50
51

52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
** with the current nonce, else no SCRIPT tag is emitted.
**
** Requires that builtin_emit_script_fossil_bootstrap() has already been
** called in order to initialize the window.fossil.page object.
*/
void ajax_emit_js_preview_modes(int addScriptTag){
  if(addScriptTag){
    style_script_begin(__FILE__,__LINE__);

  }
  CX("fossil.page.previewModes={"
     "guess: %d, %d: 'guess', wiki: %d, %d: 'wiki',"
     "htmlIframe: %d, %d: 'htmlIframe', "
     "htmlInline: %d, %d: 'htmlInline', "
     "text: %d, %d: 'text'"
     "};\n",
     AJAX_RENDER_GUESS, AJAX_RENDER_GUESS,
     AJAX_RENDER_WIKI, AJAX_RENDER_WIKI,
     AJAX_RENDER_HTML_IFRAME, AJAX_RENDER_HTML_IFRAME,
     AJAX_RENDER_HTML_INLINE, AJAX_RENDER_HTML_INLINE,
     AJAX_RENDER_PLAIN_TEXT, AJAX_RENDER_PLAIN_TEXT);
  if(addScriptTag){
    style_script_end();
  }
}

/*
** Returns a value from the ajax_render_modes enum, based on the
** given mime type string (which may be NULL), defaulting to
** AJAX_RENDER_PLAIN_TEXT.

Changes to src/builtin.c.

314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
void builtin_fulfill_js_requests(void){
  if( builtin.nSent>=builtin.nReq ) return;  /* nothing to do */
  switch( builtin.eDelivery ){
    case JS_INLINE: {
      CX("<script nonce='%h'>\n",style_nonce());
      do{
        int i = builtin.aReq[builtin.nSent++];
        CX("/* %s */\n", aBuiltinFiles[i].zName);
        cgi_append_content((const char*)aBuiltinFiles[i].pData,
                           aBuiltinFiles[i].nByte);
      }while( builtin.nSent<builtin.nReq );
      CX("</script>\n");
      break;
    }
    case JS_BUNDLED: {







|







314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
void builtin_fulfill_js_requests(void){
  if( builtin.nSent>=builtin.nReq ) return;  /* nothing to do */
  switch( builtin.eDelivery ){
    case JS_INLINE: {
      CX("<script nonce='%h'>\n",style_nonce());
      do{
        int i = builtin.aReq[builtin.nSent++];
        CX("/* %s %.60c*/\n", aBuiltinFiles[i].zName, '*');
        cgi_append_content((const char*)aBuiltinFiles[i].pData,
                           aBuiltinFiles[i].nByte);
      }while( builtin.nSent<builtin.nReq );
      CX("</script>\n");
      break;
    }
    case JS_BUNDLED: {
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
void builtin_emit_script_fossil_bootstrap(int addScriptTag){
  static int once = 0;
  if(0==once++){
    char * zName;
    /* Set up the generic/app-agnostic parts of window.fossil
    ** which require C-level state... */
    if(addScriptTag!=0){
      style_emit_script_tag(0,0);
    }
    CX("(function(){\n");
    CX(/*MSIE NodeList.forEach polyfill, courtesy of Mozilla:
    https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#Polyfill
       */
       "if(window.NodeList && !NodeList.prototype.forEach){"
       "NodeList.prototype.forEach = Array.prototype.forEach;"







|







590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
void builtin_emit_script_fossil_bootstrap(int addScriptTag){
  static int once = 0;
  if(0==once++){
    char * zName;
    /* Set up the generic/app-agnostic parts of window.fossil
    ** which require C-level state... */
    if(addScriptTag!=0){
      style_script_begin(__FILE__,__LINE__);
    }
    CX("(function(){\n");
    CX(/*MSIE NodeList.forEach polyfill, courtesy of Mozilla:
    https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#Polyfill
       */
       "if(window.NodeList && !NodeList.prototype.forEach){"
       "NodeList.prototype.forEach = Array.prototype.forEach;"
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
    ** page-specific state, and it is reserved for that purpose.
    */
    CX("window.fossil.page = {"
       "name:\"%T\""
       "};\n", g.zPath);
    CX("})();\n");
    if(addScriptTag!=0){
      style_emit_script_tag(1,0);
    }
    /* The remaining window.fossil bootstrap code is not dependent on
    ** C-runtime state... */
    builtin_request_js("fossil.bootstrap.js");
  }
}








|







653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
    ** page-specific state, and it is reserved for that purpose.
    */
    CX("window.fossil.page = {"
       "name:\"%T\""
       "};\n", g.zPath);
    CX("})();\n");
    if(addScriptTag!=0){
      style_script_end();
    }
    /* The remaining window.fossil bootstrap code is not dependent on
    ** C-runtime state... */
    builtin_request_js("fossil.bootstrap.js");
  }
}

Changes to src/captcha.c.

560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
/*
** Add a "Speak the captcha" button.
*/
void captcha_speakit_button(unsigned int uSeed, const char *zMsg){
  if( zMsg==0 ) zMsg = "Speak the text";
  @ <input aria-label="%h(zMsg)" type="button" value="%h(zMsg)" \
  @ id="speakthetext">
  @ <script nonce="%h(style_nonce())">
  @ document.getElementById("speakthetext").onclick = function(){
  @   var audio = window.fossilAudioCaptcha \
  @ || new Audio("%R/captcha-audio/%u(uSeed)");
  @   window.fossilAudioCaptcha = audio;
  @   audio.currentTime = 0;
  @   audio.play();
  @ }







|







560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
/*
** Add a "Speak the captcha" button.
*/
void captcha_speakit_button(unsigned int uSeed, const char *zMsg){
  if( zMsg==0 ) zMsg = "Speak the text";
  @ <input aria-label="%h(zMsg)" type="button" value="%h(zMsg)" \
  @ id="speakthetext">
  @ <script nonce="%h(style_nonce())">/* captcha_speakit_button() */
  @ document.getElementById("speakthetext").onclick = function(){
  @   var audio = window.fossilAudioCaptcha \
  @ || new Audio("%R/captcha-audio/%u(uSeed)");
  @   window.fossilAudioCaptcha = audio;
  @   audio.currentTime = 0;
  @   audio.play();
  @ }

Changes to src/doc.c.

406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
** functionality, just nice-to-haves. Only call this once per page.
*/
void document_emit_js(void){
  if(!builtin_bundle_all_fossil_js_apis()){
    builtin_emit_fossil_js_apis("dom", "copybutton",
                                "pikchr", 0);
  }
  style_emit_script_tag(0,0);
  CX("window.addEventListener('load', "
     "()=>window.fossil.pikchr.addSrcView(), "
     "false);\n");
  style_emit_script_tag(1,0);
}

/*
** Guess the mime-type of a document based on its name.
*/
const char *mimetype_from_name(const char *zName){
  const char *z;







|



|







406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
** functionality, just nice-to-haves. Only call this once per page.
*/
void document_emit_js(void){
  if(!builtin_bundle_all_fossil_js_apis()){
    builtin_emit_fossil_js_apis("dom", "copybutton",
                                "pikchr", 0);
  }
  style_script_begin(__FILE__,__LINE__);
  CX("window.addEventListener('load', "
     "()=>window.fossil.pikchr.addSrcView(), "
     "false);\n");
  style_script_end();
}

/*
** Guess the mime-type of a document based on its name.
*/
const char *mimetype_from_name(const char *zName){
  const char *z;

Changes to src/fileedit.c.

1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680

  /* The CSS for this page lives in a common file but much of it we
  ** don't want inadvertently being used by other pages. We don't
  ** have a common, page-specific container we can filter our CSS
  ** selectors, but we do have the BODY, which we can decorate with
  ** whatever CSS we wish...
  */
  style_emit_script_tag(0,0);
  CX("document.body.classList.add('fileedit');\n");
  style_emit_script_tag(1,0);
  
  /* Status bar */
  CX("<div id='fossil-status-bar' "
     "title='Status message area. Double-click to clear them.'>"
     "Status messages will go here.</div>\n"
     /* will be moved into the tab container via JS */);








|

|







1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680

  /* The CSS for this page lives in a common file but much of it we
  ** don't want inadvertently being used by other pages. We don't
  ** have a common, page-specific container we can filter our CSS
  ** selectors, but we do have the BODY, which we can decorate with
  ** whatever CSS we wish...
  */
  style_script_begin(__FILE__,__LINE__);
  CX("document.body.classList.add('fileedit');\n");
  style_script_end();
  
  /* Status bar */
  CX("<div id='fossil-status-bar' "
     "title='Status message area. Double-click to clear them.'>"
     "Status messages will go here.</div>\n"
     /* will be moved into the tab container via JS */);

2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
  builtin_request_js("fossil.page.fileedit.js");
  builtin_fulfill_js_requests();
  {
    /* Dynamically populate the editor, display any error in the err
    ** blob, and/or switch to tab #0, where the file selector
    ** lives. The extra C scopes here correspond to JS-level scopes,
    ** to improve grokability. */
    style_emit_script_tag(0,0);
    CX("\n(function(){\n");
    CX("try{\n");
    {
      char * zFirstLeafUuid = 0;
      CX("fossil.config['fileedit-glob'] = ");
      glob_render_json_to_cgi(fileedit_glob());
      CX(";\n");







|







2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
  builtin_request_js("fossil.page.fileedit.js");
  builtin_fulfill_js_requests();
  {
    /* Dynamically populate the editor, display any error in the err
    ** blob, and/or switch to tab #0, where the file selector
    ** lives. The extra C scopes here correspond to JS-level scopes,
    ** to improve grokability. */
    style_script_begin(__FILE__,__LINE__);
    CX("\n(function(){\n");
    CX("try{\n");
    {
      char * zFirstLeafUuid = 0;
      CX("fossil.config['fileedit-glob'] = ");
      glob_render_json_to_cgi(fileedit_glob());
      CX(";\n");
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
      }
      CX("});\n")/*fossil.onPageLoad()*/;
    }
    CX("}catch(e){"
       "fossil.error(e); console.error('Exception:',e);"
       "}\n");
    CX("})();")/*anonymous function*/;
    style_emit_script_tag(1,0);
  }
  blob_reset(&err);
  CheckinMiniInfo_cleanup(&cimi);
  db_end_transaction(0);
  style_footer();
}







|






2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
      }
      CX("});\n")/*fossil.onPageLoad()*/;
    }
    CX("}catch(e){"
       "fossil.error(e); console.error('Exception:',e);"
       "}\n");
    CX("})();")/*anonymous function*/;
    style_script_end();
  }
  blob_reset(&err);
  CheckinMiniInfo_cleanup(&cimi);
  db_end_transaction(0);
  style_footer();
}

Changes to src/info.c.

2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
      safe_html_context(DOCSRC_FILE);
      wiki_render_by_mimetype(&content, zMime);
    }else if( renderAsHtml ){
      @ <iframe src="%R/raw/%s(zUuid)"
      @ width="100%%" frameborder="0" marginwidth="0" marginheight="0"
      @ sandbox="allow-same-origin" id="ifm1">
      @ </iframe>
      @ <script nonce="%h(style_nonce())">
      @ document.getElementById("ifm1").addEventListener("load",
      @   function(){
      @     this.height=this.contentDocument.documentElement.scrollHeight + 75;
      @   }
      @ );
      @ </script>
    }else{







|







2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
      safe_html_context(DOCSRC_FILE);
      wiki_render_by_mimetype(&content, zMime);
    }else if( renderAsHtml ){
      @ <iframe src="%R/raw/%s(zUuid)"
      @ width="100%%" frameborder="0" marginwidth="0" marginheight="0"
      @ sandbox="allow-same-origin" id="ifm1">
      @ </iframe>
      @ <script nonce="%h(style_nonce())">/* info.c:%d(__LINE__) */
      @ document.getElementById("ifm1").addEventListener("load",
      @   function(){
      @     this.height=this.contentDocument.documentElement.scrollHeight + 75;
      @   }
      @ );
      @ </script>
    }else{

Changes to src/style.c.

712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
static void style_load_all_js_files(void){
  if( needHrefJs ){
    int nDelay = db_get_int("auto-hyperlink-delay",0);
    int bMouseover = db_get_boolean("auto-hyperlink-mouseover",0);
    @ <script id='href-data' type='application/json'>\
    @ {"delay":%d(nDelay),"mouseover":%d(bMouseover)}</script>
  }
  @ <script nonce="%h(style_nonce())">
  @ function debugMsg(msg){
  @ var n = document.getElementById("debugMsg");
  @ if(n){n.textContent=msg;}
  @ }
  if( needHrefJs ){
    @ /* href.js */
    cgi_append_content(builtin_text("href.js"),-1);







|







712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
static void style_load_all_js_files(void){
  if( needHrefJs ){
    int nDelay = db_get_int("auto-hyperlink-delay",0);
    int bMouseover = db_get_boolean("auto-hyperlink-mouseover",0);
    @ <script id='href-data' type='application/json'>\
    @ {"delay":%d(nDelay),"mouseover":%d(bMouseover)}</script>
  }
  @ <script nonce="%h(style_nonce())">/* style.c:%d(__LINE__) */
  @ function debugMsg(msg){
  @ var n = document.getElementById("debugMsg");
  @ if(n){n.textContent=msg;}
  @ }
  if( needHrefJs ){
    @ /* href.js */
    cgi_append_content(builtin_text("href.js"),-1);
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447

1448
1449

1450

1451
1452
1453
1454


1455
1456
1457



1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
  CX("</select>\n");
  CX("</div>\n");
  va_end(vargs);
  fossil_free(zLabelID);
}

/*
** If passed 0 as its first argument, it emits a script opener tag
** with this request's nonce. If passed non-0 it emits a script
** closing tag. Mnemonic for remembering the order in which to pass 0
** or 1 as the first argument to this function: 0 comes before 1.
**
** If passed 0 as its first argument and a non-NULL/non-empty zSrc,
** then it instead emits:
**
** <script src='%R/{{zSrc}}'></script>
**
** zSrc is always assumed to be a repository-relative path without
** a leading slash, and has %R/ prepended to it.
**
** Meaning that no follow-up call to pass a non-0 first argument
** to close the tag. zSrc is ignored if the first argument is not

** 0.
*/

void style_emit_script_tag(int isCloser, const char * zSrc){

  if(0==isCloser){
    if(zSrc!=0 && zSrc[0]!=0){
      CX("<script src='%R/%T'></script>\n", zSrc);
    }else{


      CX("<script nonce='%s'>", style_nonce());
    }
  }else{



    CX("</script>\n");
  }
}

/*
** Emits a NOSCRIPT tag with an error message stating that JS is
** required for the current page. This "should" be called near the top
** of pages which *require* JS. The inner DIV has the CSS class
** 'error' and can be styled via a (noscript > .error) CSS selector.
*/
void style_emit_noscript_for_js_page(void){
  CX("<noscript><div class='error'>"
     "This page requires JavaScript (ES2015, a.k.a. ES6, or newer)."
     "</div></noscript>");
}







<
<
<
<
<
<
<
<
|

<
<
<
<
<
>
|

>
|
>
|
|
<
<
>
>
|
|
|
>
>
>
|
<













1426
1427
1428
1429
1430
1431
1432








1433
1434





1435
1436
1437
1438
1439
1440
1441
1442


1443
1444
1445
1446
1447
1448
1449
1450
1451

1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
  CX("</select>\n");
  CX("</div>\n");
  va_end(vargs);
  fossil_free(zLabelID);
}

/*








** Generate a <script> with an appropriate nonce.
**





** zOrigin and iLine are the source code filename and line number
** that generated this request.
*/
void style_script_begin(const char *zOrigin, int iLine){
  const char *z;
  for(z=zOrigin; z[0]!=0; z++){
    if( z[0]=='/' || z[0]=='\\' ){
      zOrigin = z+1;


    }
  }
  CX("<script nonce='%s'>/* %s:%d */\n", style_nonce(), zOrigin, iLine);
}

/* Generate the closing </script> tag 
*/
void style_script_end(void){
  CX("</script>\n");

}

/*
** Emits a NOSCRIPT tag with an error message stating that JS is
** required for the current page. This "should" be called near the top
** of pages which *require* JS. The inner DIV has the CSS class
** 'error' and can be styled via a (noscript > .error) CSS selector.
*/
void style_emit_noscript_for_js_page(void){
  CX("<noscript><div class='error'>"
     "This page requires JavaScript (ES2015, a.k.a. ES6, or newer)."
     "</div></noscript>");
}

Changes to src/wiki.c.

1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
                                "storage", "popupwidget", "copybutton",
                                "pikchr", 0);
  }
  builtin_request_js("sbsdiff.js");
  builtin_request_js("fossil.page.wikiedit.js");
  builtin_fulfill_js_requests();
  /* Dynamically populate the editor... */
  style_emit_script_tag(0,0);
  {
    /* Render the current page list to save us an XHR request
       during page initialization. This must be OUTSIDE of
       an onPageLoad() handler or else it does not get applied
       until after the wiki list widget is initialized. Similarly,
       it must come *after* window.fossil is initialized. */
    CX("\nfossil.page.initialPageList = ");







|







1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
                                "storage", "popupwidget", "copybutton",
                                "pikchr", 0);
  }
  builtin_request_js("sbsdiff.js");
  builtin_request_js("fossil.page.wikiedit.js");
  builtin_fulfill_js_requests();
  /* Dynamically populate the editor... */
  style_script_begin(__FILE__,__LINE__);
  {
    /* Render the current page list to save us an XHR request
       during page initialization. This must be OUTSIDE of
       an onPageLoad() handler or else it does not get applied
       until after the wiki list widget is initialized. Similarly,
       it must come *after* window.fossil is initialized. */
    CX("\nfossil.page.initialPageList = ");
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
  if(zPageName && *zPageName){
    CX("P.loadPage(%!j);\n", zPageName);
  }
  CX("}catch(e){"
     "fossil.error(e); console.error('Exception:',e);"
     "}\n");
  CX("});\n"/*fossil.onPageLoad()*/);
  style_emit_script_tag(1,0);
  style_footer();
}

/*
** WEBPAGE: wikinew
** URL /wikinew
**







|







1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
  if(zPageName && *zPageName){
    CX("P.loadPage(%!j);\n", zPageName);
  }
  CX("}catch(e){"
     "fossil.error(e); console.error('Exception:',e);"
     "}\n");
  CX("});\n"/*fossil.onPageLoad()*/);
  style_script_end();
  style_footer();
}

/*
** WEBPAGE: wikinew
** URL /wikinew
**