Fossil

Check-in [855076ce79]
Login

Check-in [855076ce79]

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

Overview
Comment:Sync with trunk.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | standard-cli-colors
Files: files | file ages | folders
SHA3-256: 855076ce79275ccf2e206b5359371a900734dff9228aae0262547f5f99e90b53
User & Date: florian 2025-04-25 16:18:00.000
Context
2025-04-25
16:19
Fix a logic error in processing of the NO_COLOR environment variable. ... (check-in: fbfa6daeca user: florian tags: standard-cli-colors)
16:18
Sync with trunk. ... (check-in: 855076ce79 user: florian tags: standard-cli-colors)
16:08
Simplifications to TH1 for improved defense against accident and mischief: Omit the enable_htmlify command. Htmlify is always turned on. Omit the --th option from the "fossil pikchr" command. ... (check-in: 9164a5d1aa user: drh tags: trunk)
12:53
Remove the show-repolist-desc and show-repolist-lg settings. Control of which columns of a repository list to show is now only by the FOSSIL_REPOLIST_SHOW environment variable. ... (check-in: d9bd156aad user: drh tags: trunk)
2025-04-19
04:20
Fix a logic error in processing of the FOSSIL_COLOR environment variable. ... (check-in: 6cb7a7e28d user: florian tags: standard-cli-colors)
Changes
Unified Diff Ignore Whitespace Patch
Changes to skins/default/header.txt.
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
        set logourl $baseurl
      }
      return $logourl
    }
    set logourl [getLogoUrl $baseurl]
    </th1>
    <a href="$logourl">
      <img src="$logo_image_url" border="0" alt="$project_name">
    </a>
  </div>
  <div class="title">
    <h1>$<project_name></h1>
    <span class="page-title">$<title></span>
  </div>
  <div class="status">







|







26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
        set logourl $baseurl
      }
      return $logourl
    }
    set logourl [getLogoUrl $baseurl]
    </th1>
    <a href="$logourl">
      <img src="$logo_image_url" border="0" alt="$<project_name>">
    </a>
  </div>
  <div class="title">
    <h1>$<project_name></h1>
    <span class="page-title">$<title></span>
  </div>
  <div class="status">
Changes to skins/eagle/header.txt.
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
      set logourl [getLogoUrl $baseurl]
    } else {
      # Link logo to the top of the current repo
      set logourl $baseurl
    }
    </th1>
    <a href="$logourl">
      <img src="$logo_image_url" border="0" alt="$project_name">
    </a>
  </div>
  <div class="title">$<title></div>
  <div class="status"><nobr><th1>
     if {[info exists login]} {
       puts "Logged in as $login"
     } else {







|







63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
      set logourl [getLogoUrl $baseurl]
    } else {
      # Link logo to the top of the current repo
      set logourl $baseurl
    }
    </th1>
    <a href="$logourl">
      <img src="$logo_image_url" border="0" alt="$<project_name>">
    </a>
  </div>
  <div class="title">$<title></div>
  <div class="status"><nobr><th1>
     if {[info exists login]} {
       puts "Logged in as $login"
     } else {
Changes to skins/original/header.txt.
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
        set logourl $baseurl
      }
      return $logourl
    }
    set logourl [getLogoUrl $baseurl]
    </th1>
    <a href="$logourl">
      <img src="$logo_image_url" border="0" alt="$project_name">
    </a>
  </div>
  <div class="title">$<title></div>
  <div class="status"><nobr><th1>
     if {[info exists login]} {
       puts "Logged in as $login"
     } else {







|







57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
        set logourl $baseurl
      }
      return $logourl
    }
    set logourl [getLogoUrl $baseurl]
    </th1>
    <a href="$logourl">
      <img src="$logo_image_url" border="0" alt="$<project_name>">
    </a>
  </div>
  <div class="title">$<title></div>
  <div class="status"><nobr><th1>
     if {[info exists login]} {
       puts "Logged in as $login"
     } else {
Changes to skins/xekri/header.txt.
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
      set logourl [getLogoUrl $baseurl]
    } else {
      # Link logo to the top of the current repo
      set logourl $baseurl
    }
    </th1>
    <a href="$logourl">
      <img src="$logo_image_url" border="0" alt="$project_name">
    </a>
  </div>
  <div class="title">$<title></div>
  <div class="status"><nobr>
    <th1>
      if {[info exists login]} {
        puts "Logged in as $login"







|







63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
      set logourl [getLogoUrl $baseurl]
    } else {
      # Link logo to the top of the current repo
      set logourl $baseurl
    }
    </th1>
    <a href="$logourl">
      <img src="$logo_image_url" border="0" alt="$<project_name>">
    </a>
  </div>
  <div class="title">$<title></div>
  <div class="status"><nobr>
    <th1>
      if {[info exists login]} {
        puts "Logged in as $login"
Changes to src/alerts.c.
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
    blob_appendf(pOut, "From: %s <%s@%s>\r\n",
       zFromName, alert_mailbox_name(zFromName), alert_hostname(p->zFrom));
    blob_appendf(pOut, "Sender: <%s>\r\n", p->zFrom);
  }else{
    blob_appendf(pOut, "From: <%s>\r\n", p->zFrom);
  }
  blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0)));
  if( p->zListId  && p->zListId[0] ){
    blob_appendf(pOut, "List-Id: %s\r\n", p->zListId);
  }
  if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){
    /* Message-id format:  "<$(date)x$(random)@$(from-host)>" where $(date) is
    ** the current unix-time in hex, $(random) is a 64-bit random number,
    ** and $(from) is the domain part of the email-self setting. */
    sqlite3_randomness(sizeof(r1), &r1);
    r2 = time(0);
    blob_appendf(pOut, "Message-Id: <%llxx%016llx@%s>\r\n",







<
<
<







982
983
984
985
986
987
988



989
990
991
992
993
994
995
    blob_appendf(pOut, "From: %s <%s@%s>\r\n",
       zFromName, alert_mailbox_name(zFromName), alert_hostname(p->zFrom));
    blob_appendf(pOut, "Sender: <%s>\r\n", p->zFrom);
  }else{
    blob_appendf(pOut, "From: <%s>\r\n", p->zFrom);
  }
  blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0)));



  if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){
    /* Message-id format:  "<$(date)x$(random)@$(from-host)>" where $(date) is
    ** the current unix-time in hex, $(random) is a 64-bit random number,
    ** and $(from) is the domain part of the email-self setting. */
    sqlite3_randomness(sizeof(r1), &r1);
    r2 = time(0);
    blob_appendf(pOut, "Message-Id: <%llxx%016llx@%s>\r\n",
3213
3214
3215
3216
3217
3218
3219


3220
3221
3222
3223
3224
3225
3226
3227

3228
3229
3230
3231
3232
3233
3234
      if( blob_size(&p->hdr)>0 ){
        /* This alert should be sent as a separate email */
        Blob fhdr, fbody;
        blob_init(&fhdr, 0, 0);
        blob_appendf(&fhdr, "To: <%s>\r\n", zEmail);
        blob_append(&fhdr, blob_buffer(&p->hdr), blob_size(&p->hdr));
        blob_init(&fbody, blob_buffer(&p->txt), blob_size(&p->txt));


        blob_appendf(&fhdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
                     zUrl, zCode);
        blob_appendf(&fhdr,
                   "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
        blob_appendf(&fbody, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n",
           zUrl, zCode);
        /* blob_appendf(&fbody, "Subscription settings: %s/alerts/%s\n",
        **   zUrl, zCode); */

        alert_send(pSender,&fhdr,&fbody,p->zFromName);
        nSent++;
        blob_reset(&fhdr);
        blob_reset(&fbody);
      }else{
        /* Events other than forum posts are gathered together into
        ** a single email message */







>
>
|
|
|
|
|
|
|
|
>







3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
      if( blob_size(&p->hdr)>0 ){
        /* This alert should be sent as a separate email */
        Blob fhdr, fbody;
        blob_init(&fhdr, 0, 0);
        blob_appendf(&fhdr, "To: <%s>\r\n", zEmail);
        blob_append(&fhdr, blob_buffer(&p->hdr), blob_size(&p->hdr));
        blob_init(&fbody, blob_buffer(&p->txt), blob_size(&p->txt));
        if( pSender->zListId  && pSender->zListId[0] ){
           blob_appendf(&fhdr, "List-Id: %s\r\n", pSender->zListId);
           blob_appendf(&fhdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
                        zUrl, zCode);
           blob_appendf(&fhdr,
                       "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
           blob_appendf(&fbody, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n",
                        zUrl, zCode);
           /* blob_appendf(&fbody, "Subscription settings: %s/alerts/%s\n",
           **   zUrl, zCode); */
        }
        alert_send(pSender,&fhdr,&fbody,p->zFromName);
        nSent++;
        blob_reset(&fhdr);
        blob_reset(&fbody);
      }else{
        /* Events other than forum posts are gathered together into
        ** a single email message */
3243
3244
3245
3246
3247
3248
3249


3250
3251

3252
3253
3254

3255
3256
3257
3258
3259
3260
3261
        }
        nHit++;
        blob_append(&body, "\n", 1);
        blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt));
      }
    }
    if( nHit==0 ) continue;


    blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
         zUrl, zCode);

    blob_appendf(&hdr, "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
    blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
         zUrl, zCode);

    alert_send(pSender,&hdr,&body,0);
    nSent++;
    blob_truncate(&hdr, 0);
    blob_truncate(&body, 0);
  }
  blob_reset(&hdr);
  blob_reset(&body);







>
>
|
|
>
|
|
|
>







3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
        }
        nHit++;
        blob_append(&body, "\n", 1);
        blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt));
      }
    }
    if( nHit==0 ) continue;
    if( pSender->zListId  && pSender->zListId[0] ){
      blob_appendf(&hdr, "List-Id: %s\r\n", pSender->zListId);
      blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
           zUrl, zCode);
      blob_appendf(&hdr,
            "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
      blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
           zUrl, zCode);
    }
    alert_send(pSender,&hdr,&body,0);
    nSent++;
    blob_truncate(&hdr, 0);
    blob_truncate(&body, 0);
  }
  blob_reset(&hdr);
  blob_reset(&body);
3293
3294
3295
3296
3297
3298
3299

3300
3301
3302
3303
3304
3305
3306
3307









3308
3309
3310
3311
3312
3313
3314
         " WHERE lastContact<=%d AND lastContact>%d"
         "   AND NOT sdonotcall"
         "   AND length(sdigest)>0",
         iNewWarn, iOldWarn
      );
      while( db_step(&q)==SQLITE_ROW ){
        Blob hdr, body;

        blob_init(&hdr, 0, 0);
        blob_init(&body, 0, 0);
        alert_renewal_msg(&hdr, &body,
           db_column_text(&q,0),
           db_column_int(&q,1),
           db_column_text(&q,2),
           db_column_text(&q,3),
           zRepoName, zUrl);









        alert_send(pSender,&hdr,&body,0);
        blob_reset(&hdr);
        blob_reset(&body);
      }
      db_finalize(&q);
      if( (flags & SENDALERT_PRESERVE)==0 ){
        if( iOldWarn>0 ){







>



|




>
>
>
>
>
>
>
>
>







3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
         " WHERE lastContact<=%d AND lastContact>%d"
         "   AND NOT sdonotcall"
         "   AND length(sdigest)>0",
         iNewWarn, iOldWarn
      );
      while( db_step(&q)==SQLITE_ROW ){
        Blob hdr, body;
        const char *zCode = db_column_text(&q,0);
        blob_init(&hdr, 0, 0);
        blob_init(&body, 0, 0);
        alert_renewal_msg(&hdr, &body,
           zCode,
           db_column_int(&q,1),
           db_column_text(&q,2),
           db_column_text(&q,3),
           zRepoName, zUrl);
        if( pSender->zListId  && pSender->zListId[0] ){
           blob_appendf(&hdr, "List-Id: %s\r\n", pSender->zListId);
           blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
                        zUrl, zCode);
           blob_appendf(&hdr,
                       "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
           blob_appendf(&body, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n",
                        zUrl, zCode);
        }
        alert_send(pSender,&hdr,&body,0);
        blob_reset(&hdr);
        blob_reset(&body);
      }
      db_finalize(&q);
      if( (flags & SENDALERT_PRESERVE)==0 ){
        if( iOldWarn>0 ){
Changes to src/browse.c.
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
      int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
      linkTrunk = trunkRid && rid != trunkRid;
      linkTip = rid != symbolic_name_to_rid("tip", "ci");
      zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
      isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0);
      isBranchCI = branch_includes_uuid(zCI, zUuid);
      if( bDocDir ) zCI = mprintf("%S", zUuid);
      Th_Store("current_checkin", zCI);
    }else{
      zCI = 0;
    }
  }

  assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) );
  if( zD==0 ){







|







203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
      int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
      linkTrunk = trunkRid && rid != trunkRid;
      linkTip = rid != symbolic_name_to_rid("tip", "ci");
      zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
      isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0);
      isBranchCI = branch_includes_uuid(zCI, zUuid);
      if( bDocDir ) zCI = mprintf("%S", zUuid);
      Th_StoreUnsafe("current_checkin", zCI);
    }else{
      zCI = 0;
    }
  }

  assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) );
  if( zD==0 ){
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
      linkTip = rid != symbolic_name_to_rid("tip", "ci");
      zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
      rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
      zNow = db_text("", "SELECT datetime(mtime,toLocal())"
                         " FROM event WHERE objid=%d", rid);
      isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0);
      isBranchCI = branch_includes_uuid(zCI, zUuid);
      Th_Store("current_checkin", zCI);
    }else{
      zCI = 0;
    }
  }
  if( zCI==0 ){
    rNow = db_double(0.0, "SELECT max(mtime) FROM event");
    zNow = db_text("", "SELECT datetime(max(mtime),toLocal()) FROM event");







|







769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
      linkTip = rid != symbolic_name_to_rid("tip", "ci");
      zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
      rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
      zNow = db_text("", "SELECT datetime(mtime,toLocal())"
                         " FROM event WHERE objid=%d", rid);
      isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0);
      isBranchCI = branch_includes_uuid(zCI, zUuid);
      Th_StoreUnsafe("current_checkin", zCI);
    }else{
      zCI = 0;
    }
  }
  if( zCI==0 ){
    rNow = db_double(0.0, "SELECT max(mtime) FROM event");
    zNow = db_text("", "SELECT datetime(max(mtime),toLocal()) FROM event");
Changes to src/cgi.c.
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
    zHdr += m+1;
    nHdr -= m+1;
  }
  fossil_free(zToFree);
  fgetc(g.httpIn);  /* Read past the "," separating header from content */
  cgi_init();
}

#if !defined(_WIN32)
/*
** Change the listening socket, if necessary, so that it will accept both IPv4
** and IPv6
*/
static void allowBothIpV4andV6(int listener){
#if defined(IPV6_V6ONLY)
  int ipv6only = -1;
  socklen_t ipv6only_size = sizeof(ipv6only);
  getsockopt(listener, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, &ipv6only_size);
  if( ipv6only ){
    ipv6only = 0;
    setsockopt(listener, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, ipv6only_size);
  }
#endif /* defined(IPV6_ONLY) */
}
#endif /* !defined(_WIN32) */


#if INTERFACE
/*
** Bitmap values for the flags parameter to cgi_http_server().
*/
#define HTTP_SERVER_LOCALHOST      0x0001     /* Bind to 127.0.0.1 only */
#define HTTP_SERVER_SCGI           0x0002     /* SCGI instead of HTTP */







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







2495
2496
2497
2498
2499
2500
2501



















2502
2503
2504
2505
2506
2507
2508
    zHdr += m+1;
    nHdr -= m+1;
  }
  fossil_free(zToFree);
  fgetc(g.httpIn);  /* Read past the "," separating header from content */
  cgi_init();
}




















#if INTERFACE
/*
** Bitmap values for the flags parameter to cgi_http_server().
*/
#define HTTP_SERVER_LOCALHOST      0x0001     /* Bind to 127.0.0.1 only */
#define HTTP_SERVER_SCGI           0x0002     /* SCGI instead of HTTP */
2557
2558
2559
2560
2561
2562
2563


2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577

2578
2579







2580
2581
2582



2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603



2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669



2670













2671














2672



2673






2674




2675
2676
2677
2678
2679
2680
2681
















2682




2683
2684
2685
2686

2687



2688








2689

2690












2691





2692
2693



2694
2695
2696
2697

2698




2699

2700






2701
2702

2703





2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720





2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731

2732
2733
2734





2735

2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763

2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
  const char *zIpAddr,      /* Bind to this IP address, if not null */
  int flags                 /* HTTP_SERVER_* flags */
){
#if defined(_WIN32)
  /* Use win32_http_server() instead */
  fossil_exit(1);
#else


  int listener = -1;           /* The server socket */
  int connection;              /* A socket for each individual connection */
  int nRequest = 0;            /* Number of requests handled so far */
  fd_set readfds;              /* Set of file descriptors for select() */
  socklen_t lenaddr;           /* Length of the inaddr structure */
  int child;                   /* PID of the child process */
  int nchildren = 0;           /* Number of child processes */
  struct timeval delay;        /* How long to wait inside select() */
  struct sockaddr_in6 inaddr;  /* The socket address */
  struct sockaddr_in inaddr4;  /* IPv4 address; needed by OpenBSD */
  struct sockaddr_un uxaddr;   /* The address for unix-domain sockets */
  int opt = 1;                 /* setsockopt flag */
  int rc;                      /* Result code from system calls */
  int iPort = mnPort;          /* Port to try to use */

  int bIPv4 = 0;               /* Use IPv4 only; use inaddr4, not inaddr */








  while( iPort<=mxPort ){
    if( flags & HTTP_SERVER_UNIXSOCKET ){
      /* Initialize a Unix socket named g.zSockName */



      assert( g.zSockName!=0 );
      memset(&uxaddr, 0, sizeof(uxaddr));
      if( strlen(g.zSockName)>sizeof(uxaddr.sun_path) ){
        fossil_fatal("name of unix socket too big: %s\nmax size: %d\n",
                     g.zSockName, (int)sizeof(uxaddr.sun_path));
      }
      if( file_isdir(g.zSockName, ExtFILE)!=0 ){
        if( !file_issocket(g.zSockName) ){
          fossil_fatal("cannot name socket \"%s\" because another object"
                       " with that name already exists", g.zSockName);
        }else{
          unlink(g.zSockName);
        }
      }
      uxaddr.sun_family = AF_UNIX;
      strncpy(uxaddr.sun_path, g.zSockName, sizeof(uxaddr.sun_path)-1);
      listener = socket(AF_UNIX, SOCK_STREAM, 0);
      if( listener<0 ){
        fossil_fatal("unable to create a unix socket named %s",
                     g.zSockName);
      }



      /* Set the access permission for the new socket.  Default to 0660.
      ** But use an alternative specified by --socket-mode if available.
      ** Do this before bind() to avoid a race condition. */
      if( g.zSockMode ){
        file_set_mode(g.zSockName, listener, g.zSockMode, 0);
      }else{
        file_set_mode(g.zSockName, listener, "0660", 1);
      }
    }else{
      /* Initialize a TCP/IP socket on port iPort */
      if( (flags & HTTP_SERVER_LOCALHOST)!=0 && zIpAddr==0 ){
        /* Map all loopback to 127.0.0.1, since this is the easiest way
        ** to support OpenBSD and its limitations without burdening
        ** Linux and MacOS with lots of extra code and complication. */
        zIpAddr = "127.0.0.1";
      }
      if( zIpAddr ){
        if( strchr(zIpAddr,':') ){
          memset(&inaddr, 0, sizeof(inaddr));
          inaddr.sin6_family = AF_INET6;
          bIPv4 = 0;
          if( inet_pton(AF_INET6, zIpAddr, &inaddr.sin6_addr)==0 ){
            fossil_fatal("not a valid IPv6 address: %s", zIpAddr);
          }
        }else{
          memset(&inaddr4, 0, sizeof(inaddr4));
          inaddr4.sin_family = AF_INET;
          bIPv4 = 1;
          inaddr4.sin_addr.s_addr = inet_addr(zIpAddr);
          if( inaddr4.sin_addr.s_addr == INADDR_NONE ){
            fossil_fatal("not a valid IPv4 address: %s", zIpAddr);
          }
        }
      }else{
        /* Bind to any and all available IP addresses */
        memset(&inaddr, 0, sizeof(inaddr));
        inaddr.sin6_family = AF_INET6;
        inaddr.sin6_addr = in6addr_any;
        bIPv4 = 0;
      }
      if( bIPv4 ){
        inaddr4.sin_port = htons(iPort);
        listener = socket(AF_INET, SOCK_STREAM, 0);
      }else{
        inaddr.sin6_port = htons(iPort);
        listener = socket(AF_INET6, SOCK_STREAM, 0);
        allowBothIpV4andV6(listener);
      }
      if( listener<0 ){
        iPort++;
        continue;
      }
    }

    /* if we can't terminate nicely, at least allow the socket to be reused */
    setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
  
    if( flags & HTTP_SERVER_UNIXSOCKET ){
      rc = bind(listener, (struct sockaddr*)&uxaddr, sizeof(uxaddr));
      /* Set the owner of the socket if requested by --socket-owner.  This
      ** must wait until after bind(), after the filesystem object has been
      ** created.  See https://lkml.org/lkml/2004/11/1/84 and
      ** https://fossil-scm.org/forum/forumpost/7517680ef9684c57 */
      if( g.zSockOwner ){
        file_set_owner(g.zSockName, listener, g.zSockOwner);
      }



    }else if( bIPv4 ){













      rc = bind(listener, (struct sockaddr*)&inaddr4, sizeof(inaddr4));














    }else{



      rc = bind(listener, (struct sockaddr*)&inaddr, sizeof(inaddr));






    }




    if( rc<0 ){
      close(listener);
      iPort++;
      continue;
    }
    break;
  }
















  if( iPort>mxPort ){




    if( flags & HTTP_SERVER_UNIXSOCKET ){
      fossil_fatal("unable to listen on unix socket %s", zIpAddr);
    }else if( mnPort==mxPort ){
      fossil_fatal("unable to open listening socket on port %d", mnPort);

    }else{



      fossil_fatal("unable to open listening socket on any"








                   " port in the range %d..%d", mnPort, mxPort);

    }












  }





  if( iPort>mxPort ) return 1;
  listen(listener,10);



  if( flags & HTTP_SERVER_UNIXSOCKET ){
    fossil_print("Listening for %s requests on unix socket %s\n",
       (flags & HTTP_SERVER_SCGI)!=0 ? "SCGI" :
          g.httpUseSSL?"TLS-encrypted HTTPS":"HTTP",  g.zSockName);

  }else{




    fossil_print("Listening for %s requests on TCP port %d\n",

       (flags & HTTP_SERVER_SCGI)!=0 ? "SCGI" :






          g.httpUseSSL?"TLS-encrypted HTTPS":"HTTP",  iPort);
  }

  fflush(stdout);





  if( zBrowser && (flags & HTTP_SERVER_UNIXSOCKET)==0 ){
    assert( strstr(zBrowser,"%d")!=0 );
    zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort);
#if defined(__CYGWIN__)
    /* On Cygwin, we can do better than "echo" */
    if( fossil_strncmp(zBrowser, "echo ", 5)==0 ){
      wchar_t *wUrl = fossil_utf8_to_unicode(zBrowser+5);
      wUrl[wcslen(wUrl)-2] = 0; /* Strip terminating " &" */
      if( (size_t)ShellExecuteW(0, L"open", wUrl, 0, 0, 1)<33 ){
        fossil_warning("cannot start browser\n");
      }
    }else
#endif
    if( fossil_system(zBrowser)<0 ){
      fossil_warning("cannot start browser: %s\n", zBrowser);
    }
  }





  while( 1 ){
#if FOSSIL_MAX_CONNECTIONS>0
    while( nchildren>=FOSSIL_MAX_CONNECTIONS ){
      if( wait(0)>=0 ) nchildren--;
    }
#endif
    delay.tv_sec = 0;
    delay.tv_usec = 100000;
    FD_ZERO(&readfds);
    assert( listener>=0 );
    FD_SET( listener, &readfds);

    select( listener+1, &readfds, 0, 0, &delay);
    if( FD_ISSET(listener, &readfds) ){
      lenaddr = sizeof(inaddr);





      connection = accept(listener, (struct sockaddr*)&inaddr, &lenaddr);

      if( connection>=0 ){
        if( flags & HTTP_SERVER_NOFORK ){
          child = 0;
        }else{
          child = fork();
        }
        if( child!=0 ){
          if( child>0 ){
            nchildren++;
            nRequest++;
          }
          close(connection);
        }else{
          int nErr = 0, fd;
          g.zSockName = 0 /* avoid deleting the socket via atexit() */;
          close(0);
          fd = dup(connection);
          if( fd!=0 ) nErr++;
          close(1);
          fd = dup(connection);
          if( fd!=1 ) nErr++;
          if( 0 && !g.fAnyTrace ){
            close(2);
            fd = dup(connection);
            if( fd!=2 ) nErr++;
          }
          close(connection);
          close(listener);

          g.nPendingRequest = nchildren+1;
          g.nRequest = nRequest+1;
          return nErr;
        }
      }
    }
    /* Bury dead children */
    if( nchildren ){
      while(1){
        int iStatus = 0;
        pid_t x = waitpid(-1, &iStatus, WNOHANG);







>
>
|
|






|
|




>
|

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

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

















>
>
>
>
>









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







2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608


















































2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667

2668

2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691



2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732



2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832

2833
2834
2835
2836
2837
2838
2839
  const char *zIpAddr,      /* Bind to this IP address, if not null */
  int flags                 /* HTTP_SERVER_* flags */
){
#if defined(_WIN32)
  /* Use win32_http_server() instead */
  fossil_exit(1);
#else
  int listen4 = -1;            /* Main socket; IPv4 or unix-domain */
  int listen6 = -1;            /* Aux socket for corresponding IPv6 */
  int mxListen = -1;           /* Maximum of listen4 and listen6 */
  int connection;              /* An incoming connection */
  int nRequest = 0;            /* Number of requests handled so far */
  fd_set readfds;              /* Set of file descriptors for select() */
  socklen_t lenaddr;           /* Length of the inaddr structure */
  int child;                   /* PID of the child process */
  int nchildren = 0;           /* Number of child processes */
  struct timeval delay;        /* How long to wait inside select() */
  struct sockaddr_in6 inaddr6; /* Address for IPv6 */
  struct sockaddr_in inaddr4;  /* Address for IPv4 */
  struct sockaddr_un uxaddr;   /* The address for unix-domain sockets */
  int opt = 1;                 /* setsockopt flag */
  int rc;                      /* Result code from system calls */
  int iPort = mnPort;          /* Port to try to use */
  const char *zRequestType;    /* Type of requests to listen for */


  if( flags & HTTP_SERVER_SCGI ){
    zRequestType = "SCGI";
  }else if( g.httpUseSSL ){
    zRequestType = "TLS-encrypted HTTPS";
  }else{
    zRequestType = "HTTP";
  }

  if( flags & HTTP_SERVER_UNIXSOCKET ){
    /* CASE 1:  A unix socket named g.zSockName.  After creation, set the
    **          permissions on the new socket to g.zSockMode and make the
    **          owner of the socket be g.zSockOwner.
    */
    assert( g.zSockName!=0 );
    memset(&uxaddr, 0, sizeof(uxaddr));
    if( strlen(g.zSockName)>sizeof(uxaddr.sun_path) ){
      fossil_fatal("name of unix socket too big: %s\nmax size: %d\n",
                   g.zSockName, (int)sizeof(uxaddr.sun_path));
    }
    if( file_isdir(g.zSockName, ExtFILE)!=0 ){
      if( !file_issocket(g.zSockName) ){
        fossil_fatal("cannot name socket \"%s\" because another object"
                     " with that name already exists", g.zSockName);
      }else{
        unlink(g.zSockName);
      }
    }
    uxaddr.sun_family = AF_UNIX;
    strncpy(uxaddr.sun_path, g.zSockName, sizeof(uxaddr.sun_path)-1);
    listen4 = socket(AF_UNIX, SOCK_STREAM, 0);
    if( listen4<0 ){
      fossil_fatal("unable to create a unix socket named %s",
                   g.zSockName);
    }
    mxListen = listen4;
    listen6 = -1;

    /* Set the access permission for the new socket.  Default to 0660.
    ** But use an alternative specified by --socket-mode if available.
    ** Do this before bind() to avoid a race condition. */
    if( g.zSockMode ){
      file_set_mode(g.zSockName, listen4, g.zSockMode, 0);
    }else{
      file_set_mode(g.zSockName, listen4, "0660", 1);
    }


















































    rc = bind(listen4, (struct sockaddr*)&uxaddr, sizeof(uxaddr));
    /* Set the owner of the socket if requested by --socket-owner.  This
    ** must wait until after bind(), after the filesystem object has been
    ** created.  See https://lkml.org/lkml/2004/11/1/84 and
    ** https://fossil-scm.org/forum/forumpost/7517680ef9684c57 */
    if( g.zSockOwner ){
      file_set_owner(g.zSockName, listen4, g.zSockOwner);
    }
    fossil_print("Listening for %s requests on unix socket %s\n",
                 zRequestType, g.zSockName);
    fflush(stdout);
  }else if( zIpAddr && strchr(zIpAddr,':')!=0 ){
    /* CASE 2: TCP on IPv6 IP address specified by zIpAddr and on port iPort.
    */
    assert( mnPort==mxPort );
    memset(&inaddr6, 0, sizeof(inaddr6));
    inaddr6.sin6_family = AF_INET6;
    inaddr6.sin6_port = htons(iPort);
    if( inet_pton(AF_INET6, zIpAddr, &inaddr6.sin6_addr)==0 ){
      fossil_fatal("not a valid IPv6 address: %s", zIpAddr);
    }
    listen6 = socket(AF_INET6, SOCK_STREAM, 0);
    if( listen6>0 ){
      opt = 1;
      setsockopt(listen6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
      rc = bind(listen6, (struct sockaddr*)&inaddr6, sizeof(inaddr6));
      if( rc<0 ){
        close(listen6);
        listen6 = -1;
      }
    }
    if( listen6<0 ){
      fossil_fatal("cannot open a listening socket on [%s]:%d",
                   zIpAddr, mnPort);
    }
    mxListen = listen6;
    listen4 = -1;
    fossil_print("Listening for %s requests on [%s]:%d\n",
                 zRequestType, zIpAddr, iPort);
    fflush(stdout);
  }else if( zIpAddr && zIpAddr[0] ){
    /* CASE 3: TCP on IPv4 IP address specified by zIpAddr and on port iPort.
    */
    assert( mnPort==mxPort );
    memset(&inaddr4, 0, sizeof(inaddr4));
    inaddr4.sin_family = AF_INET;
    inaddr4.sin_port = htons(iPort);
    if( strcmp(zIpAddr, "localhost")==0 ) zIpAddr = "127.0.0.1";
    inaddr4.sin_addr.s_addr = inet_addr(zIpAddr);
    if( inaddr4.sin_addr.s_addr == INADDR_NONE ){
      fossil_fatal("not a valid IPv4 address: %s", zIpAddr);
    }
    listen4 = socket(AF_INET, SOCK_STREAM, 0);
    if( listen4>0 ){
      setsockopt(listen4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
      rc = bind(listen4, (struct sockaddr*)&inaddr4, sizeof(inaddr4));
      if( rc<0 ){
        close(listen6);
        listen4 = -1;

      }

    }
    if( listen4<0 ){
      fossil_fatal("cannot open a listening socket on %s:%d",
                   zIpAddr, mnPort);
    }
    mxListen = listen4;
    listen6 = -1;
    fossil_print("Listening for %s requests on TCP port %s:%d\n",
                 zRequestType, zIpAddr, iPort);
    fflush(stdout);
  }else{
    /* CASE 4: Listen on all available IP addresses, or on only loopback
    **         addresses (if HTTP_SERVER_LOCALHOST).  The TCP port is the
    **         first available in the range of mnPort..mxPort.  Listen
    **         on both IPv4 and IPv6, if possible.  The TCP port scan is done
    **         on IPv4.
    */
    while( iPort<=mxPort ){
      const char *zProto;
      memset(&inaddr4, 0, sizeof(inaddr4));
      inaddr4.sin_family = AF_INET;
      inaddr4.sin_port = htons(iPort);
      if( flags & HTTP_SERVER_LOCALHOST ){



        inaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
      }else{
        inaddr4.sin_addr.s_addr = htonl(INADDR_ANY);
      }
      listen4 = socket(AF_INET, SOCK_STREAM, 0);
      if( listen4>0 ){
        setsockopt(listen4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
        rc = bind(listen4, (struct sockaddr*)&inaddr4, sizeof(inaddr4));
        if( rc<0 ){
          close(listen4);
          listen4 = -1;
        }
      }
      if( listen4<0 ){
        iPort++;
        continue;
      }
      mxListen = listen4;

      /* If we get here, that means we found an open TCP port at iPort for
      ** IPv4.  Try to set up a corresponding IPv6 socket on the same port.
      */
      memset(&inaddr6, 0, sizeof(inaddr6));
      inaddr6.sin6_family = AF_INET6;
      inaddr6.sin6_port = htons(iPort);
      if( flags & HTTP_SERVER_LOCALHOST ){
        inaddr6.sin6_addr = in6addr_loopback;
      }else{
        inaddr6.sin6_addr = in6addr_any;
      }
      listen6 = socket(AF_INET6, SOCK_STREAM, 0);
      if( listen6>0 ){
        setsockopt(listen6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
        setsockopt(listen6, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
        rc = bind(listen6, (struct sockaddr*)&inaddr6, sizeof(inaddr6));
        if( rc<0 ){
          close(listen6);
          listen6 = -1;
        }
      }
      if( listen6<0 ){



        zProto = "IPv4 only";
      }else{
        zProto = "IPv4 and IPv6";
        if( listen6>listen4 ) mxListen = listen6;
      }

      fossil_print("Listening for %s requests on TCP port %s%d, %s\n",
                   zRequestType, 
                   (flags & HTTP_SERVER_LOCALHOST)!=0 ? "localhost:" : "",
                   iPort, zProto);
      fflush(stdout);
      break;
    }
    if( iPort>mxPort ){
      fossil_fatal("no available TCP ports in the range %d..%d",
                   mnPort, mxPort);
    }
  }

  /* If we get to this point, that means there is at least one listening
  ** socket on either listen4 or listen6 and perhaps on both. */
  assert( listen4>0 || listen6>0 );
  if( listen4>0 ) listen(listen4,10);
  if( listen6>0 ) listen(listen6,10);
  if( zBrowser && (flags & HTTP_SERVER_UNIXSOCKET)==0 ){
    assert( strstr(zBrowser,"%d")!=0 );
    zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort);
#if defined(__CYGWIN__)
    /* On Cygwin, we can do better than "echo" */
    if( fossil_strncmp(zBrowser, "echo ", 5)==0 ){
      wchar_t *wUrl = fossil_utf8_to_unicode(zBrowser+5);
      wUrl[wcslen(wUrl)-2] = 0; /* Strip terminating " &" */
      if( (size_t)ShellExecuteW(0, L"open", wUrl, 0, 0, 1)<33 ){
        fossil_warning("cannot start browser\n");
      }
    }else
#endif
    if( fossil_system(zBrowser)<0 ){
      fossil_warning("cannot start browser: %s\n", zBrowser);
    }
  }

  /* What for incomming requests.  For each request, fork() a child process
  ** to deal with that request.  The child process returns.  The parent
  ** keeps on listening and never returns.
  */
  while( 1 ){
#if FOSSIL_MAX_CONNECTIONS>0
    while( nchildren>=FOSSIL_MAX_CONNECTIONS ){
      if( wait(0)>=0 ) nchildren--;
    }
#endif
    delay.tv_sec = 0;
    delay.tv_usec = 100000;
    FD_ZERO(&readfds);
    assert( listen4>0 || listen6>0 );
    if( listen4>0 ) FD_SET( listen4, &readfds);
    if( listen6>0 ) FD_SET( listen6, &readfds);
    select( mxListen+1, &readfds, 0, 0, &delay);
    if( listen4>0 && FD_ISSET(listen4, &readfds) ){
      lenaddr = sizeof(inaddr4);
      connection = accept(listen4, (struct sockaddr*)&inaddr4, &lenaddr);
    }else if( listen6>0 && FD_ISSET(listen6, &readfds) ){
      lenaddr = sizeof(inaddr6);
      connection = accept(listen6, (struct sockaddr*)&inaddr6, &lenaddr);
    }else{
      connection = -1;
    }
    if( connection>=0 ){
      if( flags & HTTP_SERVER_NOFORK ){
        child = 0;
      }else{
        child = fork();
      }
      if( child!=0 ){
        if( child>0 ){
          nchildren++;
          nRequest++;
        }
        close(connection);
      }else{
        int nErr = 0, fd;
        g.zSockName = 0 /* avoid deleting the socket via atexit() */;
        close(0);
        fd = dup(connection);
        if( fd!=0 ) nErr++;
        close(1);
        fd = dup(connection);
        if( fd!=1 ) nErr++;
        if( 0 && !g.fAnyTrace ){
          close(2);
          fd = dup(connection);
          if( fd!=2 ) nErr++;
        }
        close(connection);
        if( listen4>0 ) close(listen4);
        if( listen6>0 ) close(listen6);
        g.nPendingRequest = nchildren+1;
        g.nRequest = nRequest+1;
        return nErr;

      }
    }
    /* Bury dead children */
    if( nchildren ){
      while(1){
        int iStatus = 0;
        pid_t x = waitpid(-1, &iStatus, WNOHANG);
Changes to src/db.c.
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
  db_end_transaction(0);
  if( zTemplate ) db_detach("settingSrc");
  if( zProjectName ) fossil_print("project-name: %s\n", zProjectName);
  if( zProjectDesc ) fossil_print("project-description: %s\n", zProjectDesc);
  fossil_print("project-id: %s\n", db_get("project-code", 0));
  fossil_print("server-id:  %s\n", db_get("server-code", 0));
  zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
  fossil_print("admin-user: %s (initial password is \"%s\")\n",
               g.zLogin, zPassword);
  hash_user_password(g.zLogin);
}

/*
** SQL functions for debugging.
**







|







3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
  db_end_transaction(0);
  if( zTemplate ) db_detach("settingSrc");
  if( zProjectName ) fossil_print("project-name: %s\n", zProjectName);
  if( zProjectDesc ) fossil_print("project-description: %s\n", zProjectDesc);
  fossil_print("project-id: %s\n", db_get("project-code", 0));
  fossil_print("server-id:  %s\n", db_get("server-code", 0));
  zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
  fossil_print("admin-user: %s (initial remote-access password is \"%s\")\n",
               g.zLogin, zPassword);
  hash_user_password(g.zLogin);
}

/*
** SQL functions for debugging.
**
Changes to src/default.css.
749
750
751
752
753
754
755

756
757
758
759
760
761
762
}
body.tkt div.content ol.tkt-changes > li:target > ol {
  border-left: 1px solid gold;
}
body.cpage-ckout .file-change-line,
body.cpage-info .file-change-line,
body.cpage-vinfo .file-change-line,

body.cpage-vdiff .file-change-line {
  margin-top: 16px;
  margin-bottom: 16px;
  margin-right: 1em /* keep it from nudging right up against the scrollbar-reveal zone */;
  display: flex;
  flex-direction: row;
  justify-content: space-between;







>







749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
}
body.tkt div.content ol.tkt-changes > li:target > ol {
  border-left: 1px solid gold;
}
body.cpage-ckout .file-change-line,
body.cpage-info .file-change-line,
body.cpage-vinfo .file-change-line,
body.cpage-ci .file-change-line,
body.cpage-vdiff .file-change-line {
  margin-top: 16px;
  margin-bottom: 16px;
  margin-right: 1em /* keep it from nudging right up against the scrollbar-reveal zone */;
  display: flex;
  flex-direction: row;
  justify-content: space-between;
Changes to src/doc.c.
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
  /* The file is now contained in the filebody blob.  Deliver the
  ** file to the user
  */
  zMime = nMiss==0 ? P("mimetype") : 0;
  if( zMime==0 ){
    zMime = mimetype_from_name(zName);
  }
  Th_Store("doc_name", zName);
  if( vid ){
    Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'"
                                       "  FROM blob WHERE rid=%d", vid));
    Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event"
                                    " WHERE objid=%d AND type='ci'", vid));
  }
  cgi_check_for_malice();







|







1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
  /* The file is now contained in the filebody blob.  Deliver the
  ** file to the user
  */
  zMime = nMiss==0 ? P("mimetype") : 0;
  if( zMime==0 ){
    zMime = mimetype_from_name(zName);
  }
  Th_StoreUnsafe("doc_name", zName);
  if( vid ){
    Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'"
                                       "  FROM blob WHERE rid=%d", vid));
    Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event"
                                    " WHERE objid=%d AND type='ci'", vid));
  }
  cgi_check_for_malice();
Changes to src/info.c.
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
    const char *zComment;
    const char *zDate;
    const char *zOrigDate;
    int okWiki = 0;
    Blob wiki_read_links = BLOB_INITIALIZER;
    Blob wiki_add_links = BLOB_INITIALIZER;

    Th_Store("current_checkin", zName);
    style_header("Check-in [%S]", zUuid);
    login_anonymous_available();
    zEUser = db_text(0,
                   "SELECT value FROM tagxref"
                   " WHERE tagid=%d AND rid=%d AND tagtype>0",
                    TAG_USER, rid);
    zEComment = db_text(0,







|







949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
    const char *zComment;
    const char *zDate;
    const char *zOrigDate;
    int okWiki = 0;
    Blob wiki_read_links = BLOB_INITIALIZER;
    Blob wiki_add_links = BLOB_INITIALIZER;

    Th_StoreUnsafe("current_checkin", zName);
    style_header("Check-in [%S]", zUuid);
    login_anonymous_available();
    zEUser = db_text(0,
                   "SELECT value FROM tagxref"
                   " WHERE tagid=%d AND rid=%d AND tagtype>0",
                    TAG_USER, rid);
    zEComment = db_text(0,
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
  }
  if( diffType!=2 ){
    @ %z(chref("button","%R/%s/%T?diff=2%s",zPage,zName,zW))\
    @ Side-by-Side&nbsp;Diff</a>
  }
  if( diffType!=0 ){
    if( *zW ){
      @ %z(chref("button","%R/%s/%T",zPage,zName))
      @ Show&nbsp;Whitespace&nbsp;Changes</a>
    }else{
      @ %z(chref("button","%R/%s/%T?w",zPage,zName))
      @ Ignore&nbsp;Whitespace</a>
    }
  }
  if( zParent ){
    @ %z(chref("button","%R/vpatch?from=%!S&to=%!S",zParent,zUuid))
    @ Patch</a>
  }







|


|







1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
  }
  if( diffType!=2 ){
    @ %z(chref("button","%R/%s/%T?diff=2%s",zPage,zName,zW))\
    @ Side-by-Side&nbsp;Diff</a>
  }
  if( diffType!=0 ){
    if( *zW ){
      @ %z(chref("button","%R/%s/%T?diff=%d",zPage,zName,diffType))
      @ Show&nbsp;Whitespace&nbsp;Changes</a>
    }else{
      @ %z(chref("button","%R/%s/%T?diff=%d&w",zPage,zName,diffType))
      @ Ignore&nbsp;Whitespace</a>
    }
  }
  if( zParent ){
    @ %z(chref("button","%R/vpatch?from=%!S&to=%!S",zParent,zUuid))
    @ Patch</a>
  }
Changes to src/login.c.
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
  ** This feature allows the "fossil ui" command to give the user
  ** full access rights without having to log in.
  */
  zIpAddr = PD("REMOTE_ADDR","nil");
  if( ( cgi_is_loopback(zIpAddr)
       || (g.fSshClient & CGI_SSH_CLIENT)!=0 )
   && g.useLocalauth
   && db_get_int("localauth",0)==0
   && P("HTTPS")==0
  ){
    char *zSeed;
    if( g.localOpen ) zLogin = db_lget("default-user",0);
    if( zLogin!=0 ){
      uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zLogin);
    }else{







|







1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
  ** This feature allows the "fossil ui" command to give the user
  ** full access rights without having to log in.
  */
  zIpAddr = PD("REMOTE_ADDR","nil");
  if( ( cgi_is_loopback(zIpAddr)
       || (g.fSshClient & CGI_SSH_CLIENT)!=0 )
   && g.useLocalauth
   && db_get_boolean("localauth",0)==0
   && P("HTTPS")==0
  ){
    char *zSeed;
    if( g.localOpen ) zLogin = db_lget("default-user",0);
    if( zLogin!=0 ){
      uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zLogin);
    }else{
Changes to src/main.c.
3762
3763
3764
3765
3766
3767
3768



3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781

3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
**     case=1           Issue a fossil_warning() while generating the page.
**     case=2           Extra db_begin_transaction()
**     case=3           Extra db_end_transaction()
**     case=4           Error during SQL processing
**     case=5           Call the segfault handler
**     case=6           Call webpage_assert()
**     case=7           Call webpage_error()



*/
void test_warning_page(void){
  int iCase = atoi(PD("case","0"));
  int i;
  login_check_credentials();
  if( !g.perm.Admin ){
    login_needed(0);
    return;
  }
  style_set_current_feature("test");
  style_header("Warning Test Page");
  style_submenu_element("Error Log","%R/errorlog");
  if( iCase<1 || iCase>4 ){

    @ <p>Generate a message to the <a href="%R/errorlog">error log</a>
    @ by clicking on one of the following cases:
  }else{
    @ <p>This is the test page for case=%d(iCase).  All possible cases:
  }
  for(i=1; i<=8; i++){
    @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
  }
  @ </p>
  @ <p><ol>
  @ <li value='1'> Call fossil_warning()
  if( iCase==1 ){
    fossil_warning("Test warning message from /test-warning");







>
>
>












|
>
|
|
<
<
<
|







3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787



3788
3789
3790
3791
3792
3793
3794
3795
**     case=1           Issue a fossil_warning() while generating the page.
**     case=2           Extra db_begin_transaction()
**     case=3           Extra db_end_transaction()
**     case=4           Error during SQL processing
**     case=5           Call the segfault handler
**     case=6           Call webpage_assert()
**     case=7           Call webpage_error()
**     case=8           Simulate a timeout
**     case=9           Simulate a TH1 XSS vulnerability
**     case=10          Simulate a TH1 SQL-injection vulnerability
*/
void test_warning_page(void){
  int iCase = atoi(PD("case","0"));
  int i;
  login_check_credentials();
  if( !g.perm.Admin ){
    login_needed(0);
    return;
  }
  style_set_current_feature("test");
  style_header("Warning Test Page");
  style_submenu_element("Error Log","%R/errorlog");
  @ <p>This page will generate various kinds of errors to test Fossil's
  @ reaction.  Depending on settings, a message might be written
  @ into the <a href="%R/errorlog">error log</a>.  Click on
  @ one of the following hyperlinks to generate a simulated error:



  for(i=1; i<=10; i++){
    @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
  }
  @ </p>
  @ <p><ol>
  @ <li value='1'> Call fossil_warning()
  if( iCase==1 ){
    fossil_warning("Test warning message from /test-warning");
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829



















3830
3831
3832
3833
3834
  if( iCase==5 ){
    sigsegv_handler(0);
  }
  @ <li value='6'> call webpage_assert(0)
  if( iCase==6 ){
    webpage_assert( 5==7 );
  }
  @ <li value='7'> call webpage_error()"
  if( iCase==7 ){
    cgi_reset_content();
    webpage_error("Case 7 from /test-warning");
  }
  @ <li value='8'> simulated timeout"
  if( iCase==8 ){
    fossil_set_timeout(1);
    cgi_reset_content();
    sqlite3_sleep(1100);



















  }
  @ </ol>
  @ <p>End of test</p>
  style_finish_page();
}







|




|




>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>





3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
  if( iCase==5 ){
    sigsegv_handler(0);
  }
  @ <li value='6'> call webpage_assert(0)
  if( iCase==6 ){
    webpage_assert( 5==7 );
  }
  @ <li value='7'> call webpage_error()
  if( iCase==7 ){
    cgi_reset_content();
    webpage_error("Case 7 from /test-warning");
  }
  @ <li value='8'> simulated timeout
  if( iCase==8 ){
    fossil_set_timeout(1);
    cgi_reset_content();
    sqlite3_sleep(1100);
  }
  @ <li value='9'> simulated TH1 XSS vulnerability
  @ <li value='10'> simulated TH1 SQL-injection vulnerability
  if( iCase==9 || iCase==10 ){
    const char *zR;
    int n, rc;
    static const char *zTH1[] = {
       /* case 9 */  "html [taint {<b>XSS</b>}]",
       /* case 10 */ "query [taint {SELECT 'SQL-injection' AS msg}] {\n"
                     "  html \"<b>[htmlize $msg]</b>\"\n"
                     "}"
    };
    rc = Th_Eval(g.interp, 0, zTH1[iCase==10], -1);
    zR = Th_GetResult(g.interp, &n);
    if( rc==TH_OK ){
      @ <pre class="th1result">%h(zR)</pre>
    }else{
      @ <pre class="th1error">%h(zR)</pre>
    }
  }
  @ </ol>
  @ <p>End of test</p>
  style_finish_page();
}
Changes to src/markdown_html.c.
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
   && (rPikVar = atof(zPikVar))>=0.1
   && rPikVar<10.0
  ){
    blob_appendf(&bSrc, "fontscale = %.13g\n", rPikVar);
  }
  blob_append(&bSrc, zSrc, nSrc)
    /*have to dup input to ensure a NUL-terminated source string */;
  pikchr_process(blob_str(&bSrc), pikFlags, 0, ob);
  blob_reset(&bSrc);
}

/* Invoked for `...` blocks where there are nSep grave accents in a
** row that serve as the delimiter.  According to CommonMark:
**
**   *  https://spec.commonmark.org/0.29/#fenced-code-blocks







|







694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
   && (rPikVar = atof(zPikVar))>=0.1
   && rPikVar<10.0
  ){
    blob_appendf(&bSrc, "fontscale = %.13g\n", rPikVar);
  }
  blob_append(&bSrc, zSrc, nSrc)
    /*have to dup input to ensure a NUL-terminated source string */;
  pikchr_process(blob_str(&bSrc), pikFlags, ob);
  blob_reset(&bSrc);
}

/* Invoked for `...` blocks where there are nSep grave accents in a
** row that serve as the delimiter.  According to CommonMark:
**
**   *  https://spec.commonmark.org/0.29/#fenced-code-blocks
Changes to src/pikchrshow.c.
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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
74
75
76
77
78
79
80
#if INTERFACE
/* These are described in pikchr_process()'s docs. */
/* The first two must match the values from pikchr.c */
#define PIKCHR_PROCESS_PLAINTEXT_ERRORS  0x0001
#define PIKCHR_PROCESS_DARK_MODE         0x0002
/* end of flags supported directly by pikchr() */
#define PIKCHR_PROCESS_PASSTHROUGH       0x0003   /* Pass through these flags */
#define PIKCHR_PROCESS_TH1               0x0004
#define PIKCHR_PROCESS_TH1_NOSVG         0x0008
#define PIKCHR_PROCESS_NONCE             0x0010
#define PIKCHR_PROCESS_ERR_PRE           0x0020
#define PIKCHR_PROCESS_SRC               0x0040
#define PIKCHR_PROCESS_DIV               0x0080
#define PIKCHR_PROCESS_DIV_INDENT        0x0100
#define PIKCHR_PROCESS_DIV_CENTER        0x0200
#define PIKCHR_PROCESS_DIV_FLOAT_LEFT    0x0400
#define PIKCHR_PROCESS_DIV_FLOAT_RIGHT   0x0800
#define PIKCHR_PROCESS_DIV_TOGGLE        0x1000
#define PIKCHR_PROCESS_DIV_SOURCE        0x2000
#define PIKCHR_PROCESS_DIV_SOURCE_INLINE 0x4000
#endif

/*
** Processes a pikchr script, optionally with embedded TH1, and
** produces HTML code for it. zIn is the NUL-terminated input
** script. pikFlags may be a bitmask of any of the PIKCHR_PROCESS_xxx
** flags documented below. thFlags may be a bitmask of any of the
** TH_INIT_xxx and/or TH_R2B_xxx flags. Output is sent to pOut,
** appending to it without modifying any prior contents.
**
** Returns 0 on success, 1 if TH1 processing failed, or 2 if pikchr
** processing failed. In either case, the error message (if any) from
** TH1 or pikchr will be appended to pOut.
**
** pikFlags flag descriptions:
**
** - PIKCHR_PROCESS_TH1 means to run zIn through TH1, using the TH1
** init flags specified in the 3rd argument. If thFlags is non-0 then
** this flag is assumed even if it is not specified.
**
** - PIKCHR_PROCESS_TH1_NOSVG means that processing stops after the
** TH1 eval step, thus the output will be (presumably) a
** TH1-generated/processed pikchr script (or whatever else the TH1
** outputs). If this flag is set, PIKCHR_PROCESS_TH1 is assumed even
** if it is not specified.
**
** All of the remaining flags listed below are ignored if
** PIKCHR_PROCESS_TH1_NOSVG is specified!
**
** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV
** element which specifies a max-width style value based on the SVG's
** calculated size. This flag has multiple mutually exclusive forms:
**
**  - PIKCHR_PROCESS_DIV uses default element alignment.
**  - PIKCHR_PROCESS_DIV_INDENT indents the div.
**  - PIKCHR_PROCESS_DIV_CENTER centers the div.







<
<














<
|

|
<
<

|
|
|



<
<
<
<
<
<
<
<
<
<
<
<
<







25
26
27
28
29
30
31


32
33
34
35
36
37
38
39
40
41
42
43
44
45

46
47
48


49
50
51
52
53
54
55













56
57
58
59
60
61
62
#if INTERFACE
/* These are described in pikchr_process()'s docs. */
/* The first two must match the values from pikchr.c */
#define PIKCHR_PROCESS_PLAINTEXT_ERRORS  0x0001
#define PIKCHR_PROCESS_DARK_MODE         0x0002
/* end of flags supported directly by pikchr() */
#define PIKCHR_PROCESS_PASSTHROUGH       0x0003   /* Pass through these flags */


#define PIKCHR_PROCESS_NONCE             0x0010
#define PIKCHR_PROCESS_ERR_PRE           0x0020
#define PIKCHR_PROCESS_SRC               0x0040
#define PIKCHR_PROCESS_DIV               0x0080
#define PIKCHR_PROCESS_DIV_INDENT        0x0100
#define PIKCHR_PROCESS_DIV_CENTER        0x0200
#define PIKCHR_PROCESS_DIV_FLOAT_LEFT    0x0400
#define PIKCHR_PROCESS_DIV_FLOAT_RIGHT   0x0800
#define PIKCHR_PROCESS_DIV_TOGGLE        0x1000
#define PIKCHR_PROCESS_DIV_SOURCE        0x2000
#define PIKCHR_PROCESS_DIV_SOURCE_INLINE 0x4000
#endif

/*

** Processes a pikchr script. zIn is the NUL-terminated input
** script. pikFlags may be a bitmask of any of the PIKCHR_PROCESS_xxx
** flags documented below. Output is sent to pOut,


**
** Returns 0 on success, or non-zero if pikchr processing failed.
** In either case, the error message (if any) from pikchr will be
** appended to pOut.
**
** pikFlags flag descriptions:
**













** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV
** element which specifies a max-width style value based on the SVG's
** calculated size. This flag has multiple mutually exclusive forms:
**
**  - PIKCHR_PROCESS_DIV uses default element alignment.
**  - PIKCHR_PROCESS_DIV_INDENT indents the div.
**  - PIKCHR_PROCESS_DIV_CENTER centers the div.
114
115
116
117
118
119
120
121
122
123
124


125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
** PIKCHR_PROCESS_DIV_SOURCE or PIKCHR_PROCESS_DIV_SOURCE_INLINE is
** set, this flag is automatically implied.
**
** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting
** error report is wrapped in a PRE element, else it is retained
** as-is (intended only for console output).
*/
int pikchr_process(const char * zIn, int pikFlags, int thFlags,
                   Blob * pOut){
  Blob bIn = empty_blob;
  int isErr = 0;


  const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags)
    ? safe_html_nonce(1) : 0;

  if(!(PIKCHR_PROCESS_DIV & pikFlags)
     /* If any DIV_xxx flags are set, set DIV */
     && (PIKCHR_PROCESS_DIV_INDENT
         | PIKCHR_PROCESS_DIV_CENTER
         | PIKCHR_PROCESS_DIV_FLOAT_RIGHT
         | PIKCHR_PROCESS_DIV_FLOAT_LEFT
         | PIKCHR_PROCESS_DIV_SOURCE
         | PIKCHR_PROCESS_DIV_SOURCE_INLINE
         | PIKCHR_PROCESS_DIV_TOGGLE
         ) & pikFlags){
    pikFlags |= PIKCHR_PROCESS_DIV;
  }
  if(!(PIKCHR_PROCESS_TH1 & pikFlags)
     /* If any TH1_xxx flags are set, set TH1 */
     && (PIKCHR_PROCESS_TH1_NOSVG & pikFlags || thFlags!=0)){
    pikFlags |= PIKCHR_PROCESS_TH1;
  }
  if(zNonce){
    blob_appendf(pOut, "%s\n", zNonce);
  }
  if(PIKCHR_PROCESS_TH1 & pikFlags){
    Blob out = empty_blob;
    isErr = Th_RenderToBlob(zIn, &out, thFlags)
      ? 1 : 0;
    if(isErr){
      blob_append(pOut, blob_str(&out), blob_size(&out));
      blob_reset(&out);
    }else{
      bIn = out;
    }
  }else{
    blob_init(&bIn, zIn, -1);
  }
  if(!isErr){
    if(PIKCHR_PROCESS_TH1_NOSVG & pikFlags){
      blob_append(pOut, blob_str(&bIn), blob_size(&bIn));
    }else{
      int w = 0, h = 0;
      const char * zContent = blob_str(&bIn);
      char *zOut;
      zOut = pikchr(zContent, "pikchr",
                    0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH),
                    &w, &h);
      if( w>0 && h>0 ){
        const char * zClassToggle = "";
        const char * zClassSource = "";
        const char * zWrapperClass = "";
        if(PIKCHR_PROCESS_DIV & pikFlags){
          if(PIKCHR_PROCESS_DIV_CENTER & pikFlags){
            zWrapperClass = " center";
          }else if(PIKCHR_PROCESS_DIV_INDENT & pikFlags){
            zWrapperClass = " indent";
          }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){
            zWrapperClass = " float-left";
          }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){
            zWrapperClass = " float-right";
          }
          if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){
            zClassToggle = " toggle";
          }
          if(PIKCHR_PROCESS_DIV_SOURCE_INLINE & pikFlags){
            if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
              zClassSource = " source source-inline";
            }else{
              zClassSource = " source-inline";
            }
            pikFlags |= PIKCHR_PROCESS_SRC;
          }else if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
            zClassSource = " source";
            pikFlags |= PIKCHR_PROCESS_SRC;
          }
          blob_appendf(pOut,"<div class='pikchr-wrapper"
                       "%s%s%s'>"
                       "<div class=\"pikchr-svg\" "
                       "style=\"max-width:%dpx\">\n",
                       zWrapperClass/*safe-for-%s*/,
                       zClassToggle/*safe-for-%s*/,
                       zClassSource/*safe-for-%s*/, w);
        }
        blob_append(pOut, zOut, -1);
        if(PIKCHR_PROCESS_DIV & pikFlags){
          blob_append(pOut, "</div>\n", 7);
        }
        if(PIKCHR_PROCESS_SRC & pikFlags){
          static int counter = 0;
          ++counter;
          blob_appendf(pOut, "<div class='pikchr-src'>"
                       "<pre id='pikchr-src-%d'>%h</pre>"
                       "<span class='hidden'>"
                       "<a href='%R/pikchrshow?fromSession' "
                       "class='pikchr-src-pikchrshow' target='_new-%d' "
                       "data-pikchrid='pikchr-src-%d' "
                       "title='Open this pikchr in /pikchrshow'"
                       ">&rarr; /pikchrshow</a></span>"
                       "</div>\n",
                       counter, blob_str(&bIn), counter, counter);
        }
        if(PIKCHR_PROCESS_DIV & pikFlags){
          blob_append(pOut, "</div>\n", 7);
        }
      }else{
        isErr = 2;
        if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
          blob_append(pOut, "<pre class='error'>\n", 20);
        }
        blob_appendf(pOut, "%h", zOut);
        if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
          blob_append(pOut, "\n</pre>\n", 8);
        }
      }
      fossil_free(zOut);
    }
  }
  if(zNonce){
    blob_appendf(pOut, "%s\n", zNonce);
  }
  blob_reset(&bIn);
  return isErr;
}

/*
** Legacy impl of /pikchrshow. pikchrshow_page() will delegate to
** this one if the "legacy" or "ajax" request arguments are set.
**







|
<
<

>
>















<
<
<
<
<



<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<
<



<







96
97
98
99
100
101
102
103


104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121





122
123
124




















125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195


196
197
198

199
200
201
202
203
204
205
** PIKCHR_PROCESS_DIV_SOURCE or PIKCHR_PROCESS_DIV_SOURCE_INLINE is
** set, this flag is automatically implied.
**
** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting
** error report is wrapped in a PRE element, else it is retained
** as-is (intended only for console output).
*/
int pikchr_process(const char *zIn, int pikFlags, Blob * pOut){


  int isErr = 0;
  int w = 0, h = 0;
  char *zOut;
  const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags)
    ? safe_html_nonce(1) : 0;

  if(!(PIKCHR_PROCESS_DIV & pikFlags)
     /* If any DIV_xxx flags are set, set DIV */
     && (PIKCHR_PROCESS_DIV_INDENT
         | PIKCHR_PROCESS_DIV_CENTER
         | PIKCHR_PROCESS_DIV_FLOAT_RIGHT
         | PIKCHR_PROCESS_DIV_FLOAT_LEFT
         | PIKCHR_PROCESS_DIV_SOURCE
         | PIKCHR_PROCESS_DIV_SOURCE_INLINE
         | PIKCHR_PROCESS_DIV_TOGGLE
         ) & pikFlags){
    pikFlags |= PIKCHR_PROCESS_DIV;
  }





  if(zNonce){
    blob_appendf(pOut, "%s\n", zNonce);
  }




















  zOut = pikchr(zIn, "pikchr",
                0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH),
                &w, &h);
  if( w>0 && h>0 ){
    const char * zClassToggle = "";
    const char * zClassSource = "";
    const char * zWrapperClass = "";
    if(PIKCHR_PROCESS_DIV & pikFlags){
      if(PIKCHR_PROCESS_DIV_CENTER & pikFlags){
        zWrapperClass = " center";
      }else if(PIKCHR_PROCESS_DIV_INDENT & pikFlags){
        zWrapperClass = " indent";
      }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){
        zWrapperClass = " float-left";
      }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){
        zWrapperClass = " float-right";
      }
      if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){
        zClassToggle = " toggle";
      }
      if(PIKCHR_PROCESS_DIV_SOURCE_INLINE & pikFlags){
        if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
          zClassSource = " source source-inline";
        }else{
          zClassSource = " source-inline";
        }
        pikFlags |= PIKCHR_PROCESS_SRC;
      }else if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
        zClassSource = " source";
        pikFlags |= PIKCHR_PROCESS_SRC;
      }
      blob_appendf(pOut,"<div class='pikchr-wrapper"
                   "%s%s%s'>"
                   "<div class=\"pikchr-svg\" "
                   "style=\"max-width:%dpx\">\n",
                   zWrapperClass/*safe-for-%s*/,
                   zClassToggle/*safe-for-%s*/,
                   zClassSource/*safe-for-%s*/, w);
    }
    blob_append(pOut, zOut, -1);
    if(PIKCHR_PROCESS_DIV & pikFlags){
      blob_append(pOut, "</div>\n", 7);
    }
    if(PIKCHR_PROCESS_SRC & pikFlags){
      static int counter = 0;
      ++counter;
      blob_appendf(pOut, "<div class='pikchr-src'>"
                   "<pre id='pikchr-src-%d'>%h</pre>"
                   "<span class='hidden'>"
                   "<a href='%R/pikchrshow?fromSession' "
                   "class='pikchr-src-pikchrshow' target='_new-%d' "
                   "data-pikchrid='pikchr-src-%d' "
                   "title='Open this pikchr in /pikchrshow'"
                   ">&rarr; /pikchrshow</a></span>"
                   "</div>\n",
                   counter, zIn, counter, counter);
    }
    if(PIKCHR_PROCESS_DIV & pikFlags){
      blob_append(pOut, "</div>\n", 7);
    }
  }else{
    isErr = 2;
    if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
      blob_append(pOut, "<pre class='error'>\n", 20);
    }
    blob_appendf(pOut, "%h", zOut);
    if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
      blob_append(pOut, "\n</pre>\n", 8);
    }
  }
  fossil_free(zOut);


  if(zNonce){
    blob_appendf(pOut, "%s\n", zNonce);
  }

  return isErr;
}

/*
** Legacy impl of /pikchrshow. pikchrshow_page() will delegate to
** this one if the "legacy" or "ajax" request arguments are set.
**
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
  if(P("ajax")!=0){
    /* Called from the JS-side preview updater.
       TODO: respond with JSON instead.*/
    cgi_set_content_type("text/html");
    if(zContent && *zContent){
      Blob out = empty_blob;
      const int isErr =
        pikchr_process(zContent, pikFlags, 0, &out);
      if(isErr){
        cgi_printf_header("x-pikchrshow-is-error: %d\r\n", isErr);
      }
      CX("%b", &out);
      blob_reset(&out);
    }else{
      CX("<pre>No content! Nothing to render</pre>");







|







231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
  if(P("ajax")!=0){
    /* Called from the JS-side preview updater.
       TODO: respond with JSON instead.*/
    cgi_set_content_type("text/html");
    if(zContent && *zContent){
      Blob out = empty_blob;
      const int isErr =
        pikchr_process(zContent, pikFlags, &out);
      if(isErr){
        cgi_printf_header("x-pikchrshow-is-error: %d\r\n", isErr);
      }
      CX("%b", &out);
      blob_reset(&out);
    }else{
      CX("<pre>No content! Nothing to render</pre>");
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
    CX("<fieldset id='pikchrshow-output-wrapper'>"); {
      CX("<legend></legend>"
         /* Reminder: Firefox does not properly flexbox a LEGEND
            element, always flowing it in column mode. */);
      CX("<div id='pikchrshow-output'>");
      if(*zContent){
        Blob out = empty_blob;
        pikchr_process(zContent, pikFlags, 0, &out);
        CX("%b", &out);
        blob_reset(&out);
      } CX("</div>"/*#pikchrshow-output*/);
    } CX("</fieldset>"/*#pikchrshow-output-wrapper*/);
  } CX("</div>"/*sbs-wrapper*/);
  builtin_fossil_js_bundle_or("fetch", "copybutton", "popupwidget",
                              "storage", "pikchr", NULL);







|







336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
    CX("<fieldset id='pikchrshow-output-wrapper'>"); {
      CX("<legend></legend>"
         /* Reminder: Firefox does not properly flexbox a LEGEND
            element, always flowing it in column mode. */);
      CX("<div id='pikchrshow-output'>");
      if(*zContent){
        Blob out = empty_blob;
        pikchr_process(zContent, pikFlags, &out);
        CX("%b", &out);
        blob_reset(&out);
      } CX("</div>"/*#pikchrshow-output*/);
    } CX("</fieldset>"/*#pikchrshow-output-wrapper*/);
  } CX("</div>"/*sbs-wrapper*/);
  builtin_fossil_js_bundle_or("fetch", "copybutton", "popupwidget",
                              "storage", "pikchr", NULL);
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
**    -div-source  Set the 'source' CSS class on the div, which tells
**                 CSS to hide the SVG and reveal the source by default.
**
**    -src       Store the input pikchr's source code in the output as
**               a separate element adjacent to the SVG one. Implied
**               by -div-source.
**
**
**    -th        Process the input using TH1 before passing it to pikchr
**
**    -th-novar  Disable $var and $<var> TH1 processing. Use this if the
**               pikchr script uses '$' for its own purposes and that
**               causes issues. This only affects parsing of '$' outside
**               of TH1 script blocks. Code in such blocks is unaffected.
**
**    -th-nosvg  When using -th, output the post-TH1'd script
**               instead of the pikchr-rendered output
**
**    -th-trace  Trace TH1 execution (for debugging purposes)
**
**    -dark      Change pikchr colors to assume a dark-mode theme.
**
**
** The -div-indent/center/left/right flags may not be combined.
**
** TH1-related Notes and Caveats:
**
** If the -th flag is used, this command must open a fossil database
** for certain functionality to work (via a check-out or the -R REPO
** flag). If opening a db fails, execution will continue but any TH1
** commands which require a db will trigger a fatal error.
**
** In Fossil skins, TH1 variables in the form $varName are expanded
** as-is and those in the form $<varName> are htmlized in the
** resulting output. This processor disables the htmlizing step, so $x
** and $<x> are equivalent unless the TH1-processed pikchr script
** invokes the TH1 command [enable_htmlify 1] to enable it. Normally
** that option will interfere with pikchr output, however, e.g. by
** HTML-encoding double-quotes.
**
** Many of the fossil-installed TH1 functions simply do not make any
** sense for pikchr scripts.
*/
void pikchr_cmd(void){
  Blob bIn = empty_blob;
  Blob bOut = empty_blob;
  const char * zInfile = "-";
  const char * zOutfile = "-";
  const int fTh1 = find_option("th",0,0)!=0;
  const int fNosvg = find_option("th-nosvg",0,0)!=0;
  int isErr = 0;
  int pikFlags = find_option("src",0,0)!=0
    ? PIKCHR_PROCESS_SRC : 0;
  u32 fThFlags = TH_INIT_NO_ENCODE
    | (find_option("th-novar",0,0)!=0 ? TH_R2B_NO_VARS : 0);

  Th_InitTraceLog()/*processes -th-trace flag*/;

  if(find_option("div",0,0)!=0){
    pikFlags |= PIKCHR_PROCESS_DIV;
  }else if(find_option("div-indent",0,0)!=0){
    pikFlags |= PIKCHR_PROCESS_DIV_INDENT;
  }else if(find_option("div-center",0,0)!=0){
    pikFlags |= PIKCHR_PROCESS_DIV_CENTER;







<
<
<
<
<
<
<
<
<
<
<
<
<




<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






<
<



<
<
<
<







513
514
515
516
517
518
519













520
521
522
523


















524
525
526
527
528
529


530
531
532




533
534
535
536
537
538
539
**    -div-source  Set the 'source' CSS class on the div, which tells
**                 CSS to hide the SVG and reveal the source by default.
**
**    -src       Store the input pikchr's source code in the output as
**               a separate element adjacent to the SVG one. Implied
**               by -div-source.
**













**    -dark      Change pikchr colors to assume a dark-mode theme.
**
**
** The -div-indent/center/left/right flags may not be combined.


















*/
void pikchr_cmd(void){
  Blob bIn = empty_blob;
  Blob bOut = empty_blob;
  const char * zInfile = "-";
  const char * zOutfile = "-";


  int isErr = 0;
  int pikFlags = find_option("src",0,0)!=0
    ? PIKCHR_PROCESS_SRC : 0;





  if(find_option("div",0,0)!=0){
    pikFlags |= PIKCHR_PROCESS_DIV;
  }else if(find_option("div-indent",0,0)!=0){
    pikFlags |= PIKCHR_PROCESS_DIV_INDENT;
  }else if(find_option("div-center",0,0)!=0){
    pikFlags |= PIKCHR_PROCESS_DIV_CENTER;
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
  if(g.argc>2){
    zInfile = g.argv[2];
  }
  if(g.argc>3){
    zOutfile = g.argv[3];
  }
  blob_read_from_file(&bIn, zInfile, ExtFILE);
  if(fTh1){
    db_find_and_open_repository(OPEN_ANY_SCHEMA | OPEN_OK_NOT_FOUND, 0)
      /* ^^^ needed for certain TH1 functions to work */;
    pikFlags |= PIKCHR_PROCESS_TH1;
    if(fNosvg) pikFlags |= PIKCHR_PROCESS_TH1_NOSVG;
  }
  isErr = pikchr_process(blob_str(&bIn), pikFlags,
                         fTh1 ? fThFlags : 0, &bOut);
  if(isErr){
    fossil_fatal("%s ERROR:%c%b", 1==isErr ? "TH1" : "pikchr",
                 1==isErr ? ' ' : '\n',
                 &bOut);
  }else{
    blob_write_to_file(&bOut, zOutfile);
  }
  Th_PrintTraceLog();
  blob_reset(&bIn);
  blob_reset(&bOut);
}







<
<
<
<
<
<
|
<

<
<
|



<



559
560
561
562
563
564
565






566

567


568
569
570
571

572
573
574
  if(g.argc>2){
    zInfile = g.argv[2];
  }
  if(g.argc>3){
    zOutfile = g.argv[3];
  }
  blob_read_from_file(&bIn, zInfile, ExtFILE);






  isErr = pikchr_process(blob_str(&bIn), pikFlags, &bOut);

  if(isErr){


    fossil_fatal("pikchr ERROR: %b", &bOut);
  }else{
    blob_write_to_file(&bOut, zOutfile);
  }

  blob_reset(&bIn);
  blob_reset(&bOut);
}
Changes to src/printf.c.
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
        fprintf(out, "%s=%s\n", azEnv[i], p);
        fossil_path_free(p);
      }else if( (z = P(azEnv[i]))!=0 && z[0]!=0 ){
        fprintf(out, "%s=%s\n", azEnv[i], z);
      }
    }
  }
  fclose(out);
}

/*
** The following variable becomes true while processing a fatal error
** or a panic.  If additional "recursive-fatal" errors occur while
** shutting down, the recursive errors are silently ignored.
*/







|







1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
        fprintf(out, "%s=%s\n", azEnv[i], p);
        fossil_path_free(p);
      }else if( (z = P(azEnv[i]))!=0 && z[0]!=0 ){
        fprintf(out, "%s=%s\n", azEnv[i], z);
      }
    }
  }
  if( out!=stderr ) fclose(out);
}

/*
** The following variable becomes true while processing a fatal error
** or a panic.  If additional "recursive-fatal" errors occur while
** shutting down, the recursive errors are silently ignored.
*/
Changes to src/repolist.c.
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
  pRepo->isValid = 1;
  sqlite3_finalize(pStmt);
finish_repo_list:
  g.dbIgnoreErrors--;
  sqlite3_close(db);
}

/*
** SETTING: show-repolist-desc                  boolean default=off
**
** If the value of this setting is "1" globally, then the repository-list
** page will show the description of each repository.  This setting only
** has effect when it is in the global setting database.
*/
/*
** SETTING: show-repolist-lg                    boolean default=off
**
** If the value of this setting is "1" globally, then the repository-list
** page will show the login-group for each repository.  This setting only
** has effect when it is in the global setting database.
*/

/*
** Generate a web-page that lists all repositories located under the
** g.zRepositoryName directory and return non-zero.
**
** For the special case when g.zRepositoryName is a non-chroot-jail "/",
** compose the list using the "repo:" entries in the global_config
** table of the configuration database.  These entries comprise all







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







99
100
101
102
103
104
105















106
107
108
109
110
111
112
  pRepo->isValid = 1;
  sqlite3_finalize(pStmt);
finish_repo_list:
  g.dbIgnoreErrors--;
  sqlite3_close(db);
}
















/*
** Generate a web-page that lists all repositories located under the
** g.zRepositoryName directory and return non-zero.
**
** For the special case when g.zRepositoryName is a non-chroot-jail "/",
** compose the list using the "repo:" entries in the global_config
** table of the configuration database.  These entries comprise all
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172

173
174
175
176
177
178
179
  int bShowLg = 0;     /* True to show the login-group column */

  assert( g.db==0 );
  zShow = P("FOSSIL_REPOLIST_SHOW");
  if( zShow ){
    bShowDesc = strstr(zShow,"description")!=0;
    bShowLg = strstr(zShow,"login-group")!=0;
  }else if( db_open_config(1, 1)
     && db_table_exists("configdb", "global_config")
  ){
    bShowDesc = db_int(bShowDesc, "SELECT value FROM global_config"
                                  " WHERE name='show-repolist-desc'");
    bShowLg = db_int(bShowLg, "SELECT value FROM global_config"
                              " WHERE name='show-repolist-lg'");
  }
  blob_init(&html, 0, 0);
  if( fossil_strcmp(g.zRepositoryName,"/")==0 && !g.fJail ){
    /* For the special case of the "repository directory" being "/",
    ** show all of the repositories named in the ~/.fossil database.
    **
    ** On unix systems, then entries are of the form "repo:/home/..."
    ** and on Windows systems they are like on unix, starting with a "/"
    ** or they can begin with a drive letter: "repo:C:/Users/...".  In either
    ** case, we want returned path to omit any initial "/".
    */

    db_multi_exec(
       "CREATE TEMP VIEW sfile AS"
       "  SELECT ltrim(substr(name,6),'/') AS 'pathname' FROM global_config"
       "   WHERE name GLOB 'repo:*'"
    );
    allRepo = 1;
  }else{







<
<
<
<
<
<
<











>







133
134
135
136
137
138
139







140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
  int bShowLg = 0;     /* True to show the login-group column */

  assert( g.db==0 );
  zShow = P("FOSSIL_REPOLIST_SHOW");
  if( zShow ){
    bShowDesc = strstr(zShow,"description")!=0;
    bShowLg = strstr(zShow,"login-group")!=0;







  }
  blob_init(&html, 0, 0);
  if( fossil_strcmp(g.zRepositoryName,"/")==0 && !g.fJail ){
    /* For the special case of the "repository directory" being "/",
    ** show all of the repositories named in the ~/.fossil database.
    **
    ** On unix systems, then entries are of the form "repo:/home/..."
    ** and on Windows systems they are like on unix, starting with a "/"
    ** or they can begin with a drive letter: "repo:C:/Users/...".  In either
    ** case, we want returned path to omit any initial "/".
    */
    db_open_config(1, 0);
    db_multi_exec(
       "CREATE TEMP VIEW sfile AS"
       "  SELECT ltrim(substr(name,6),'/') AS 'pathname' FROM global_config"
       "   WHERE name GLOB 'repo:*'"
    );
    allRepo = 1;
  }else{
Changes to src/security_audit.c.
98
99
100
101
102
103
104

105
106
107
108
109
110
111
  const char *zAnonCap;      /* Capabilities of user "anonymous" and "nobody" */
  const char *zDevCap;       /* Capabilities of user group "developer" */
  const char *zReadCap;      /* Capabilities of user group "reader" */
  const char *zPubPages;     /* GLOB pattern for public pages */
  const char *zSelfCap;      /* Capabilities of self-registered users */
  int hasSelfReg = 0;        /* True if able to self-register */
  const char *zPublicUrl;    /* Canonical access URL */

  Blob cmd;
  char *z;
  int n, i;
  CapabilityString *pCap;
  char **azCSP;              /* Parsed content security policy */

  login_check_credentials();







>







98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
  const char *zAnonCap;      /* Capabilities of user "anonymous" and "nobody" */
  const char *zDevCap;       /* Capabilities of user group "developer" */
  const char *zReadCap;      /* Capabilities of user group "reader" */
  const char *zPubPages;     /* GLOB pattern for public pages */
  const char *zSelfCap;      /* Capabilities of self-registered users */
  int hasSelfReg = 0;        /* True if able to self-register */
  const char *zPublicUrl;    /* Canonical access URL */
  const char *zVulnReport;   /* The vuln-report setting */
  Blob cmd;
  char *z;
  int n, i;
  CapabilityString *pCap;
  char **azCSP;              /* Parsed content security policy */

  login_check_credentials();
360
361
362
363
364
365
366












367
368
369
370
371
372
373
  /* The strict-manifest-syntax setting should be on. */
  if( db_get_boolean("strict-manifest-syntax",1)==0 ){
    @ <li><p><b>WARNING:</b>
    @ The "strict-manifest-syntax"  flag is off.  This is a security
    @ risk.  Turn this setting on (its default) to protect the users
    @ of this repository.
  }













  /* Obsolete:  */
  if( hasAnyCap(zAnonCap, "d") ||
      hasAnyCap(zDevCap,  "d") ||
      hasAnyCap(zReadCap, "d") ){
    @ <li><p><b>WARNING:</b>
    @ One or more users has the <a







>
>
>
>
>
>
>
>
>
>
>
>







361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
  /* The strict-manifest-syntax setting should be on. */
  if( db_get_boolean("strict-manifest-syntax",1)==0 ){
    @ <li><p><b>WARNING:</b>
    @ The "strict-manifest-syntax"  flag is off.  This is a security
    @ risk.  Turn this setting on (its default) to protect the users
    @ of this repository.
  }

  zVulnReport = db_get("vuln-report","log");
  if( fossil_strcmp(zVulnReport,"block")!=0
   && fossil_strcmp(zVulnReport,"fatal")!=0
  ){
    @ <li><p><b>WARNING:</b>
    @ The <a href="%R/help?cmd=vuln-report">vuln-report setting</a>
    @ has a value of "%h(zVulnReport)". This disables defenses against
    @ XSS or SQL-injection vulnerabilities caused by coding errors in
    @ custom TH1 scripts.  For the best security, change
    @ the value of the vuln-report setting to "block" or "fatal".
  }

  /* Obsolete:  */
  if( hasAnyCap(zAnonCap, "d") ||
      hasAnyCap(zDevCap,  "d") ||
      hasAnyCap(zReadCap, "d") ){
    @ <li><p><b>WARNING:</b>
    @ One or more users has the <a
808
809
810
811
812
813
814
815
816
817
818
819
820

821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841

842
843
844
845
846
847
848

/*
** WEBPAGE: errorlog
**
** Show the content of the error log.  Only the administrator can view
** this page.
**
**    y=0x01          Show only hack attempts
**    y=0x02          Show only panics and assertion faults
**    y=0x04          Show hung backoffice processes
**    y=0x08          Show POST requests from a different origin
**    y=0x10          Show SQLITE_AUTH and similar
**    y=0x20          Show SMTP error reports

**    y=0x40          Show other uncategorized messages
**
** If y is omitted or is zero, a count of the various message types is
** shown.
*/
void errorlog_page(void){
  i64 szFile;
  FILE *in;
  char *zLog;
  const char *zType = P("y");
  static const int eAllTypes = 0x7f;
  long eType = 0;
  int bOutput = 0;
  int prevWasTime = 0;
  int nHack = 0;
  int nPanic = 0;
  int nOther = 0;
  int nHang = 0;
  int nXPost = 0;
  int nAuth = 0;
  int nSmtp = 0;

  char z[10000];
  char zTime[10000];

  login_check_credentials();
  if( !g.perm.Admin ){
    login_needed(0);
    return;







|
|
|
|
|
|
>
|









|










>







821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863

/*
** WEBPAGE: errorlog
**
** Show the content of the error log.  Only the administrator can view
** this page.
**
**    y=0x001          Show only hack attempts
**    y=0x002          Show only panics and assertion faults
**    y=0x004          Show hung backoffice processes
**    y=0x008          Show POST requests from a different origin
**    y=0x010          Show SQLITE_AUTH and similar
**    y=0x020          Show SMTP error reports
**    y=0x040          Show TH1 vulnerability reports
**    y=0x800          Show other uncategorized messages
**
** If y is omitted or is zero, a count of the various message types is
** shown.
*/
void errorlog_page(void){
  i64 szFile;
  FILE *in;
  char *zLog;
  const char *zType = P("y");
  static const int eAllTypes = 0x87f;
  long eType = 0;
  int bOutput = 0;
  int prevWasTime = 0;
  int nHack = 0;
  int nPanic = 0;
  int nOther = 0;
  int nHang = 0;
  int nXPost = 0;
  int nAuth = 0;
  int nSmtp = 0;
  int nVuln = 0;
  char z[10000];
  char zTime[10000];

  login_check_credentials();
  if( !g.perm.Admin ){
    login_needed(0);
    return;
915
916
917
918
919
920
921



922
923
924
925
926
927
928
    if( eType & 0x10 ){
      @ <li>SQLITE_AUTH and similar errors
    }
    if( eType & 0x20 ){
      @ <li>SMTP malfunctions
    }
    if( eType & 0x40 ){



      @ <li>Other uncategorized messages
    }
    @ </ul>
  }
  @ <hr>
  if( eType ){
    @ <pre>







>
>
>







930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
    if( eType & 0x10 ){
      @ <li>SQLITE_AUTH and similar errors
    }
    if( eType & 0x20 ){
      @ <li>SMTP malfunctions
    }
    if( eType & 0x40 ){
      @ <li>TH1 vulnerabilities
    }
    if( eType & 0x800 ){
      @ <li>Other uncategorized messages
    }
    @ </ul>
  }
  @ <hr>
  if( eType ){
    @ <pre>
951
952
953
954
955
956
957




958
959
960
961
962
963
964
965
966
      }else
      if( sqlite3_strglob("SECURITY: authorizer blocks*",z)==0
       || sqlite3_strglob("warning: SQLITE_AUTH*",z)==0
      ){
        bOutput = (eType & 0x10)!=0;
        nAuth++;
      }else




      {
        bOutput = (eType & 0x40)!=0;
        nOther++;
      }
      if( bOutput ){
        @ %h(zTime)\
      }
    }
    if( strncmp(z, "--------", 8)==0 ){







>
>
>
>

|







969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
      }else
      if( sqlite3_strglob("SECURITY: authorizer blocks*",z)==0
       || sqlite3_strglob("warning: SQLITE_AUTH*",z)==0
      ){
        bOutput = (eType & 0x10)!=0;
        nAuth++;
      }else
      if( strncmp(z,"possible", 8)==0 && strstr(z,"tainted")!=0 ){
        bOutput = (eType & 0x40)!=0;
        nVuln++;
      }else
      {
        bOutput = (eType & 0x800)!=0;
        nOther++;
      }
      if( bOutput ){
        @ %h(zTime)\
      }
    }
    if( strncmp(z, "--------", 8)==0 ){
976
977
978
979
980
981
982
983
984
985
986
987
988
989




990
991
992
993
994
995
996
    }
  }
  fclose(in);
  if( eType ){
    @ </pre>
  }
  if( eType==0 ){
    int nNonHack = nPanic + nHang + nAuth + nSmtp + nOther;
    int nTotal = nNonHack + nHack + nXPost;
    @ <p><table border="a" cellspacing="0" cellpadding="5">
    if( nPanic>0 ){
      @ <tr><td align="right">%d(nPanic)</td>
      @     <td><a href="./errorlog?y=2">Panics</a></td>
    }




    if( nHack>0 ){
      @ <tr><td align="right">%d(nHack)</td>
      @     <td><a href="./errorlog?y=1">Hack Attempts</a></td>
    }
    if( nHang>0 ){
      @ <tr><td align="right">%d(nHang)</td>
      @     <td><a href="./errorlog?y=4">Hung Backoffice</a></td>







|






>
>
>
>







998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
    }
  }
  fclose(in);
  if( eType ){
    @ </pre>
  }
  if( eType==0 ){
    int nNonHack = nPanic + nHang + nAuth + nSmtp + nVuln + nOther;
    int nTotal = nNonHack + nHack + nXPost;
    @ <p><table border="a" cellspacing="0" cellpadding="5">
    if( nPanic>0 ){
      @ <tr><td align="right">%d(nPanic)</td>
      @     <td><a href="./errorlog?y=2">Panics</a></td>
    }
    if( nVuln>0 ){
      @ <tr><td align="right">%d(nVuln)</td>
      @     <td><a href="./errorlog?y=64">TH1 Vulnerabilities</a></td>
    }
    if( nHack>0 ){
      @ <tr><td align="right">%d(nHack)</td>
      @     <td><a href="./errorlog?y=1">Hack Attempts</a></td>
    }
    if( nHang>0 ){
      @ <tr><td align="right">%d(nHang)</td>
      @     <td><a href="./errorlog?y=4">Hung Backoffice</a></td>
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
    }
    if( nSmtp>0 ){
      @ <tr><td align="right">%d(nSmtp)</td>
      @     <td><a href="./errorlog?y=32">SMTP faults</a></td>
    }
    if( nOther>0 ){
      @ <tr><td align="right">%d(nOther)</td>
      @     <td><a href="./errorlog?y=64">Other</a></td>
    }
    @ <tr><td align="right">%d(nTotal)</td>
    if( nTotal>0 ){
      @     <td><a href="./errorlog?y=255">All Messages</a></td>
    }else{
      @     <td>All Messages</td>
    }
    @ </table>
  }
  style_finish_page();
}







|



|







1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
    }
    if( nSmtp>0 ){
      @ <tr><td align="right">%d(nSmtp)</td>
      @     <td><a href="./errorlog?y=32">SMTP faults</a></td>
    }
    if( nOther>0 ){
      @ <tr><td align="right">%d(nOther)</td>
      @     <td><a href="./errorlog?y=2048">Other</a></td>
    }
    @ <tr><td align="right">%d(nTotal)</td>
    if( nTotal>0 ){
      @     <td><a href="./errorlog?y=4095">All Messages</a></td>
    }else{
      @     <td>All Messages</td>
    }
    @ </table>
  }
  style_finish_page();
}
Changes to src/style.c.
742
743
744
745
746
747
748

749
750
751
752
753
754
755
756
757
  ** Do not overwrite the TH1 variable "default_csp" if it exists, as this
  ** allows it to be properly overridden via the TH1 setup script (i.e. it
  ** is evaluated before the header is rendered).
  */
  Th_MaybeStore("default_csp", zDfltCsp);
  fossil_free(zDfltCsp);
  Th_Store("nonce", zNonce);

  Th_Store("project_name", db_get("project-name","Unnamed Fossil Project"));
  Th_Store("project_description", db_get("project-description",""));
  if( zTitle ) Th_Store("title", html_lookalike(zTitle,-1));
  Th_Store("baseurl", g.zBaseURL);
  Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL);
  Th_Store("home", g.zTop);
  Th_Store("index_page", db_get("index-page","/home"));
  if( local_zCurrentPage==0 ) style_set_current_page("%T", g.zPath);
  Th_Store("current_page", local_zCurrentPage);







>
|
|







742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
  ** Do not overwrite the TH1 variable "default_csp" if it exists, as this
  ** allows it to be properly overridden via the TH1 setup script (i.e. it
  ** is evaluated before the header is rendered).
  */
  Th_MaybeStore("default_csp", zDfltCsp);
  fossil_free(zDfltCsp);
  Th_Store("nonce", zNonce);
  Th_StoreUnsafe("project_name",
                 db_get("project-name","Unnamed Fossil Project"));
  Th_StoreUnsafe("project_description", db_get("project-description",""));
  if( zTitle ) Th_Store("title", html_lookalike(zTitle,-1));
  Th_Store("baseurl", g.zBaseURL);
  Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL);
  Th_Store("home", g.zTop);
  Th_Store("index_page", db_get("index-page","/home"));
  if( local_zCurrentPage==0 ) style_set_current_page("%T", g.zPath);
  Th_Store("current_page", local_zCurrentPage);
Changes to src/th.c.
1
2
3
4
5
6
7
8
9
10
11






12
13
14
15
16
17
18

/*
** The implementation of the TH core. This file contains the parser, and
** the implementation of the interface in th.h.
*/

#include "config.h"
#include "th.h"
#include <string.h>
#include <assert.h>







/*
** Values used for element values in the tcl_platform array.
*/

#if !defined(TH_ENGINE)
#  define TH_ENGINE          "TH1"
#endif











>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

/*
** The implementation of the TH core. This file contains the parser, and
** the implementation of the interface in th.h.
*/

#include "config.h"
#include "th.h"
#include <string.h>
#include <assert.h>

/*
** External routines
*/
void fossil_panic(const char*,...);
void fossil_errorlog(const char*,...);

/*
** Values used for element values in the tcl_platform array.
*/

#if !defined(TH_ENGINE)
#  define TH_ENGINE          "TH1"
#endif
195
196
197
198
199
200
201

202
203
204
205
206
207
208
209
210
211
212
213








214
215
216
217
218
219
220
221
222
223
224
225

226
227
228

229
230
231
232
233

234
235
236
237
238
239

240
241
242
243
244
245
246

247
248
249
250
251
252
253

254
255
256
257
258
259
260
** The Buffer structure and the thBufferXXX() functions are used to make
** memory allocation easier when building up a result.
*/
struct Buffer {
  char *zBuf;
  int nBuf;
  int nBufAlloc;

};
typedef struct Buffer Buffer;
static void thBufferInit(Buffer *);
static void thBufferFree(Th_Interp *interp, Buffer *);

/*
** This version of memcpy() allows the first and second argument to
** be NULL as long as the number of bytes to copy is zero.
*/
static void th_memcpy(void *dest, const void *src, size_t n){
  if( n>0 ) memcpy(dest,src,n);
}









/*
** Append nAdd bytes of content copied from zAdd to the end of buffer
** pBuffer. If there is not enough space currently allocated, resize
** the allocation to make space.
*/
static void thBufferWriteResize(
  Th_Interp *interp,
  Buffer *pBuffer,
  const char *zAdd,
  int nAdd
){

  int nNew = (pBuffer->nBuf+nAdd)*2+32;
#if defined(TH_MEMDEBUG)
  char *zNew = (char *)Th_Malloc(interp, nNew);

  th_memcpy(zNew, pBuffer->zBuf, pBuffer->nBuf);
  Th_Free(interp, pBuffer->zBuf);
  pBuffer->zBuf = zNew;
#else
  int nOld = pBuffer->nBufAlloc;

  pBuffer->zBuf = Th_Realloc(interp, pBuffer->zBuf, nNew);
  memset(pBuffer->zBuf+nOld, 0, nNew-nOld);
#endif
  pBuffer->nBufAlloc = nNew;
  th_memcpy(&pBuffer->zBuf[pBuffer->nBuf], zAdd, nAdd);
  pBuffer->nBuf += nAdd;

}
static void thBufferWriteFast(
  Th_Interp *interp,
  Buffer *pBuffer,
  const char *zAdd,
  int nAdd
){

  if( pBuffer->nBuf+nAdd > pBuffer->nBufAlloc ){
    thBufferWriteResize(interp, pBuffer, zAdd, nAdd);
  }else{
    if( pBuffer->zBuf ){
      memcpy(pBuffer->zBuf + pBuffer->nBuf, zAdd, nAdd);
    }
    pBuffer->nBuf += nAdd;

  }
}
#define thBufferWrite(a,b,c,d) thBufferWriteFast(a,b,(const char *)c,d)

/*
** Add a single character to a buffer
*/







>












>
>
>
>
>
>
>
>










|

>



>





>






>





|

>

|





>







201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
** The Buffer structure and the thBufferXXX() functions are used to make
** memory allocation easier when building up a result.
*/
struct Buffer {
  char *zBuf;
  int nBuf;
  int nBufAlloc;
  int bTaint;
};
typedef struct Buffer Buffer;
static void thBufferInit(Buffer *);
static void thBufferFree(Th_Interp *interp, Buffer *);

/*
** This version of memcpy() allows the first and second argument to
** be NULL as long as the number of bytes to copy is zero.
*/
static void th_memcpy(void *dest, const void *src, size_t n){
  if( n>0 ) memcpy(dest,src,n);
}

/*
** An oversized string has been encountered.  Do not try to recover.
** Panic the process.
*/
void Th_OversizeString(void){
  fossil_panic("string too large. maximum size 286MB.");
}

/*
** Append nAdd bytes of content copied from zAdd to the end of buffer
** pBuffer. If there is not enough space currently allocated, resize
** the allocation to make space.
*/
static void thBufferWriteResize(
  Th_Interp *interp,
  Buffer *pBuffer,
  const char *zAdd,
  int nAddX
){
  int nAdd = TH1_LEN(nAddX);
  int nNew = (pBuffer->nBuf+nAdd)*2+32;
#if defined(TH_MEMDEBUG)
  char *zNew = (char *)Th_Malloc(interp, nNew);
  TH1_SIZECHECK(nNew);
  th_memcpy(zNew, pBuffer->zBuf, pBuffer->nBuf);
  Th_Free(interp, pBuffer->zBuf);
  pBuffer->zBuf = zNew;
#else
  int nOld = pBuffer->nBufAlloc;
  TH1_SIZECHECK(nNew);
  pBuffer->zBuf = Th_Realloc(interp, pBuffer->zBuf, nNew);
  memset(pBuffer->zBuf+nOld, 0, nNew-nOld);
#endif
  pBuffer->nBufAlloc = nNew;
  th_memcpy(&pBuffer->zBuf[pBuffer->nBuf], zAdd, nAdd);
  pBuffer->nBuf += nAdd;
  TH1_XFER_TAINT(pBuffer->bTaint, nAddX);
}
static void thBufferWriteFast(
  Th_Interp *interp,
  Buffer *pBuffer,
  const char *zAdd,
  int nAddX
){
  int nAdd = TH1_LEN(nAddX);
  if( pBuffer->nBuf+nAdd > pBuffer->nBufAlloc ){
    thBufferWriteResize(interp, pBuffer, zAdd, nAddX);
  }else{
    if( pBuffer->zBuf ){
      memcpy(pBuffer->zBuf + pBuffer->nBuf, zAdd, nAdd);
    }
    pBuffer->nBuf += nAdd;
    TH1_XFER_TAINT(pBuffer->bTaint, nAddX);
  }
}
#define thBufferWrite(a,b,c,d) thBufferWriteFast(a,b,(const char *)c,d)

/*
** Add a single character to a buffer
*/
702
703
704
705
706
707
708

709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
  Th_Interp *interp,
  const char *zWord,
  int nWord
){
  int rc = TH_OK;
  Buffer output;
  int i;


  thBufferInit(&output);

  if( nWord>1 && (zWord[0]=='{' && zWord[nWord-1]=='}') ){
    thBufferWrite(interp, &output, &zWord[1], nWord-2);
  }else{

    /* If the word is surrounded by double-quotes strip these away. */
    if( nWord>1 && (zWord[0]=='"' && zWord[nWord-1]=='"') ){
      zWord++;
      nWord -= 2;
    }

    for(i=0; rc==TH_OK && i<nWord; i++){
      int nGet;

      int (*xGet)(Th_Interp *, const char*, int, int *) = 0;
      int (*xSubst)(Th_Interp *, const char*, int) = 0;

      switch( zWord[i] ){
        case '\\':







>



|
|



|

|


|







723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
  Th_Interp *interp,
  const char *zWord,
  int nWord
){
  int rc = TH_OK;
  Buffer output;
  int i;
  int nn = TH1_LEN(nWord);

  thBufferInit(&output);

  if( nn>1 && (zWord[0]=='{' && zWord[nn-1]=='}') ){
    thBufferWrite(interp, &output, &zWord[1], nn-2);
  }else{

    /* If the word is surrounded by double-quotes strip these away. */
    if( nn>1 && (zWord[0]=='"' && zWord[nn-1]=='"') ){
      zWord++;
      nn -= 2;
    }

    for(i=0; rc==TH_OK && i<nn; i++){
      int nGet;

      int (*xGet)(Th_Interp *, const char*, int, int *) = 0;
      int (*xSubst)(Th_Interp *, const char*, int) = 0;

      switch( zWord[i] ){
        case '\\':
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
          }
        default: {
          thBufferAddChar(interp, &output, zWord[i]);
          continue; /* Go to the next iteration of the for(...) loop */
        }
      }

      rc = xGet(interp, &zWord[i], nWord-i, &nGet);
      if( rc==TH_OK ){
        rc = xSubst(interp, &zWord[i], nGet);
      }
      if( rc==TH_OK ){
        const char *zRes;
        int nRes;
        zRes = Th_GetResult(interp, &nRes);
        thBufferWrite(interp, &output, zRes, nRes);
        i += (nGet-1);
      }
    }
  }

  if( rc==TH_OK ){
    Th_SetResult(interp, output.zBuf, output.nBuf);
  }
  thBufferFree(interp, &output);
  return rc;
}

/*
** Return true if one of the following is true of the buffer pointed







|














|







763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
          }
        default: {
          thBufferAddChar(interp, &output, zWord[i]);
          continue; /* Go to the next iteration of the for(...) loop */
        }
      }

      rc = xGet(interp, &zWord[i], nn-i, &nGet);
      if( rc==TH_OK ){
        rc = xSubst(interp, &zWord[i], nGet);
      }
      if( rc==TH_OK ){
        const char *zRes;
        int nRes;
        zRes = Th_GetResult(interp, &nRes);
        thBufferWrite(interp, &output, zRes, nRes);
        i += (nGet-1);
      }
    }
  }

  if( rc==TH_OK ){
    Th_SetResult(interp, output.zBuf, output.nBuf|output.bTaint);
  }
  thBufferFree(interp, &output);
  return rc;
}

/*
** Return true if one of the following is true of the buffer pointed
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
  int rc = TH_OK;

  Buffer strbuf;
  Buffer lenbuf;
  int nCount = 0;

  const char *zInput = zList;
  int nInput = nList;

  thBufferInit(&strbuf);
  thBufferInit(&lenbuf);

  while( nInput>0 ){
    const char *zWord;
    int nWord;

    thNextSpace(interp, zInput, nInput, &nWord);
    zInput += nWord;
    nInput = nList-(zInput-zList);

    if( TH_OK!=(rc = thNextWord(interp, zInput, nInput, &nWord, 0))
     || TH_OK!=(rc = thSubstWord(interp, zInput, nWord))
    ){
      goto finish;
    }
    zInput = &zInput[nWord];
    nInput = nList-(zInput-zList);
    if( nWord>0 ){
      zWord = Th_GetResult(interp, &nWord);
      thBufferWrite(interp, &strbuf, zWord, nWord);
      thBufferAddChar(interp, &strbuf, 0);
      thBufferWrite(interp, &lenbuf, &nWord, sizeof(int));
      nCount++;
    }







|










|






|
|







846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
  int rc = TH_OK;

  Buffer strbuf;
  Buffer lenbuf;
  int nCount = 0;

  const char *zInput = zList;
  int nInput = TH1_LEN(nList);

  thBufferInit(&strbuf);
  thBufferInit(&lenbuf);

  while( nInput>0 ){
    const char *zWord;
    int nWord;

    thNextSpace(interp, zInput, nInput, &nWord);
    zInput += nWord;
    nInput = TH1_LEN(nList)-(zInput-zList);

    if( TH_OK!=(rc = thNextWord(interp, zInput, nInput, &nWord, 0))
     || TH_OK!=(rc = thSubstWord(interp, zInput, nWord))
    ){
      goto finish;
    }
    zInput = &zInput[TH1_LEN(nWord)];
    nInput = TH1_LEN(nList)-(zInput-zList);
    if( nWord>0 ){
      zWord = Th_GetResult(interp, &nWord);
      thBufferWrite(interp, &strbuf, zWord, nWord);
      thBufferAddChar(interp, &strbuf, 0);
      thBufferWrite(interp, &lenbuf, &nWord, sizeof(int));
      nCount++;
    }
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
    );
    anElem = (int *)&azElem[nCount];
    zElem = (char *)&anElem[nCount];
    th_memcpy(anElem, lenbuf.zBuf, lenbuf.nBuf);
    th_memcpy(zElem, strbuf.zBuf, strbuf.nBuf);
    for(i=0; i<nCount;i++){
      azElem[i] = zElem;
      zElem += (anElem[i] + 1);
    }
    *pazElem = azElem;
    *panElem = anElem;
  }
  if( pnCount ){
    *pnCount = nCount;
  }







|







892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
    );
    anElem = (int *)&azElem[nCount];
    zElem = (char *)&anElem[nCount];
    th_memcpy(anElem, lenbuf.zBuf, lenbuf.nBuf);
    th_memcpy(zElem, strbuf.zBuf, strbuf.nBuf);
    for(i=0; i<nCount;i++){
      azElem[i] = zElem;
      zElem += (TH1_LEN(anElem[i]) + 1);
    }
    *pazElem = azElem;
    *panElem = anElem;
  }
  if( pnCount ){
    *pnCount = nCount;
  }
892
893
894
895
896
897
898
899
900





901
902
903
904
905
906
907
/*
** Evaluate the th1 script contained in the string (zProgram, nProgram)
** in the current stack frame.
*/
static int thEvalLocal(Th_Interp *interp, const char *zProgram, int nProgram){
  int rc = TH_OK;
  const char *zInput = zProgram;
  int nInput = nProgram;






  while( rc==TH_OK && nInput ){
    Th_HashEntry *pEntry;
    int nSpace;
    const char *zFirst;

    char **argv;
    int *argl;







|

>
>
>
>
>







914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
/*
** Evaluate the th1 script contained in the string (zProgram, nProgram)
** in the current stack frame.
*/
static int thEvalLocal(Th_Interp *interp, const char *zProgram, int nProgram){
  int rc = TH_OK;
  const char *zInput = zProgram;
  int nInput = TH1_LEN(nProgram);

  if( TH1_TAINTED(nProgram)
   && Th_ReportTaint(interp, "script", zProgram, nProgram)
  ){
    return TH_ERROR;
  }
  while( rc==TH_OK && nInput ){
    Th_HashEntry *pEntry;
    int nSpace;
    const char *zFirst;

    char **argv;
    int *argl;
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
    */
    rc = thSplitList(interp, zFirst, zInput-zFirst, &argv, &argl, &argc);
    if( rc!=TH_OK ) continue;

    if( argc>0 ){

      /* Look up the command name in the command hash-table. */
      pEntry = Th_HashFind(interp, interp->paCmd, argv[0], argl[0], 0);
      if( !pEntry ){
        Th_ErrorMessage(interp, "no such command: ", argv[0], argl[0]);
        rc = TH_ERROR;
      }

      /* Call the command procedure. */
      if( rc==TH_OK ){
        Th_Command *p = (Th_Command *)(pEntry->pData);
        const char **azArg = (const char **)argv;







|

|







974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
    */
    rc = thSplitList(interp, zFirst, zInput-zFirst, &argv, &argl, &argc);
    if( rc!=TH_OK ) continue;

    if( argc>0 ){

      /* Look up the command name in the command hash-table. */
      pEntry = Th_HashFind(interp, interp->paCmd, argv[0], TH1_LEN(argl[0]),0);
      if( !pEntry ){
        Th_ErrorMessage(interp, "no such command: ", argv[0], TH1_LEN(argl[0]));
        rc = TH_ERROR;
      }

      /* Call the command procedure. */
      if( rc==TH_OK ){
        Th_Command *p = (Th_Command *)(pEntry->pData);
        const char **azArg = (const char **)argv;
1051
1052
1053
1054
1055
1056
1057


1058
1059
1060
1061
1062
1063
1064
  if( !interp->pFrame ){
    rc = TH_ERROR;
  }else{
    int nInput = nProgram;

    if( nInput<0 ){
      nInput = th_strlen(zProgram);


    }
    rc = thEvalLocal(interp, zProgram, nInput);
  }

  interp->pFrame = pSavedFrame;
  return rc;
}







>
>







1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
  if( !interp->pFrame ){
    rc = TH_ERROR;
  }else{
    int nInput = nProgram;

    if( nInput<0 ){
      nInput = th_strlen(zProgram);
    }else{
      nInput = TH1_LEN(nInput);
    }
    rc = thEvalLocal(interp, zProgram, nInput);
  }

  interp->pFrame = pSavedFrame;
  return rc;
}
1093
1094
1095
1096
1097
1098
1099


1100
1101
1102
1103
1104
1105
1106
  const char *zInner = 0;
  int nInner = 0;
  int isGlobal = 0;
  int i;

  if( nVarname<0 ){
    nVarname = th_strlen(zVarname);


  }
  nOuter = nVarname;

  /* If the variable name starts with "::", then do the lookup is in the
  ** uppermost (global) frame.
  */
  if( nVarname>2 && zVarname[0]==':' && zVarname[1]==':' ){







>
>







1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
  const char *zInner = 0;
  int nInner = 0;
  int isGlobal = 0;
  int i;

  if( nVarname<0 ){
    nVarname = th_strlen(zVarname);
  }else{
    nVarname = TH1_LEN(nVarname);
  }
  nOuter = nVarname;

  /* If the variable name starts with "::", then do the lookup is in the
  ** uppermost (global) frame.
  */
  if( nVarname>2 && zVarname[0]==':' && zVarname[1]==':' ){
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
    Th_ErrorMessage(interp, "no such variable:", zVar, nVar);
    return TH_ERROR;
  }

  return Th_SetResult(interp, pValue->zData, pValue->nData);
}

/*
** If interp has a variable with the given name, its value is returned
** and its length is returned via *nOut if nOut is not NULL.  If
** interp has no such var then NULL is returned without setting any
** error state and *nOut, if not NULL, is set to -1. The returned value
** is owned by the interpreter and may be invalidated the next time
** the interpreter is modified.
*/
const char * Th_MaybeGetVar(Th_Interp *interp, const char *zVarName,
                            int *nOut){
  Th_Variable *pValue;

  pValue = thFindValue(interp, zVarName, -1, 0, 0, 1, 0);
  if( !pValue || !pValue->zData ){
    if( nOut!=0 ) *nOut = -1;
    return NULL;
  }
  if( nOut!=0 ) *nOut = pValue->nData;
  return pValue->zData;
}

/*
** Return true if variable (zVar, nVar) exists.
*/
int Th_ExistsVar(Th_Interp *interp, const char *zVar, int nVar){
  Th_Variable *pValue = thFindValue(interp, zVar, nVar, 0, 1, 1, 0);
  return pValue && (pValue->zData || pValue->pHash);
}







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







1300
1301
1302
1303
1304
1305
1306





















1307
1308
1309
1310
1311
1312
1313
    Th_ErrorMessage(interp, "no such variable:", zVar, nVar);
    return TH_ERROR;
  }

  return Th_SetResult(interp, pValue->zData, pValue->nData);
}






















/*
** Return true if variable (zVar, nVar) exists.
*/
int Th_ExistsVar(Th_Interp *interp, const char *zVar, int nVar){
  Th_Variable *pValue = thFindValue(interp, zVar, nVar, 0, 1, 1, 0);
  return pValue && (pValue->zData || pValue->pHash);
}
1322
1323
1324
1325
1326
1327
1328

1329

1330
1331
1332
1333
1334
1335
1336


1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
  Th_Interp *interp,
  const char *zVar,
  int nVar,
  const char *zValue,
  int nValue
){
  Th_Variable *pValue;



  pValue = thFindValue(interp, zVar, nVar, 1, 0, 0, 0);
  if( !pValue ){
    return TH_ERROR;
  }

  if( nValue<0 ){
    nValue = th_strlen(zValue);


  }
  if( pValue->zData ){
    Th_Free(interp, pValue->zData);
    pValue->zData = 0;
  }

  assert(zValue || nValue==0);
  pValue->zData = Th_Malloc(interp, nValue+1);
  pValue->zData[nValue] = '\0';
  th_memcpy(pValue->zData, zValue, nValue);
  pValue->nData = nValue;

  return TH_OK;
}

/*
** Create a variable link so that accessing variable (zLocal, nLocal) is







>

>






|
>
>






|
|
|
|







1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
  Th_Interp *interp,
  const char *zVar,
  int nVar,
  const char *zValue,
  int nValue
){
  Th_Variable *pValue;
  int nn;

  nVar = TH1_LEN(nVar);
  pValue = thFindValue(interp, zVar, nVar, 1, 0, 0, 0);
  if( !pValue ){
    return TH_ERROR;
  }

  if( nValue<0 ){
    nn = th_strlen(zValue);
  }else{
    nn = TH1_LEN(nValue);
  }
  if( pValue->zData ){
    Th_Free(interp, pValue->zData);
    pValue->zData = 0;
  }

  assert(zValue || nn==0);
  pValue->zData = Th_Malloc(interp, nn+1);
  pValue->zData[nn] = '\0';
  th_memcpy(pValue->zData, zValue, nn);
  pValue->nData = nValue;

  return TH_OK;
}

/*
** Create a variable link so that accessing variable (zLocal, nLocal) is
1456
1457
1458
1459
1460
1461
1462


1463
1464
1465
1466
1467
1468
1469
** caller is responsible for eventually calling Th_Free() to free
** the returned buffer.
*/
char *th_strdup(Th_Interp *interp, const char *z, int n){
  char *zRes;
  if( n<0 ){
    n = th_strlen(z);


  }
  zRes = Th_Malloc(interp, n+1);
  th_memcpy(zRes, z, n);
  zRes[n] = '\0';
  return zRes;
}








>
>







1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
** caller is responsible for eventually calling Th_Free() to free
** the returned buffer.
*/
char *th_strdup(Th_Interp *interp, const char *z, int n){
  char *zRes;
  if( n<0 ){
    n = th_strlen(z);
  }else{
    n = TH1_LEN(n);
  }
  zRes = Th_Malloc(interp, n+1);
  th_memcpy(zRes, z, n);
  zRes[n] = '\0';
  return zRes;
}

1517
1518
1519
1520
1521
1522
1523

1524
1525
1526
1527
1528
1529
1530
1531
1532
1533

  if( n<0 ){
    n = th_strlen(z);
  }

  if( z && n>0 ){
    char *zResult;

    zResult = Th_Malloc(pInterp, n+1);
    th_memcpy(zResult, z, n);
    zResult[n] = '\0';
    pInterp->zResult = zResult;
    pInterp->nResult = n;
  }

  return TH_OK;
}








>
|
|
|







1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550

  if( n<0 ){
    n = th_strlen(z);
  }

  if( z && n>0 ){
    char *zResult;
    int nn = TH1_LEN(n);
    zResult = Th_Malloc(pInterp, nn+1);
    th_memcpy(zResult, z, nn);
    zResult[nn] = '\0';
    pInterp->zResult = zResult;
    pInterp->nResult = n;
  }

  return TH_OK;
}

1775
1776
1777
1778
1779
1780
1781
1782
1783


1784
1785
1786


1787
1788
1789
1790
1791
1792
1793
  int i;

  int hasSpecialChar = 0;  /* Whitespace or {}[]'" */
  int hasEscapeChar = 0;   /* '}' without matching '{' to the left or a '\\' */
  int nBrace = 0;

  output.zBuf = *pzList;
  output.nBuf = *pnList;
  output.nBufAlloc = output.nBuf;



  if( nElem<0 ){
    nElem = th_strlen(zElem);


  }
  if( output.nBuf>0 ){
    thBufferAddChar(interp, &output, ' ');
  }

  for(i=0; i<nElem; i++){
    char c = zElem[i];







|

>
>



>
>







1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
  int i;

  int hasSpecialChar = 0;  /* Whitespace or {}[]'" */
  int hasEscapeChar = 0;   /* '}' without matching '{' to the left or a '\\' */
  int nBrace = 0;

  output.zBuf = *pzList;
  output.nBuf = TH1_LEN(*pnList);
  output.nBufAlloc = output.nBuf;
  output.bTaint = 0;
  TH1_XFER_TAINT(output.bTaint, *pnList);

  if( nElem<0 ){
    nElem = th_strlen(zElem);
  }else{
    nElem = TH1_LEN(nElem);
  }
  if( output.nBuf>0 ){
    thBufferAddChar(interp, &output, ' ');
  }

  for(i=0; i<nElem; i++){
    char c = zElem[i];
1832
1833
1834
1835
1836
1837
1838
1839

1840
1841
1842


1843
1844
1845

1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
  Th_Interp *interp,           /* Interpreter context */
  char **pzStr,                /* IN/OUT: Ptr to ptr to list */
  int *pnStr,                  /* IN/OUT: Current length of *pzStr */
  const char *zElem,           /* Data to append */
  int nElem                    /* Length of nElem */
){
  char *zNew;
  int nNew;


  if( nElem<0 ){
    nElem = th_strlen(zElem);


  }

  nNew = *pnStr + nElem;

  zNew = Th_Malloc(interp, nNew);
  th_memcpy(zNew, *pzStr, *pnStr);
  th_memcpy(&zNew[*pnStr], zElem, nElem);

  Th_Free(interp, *pzStr);
  *pzStr = zNew;
  *pnStr = nNew;

  return TH_OK;
}

/*
** Initialize an interpreter.
*/







|
>


|
>
>


|
>


|



|







1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
  Th_Interp *interp,           /* Interpreter context */
  char **pzStr,                /* IN/OUT: Ptr to ptr to list */
  int *pnStr,                  /* IN/OUT: Current length of *pzStr */
  const char *zElem,           /* Data to append */
  int nElem                    /* Length of nElem */
){
  char *zNew;
  long long int nNew;
  int nn;

  if( nElem<0 ){
    nn = th_strlen(zElem);
  }else{
    nn = TH1_LEN(nElem);
  }

  nNew = TH1_LEN(*pnStr) + nn;
  TH1_SIZECHECK(nNew);
  zNew = Th_Malloc(interp, nNew);
  th_memcpy(zNew, *pzStr, *pnStr);
  th_memcpy(&zNew[TH1_LEN(*pnStr)], zElem, nn);

  Th_Free(interp, *pzStr);
  *pzStr = zNew;
  *pnStr = (int)nNew;

  return TH_OK;
}

/*
** Initialize an interpreter.
*/
2104
2105
2106
2107
2108
2109
2110

2111
2112
2113
2114
2115
2116

2117
2118
2119
2120
2121
2122
2123
    char *zRight = 0; int nRight = 0;

    /* Evaluate left and right arguments, if they exist. */
    if( pExpr->pLeft ){
      rc = exprEval(interp, pExpr->pLeft);
      if( rc==TH_OK ){
        zLeft = Th_TakeResult(interp, &nLeft);

      }
    }
    if( rc==TH_OK && pExpr->pRight ){
      rc = exprEval(interp, pExpr->pRight);
      if( rc==TH_OK ){
        zRight = Th_TakeResult(interp, &nRight);

      }
    }

    /* Convert arguments to their required forms. */
    if( rc==TH_OK ){
      eArgType = pExpr->pOp->eArgType;
      if( eArgType==ARG_NUMBER ){







>






>







2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
    char *zRight = 0; int nRight = 0;

    /* Evaluate left and right arguments, if they exist. */
    if( pExpr->pLeft ){
      rc = exprEval(interp, pExpr->pLeft);
      if( rc==TH_OK ){
        zLeft = Th_TakeResult(interp, &nLeft);
        nLeft = TH1_LEN(nLeft);
      }
    }
    if( rc==TH_OK && pExpr->pRight ){
      rc = exprEval(interp, pExpr->pRight);
      if( rc==TH_OK ){
        zRight = Th_TakeResult(interp, &nRight);
        nRight = TH1_LEN(nRight);
      }
    }

    /* Convert arguments to their required forms. */
    if( rc==TH_OK ){
      eArgType = pExpr->pOp->eArgType;
      if( eArgType==ARG_NUMBER ){
2454
2455
2456
2457
2458
2459
2460


2461
2462
2463
2464
2465
2466
2467
  int i;                            /* Loop counter */

  int nToken = 0;
  Expr **apToken = 0;

  if( nExpr<0 ){
    nExpr = th_strlen(zExpr);


  }

  /* Parse the expression to a list of tokens. */
  rc = exprParse(interp, zExpr, nExpr, &apToken, &nToken);

  /* If the parsing was successful, create an expression tree from
  ** the parsed list of tokens. If successful, apToken[0] is set







>
>







2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
  int i;                            /* Loop counter */

  int nToken = 0;
  Expr **apToken = 0;

  if( nExpr<0 ){
    nExpr = th_strlen(zExpr);
  }else{
    nExpr = TH1_LEN(nExpr);
  }

  /* Parse the expression to a list of tokens. */
  rc = exprParse(interp, zExpr, nExpr, &apToken, &nToken);

  /* If the parsing was successful, create an expression tree from
  ** the parsed list of tokens. If successful, apToken[0] is set
2565
2566
2567
2568
2569
2570
2571


2572
2573
2574
2575
2576
2577
2578
  unsigned int iKey = 0;
  int i;
  Th_HashEntry *pRet;
  Th_HashEntry **ppRet;

  if( nKey<0 ){
    nKey = th_strlen(zKey);


  }

  for(i=0; i<nKey; i++){
    iKey = (iKey<<3) ^ iKey ^ zKey[i];
  }
  iKey = iKey % TH_HASHSIZE;








>
>







2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
  unsigned int iKey = 0;
  int i;
  Th_HashEntry *pRet;
  Th_HashEntry **ppRet;

  if( nKey<0 ){
    nKey = th_strlen(zKey);
  }else{
    nKey = TH1_LEN(nKey);
  }

  for(i=0; i<nKey; i++){
    iKey = (iKey<<3) ^ iKey ^ zKey[i];
  }
  iKey = iKey % TH_HASHSIZE;

2798
2799
2800
2801
2802
2803
2804


2805
2806
2807
2808
2809
2810
2811
  int i = 0;
  int iOut = 0;
  int base = 10;
  int (*isdigit)(char) = th_isdigit;

  if( n<0 ){
    n = th_strlen(z);


  }

  if( n>1 && (z[0]=='-' || z[0]=='+') ){
    i = 1;
  }
  if( (n-i)>2 && z[i]=='0' ){
    if( z[i+1]=='x' || z[i+1]=='X' ){







>
>







2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
  int i = 0;
  int iOut = 0;
  int base = 10;
  int (*isdigit)(char) = th_isdigit;

  if( n<0 ){
    n = th_strlen(z);
  }else{
    n = TH1_LEN(n);
  }

  if( n>1 && (z[0]=='-' || z[0]=='+') ){
    i = 1;
  }
  if( (n-i)>2 && z[i]=='0' ){
    if( z[i+1]=='x' || z[i+1]=='X' ){
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
int Th_ToDouble(
  Th_Interp *interp,
  const char *z,
  int n,
  double *pfOut
){
  if( !sqlite3IsNumber((const char *)z, 0) ){
    Th_ErrorMessage(interp, "expected number, got: \"", z, n);
    return TH_ERROR;
  }

  sqlite3AtoF((const char *)z, pfOut);
  return TH_OK;
}








|







2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
int Th_ToDouble(
  Th_Interp *interp,
  const char *z,
  int n,
  double *pfOut
){
  if( !sqlite3IsNumber((const char *)z, 0) ){
    Th_ErrorMessage(interp, "expected number, got: \"", z, TH1_LEN(n));
    return TH_ERROR;
  }

  sqlite3AtoF((const char *)z, pfOut);
  return TH_OK;
}

Changes to src/th.h.
1
2
3
4

























5






















6
7
8
9
10
11
12

/* This header file defines the external interface to the custom Scripting
** Language (TH) interpreter.  TH is very similar to Tcl but is not an
** exact clone.

























*/























/*
** Before creating an interpreter, the application must allocate and
** populate an instance of the following structure. It must remain valid
** for the lifetime of the interpreter.
*/
typedef struct Th_Vtab Th_Vtab;
<



>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>








1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

/* This header file defines the external interface to the custom Scripting
** Language (TH) interpreter.  TH is very similar to Tcl but is not an
** exact clone.
**
** TH1 was original developed to run SQLite tests on SymbianOS.  This version
** of TH1 was repurposed as a scripted language for Fossil, and was heavily
** modified for that purpose, beginning in early 2008.
**
** More recently, TH1 has been enhanced to distinguish between regular text
** and "tainted" text.  "Tainted" text is text that might have originated
** from an outside source and hence might not be trustworthy.  To prevent
** cross-site scripting (XSS) and SQL-injections and similar attacks,
** tainted text should not be used for the following purposes:
**
**     *   executed as TH1 script or expression.
**     *   output as HTML or Javascript
**     *   used as part of an SQL query
**
** Tainted text can be converted into a safe form using commands like
** "htmlize".  And some commands ("query" and "expr") know how to use
** potentially tainted variable values directly, and thus can bypass
** the restrictions above.
**
** Whether a string is clean or tainted is determined by its length integer.
** TH1 limits strings to be no more than 0x0fffffff bytes bytes in length
** (about 268MB - more than sufficient for the purposes of Fossil).  The top
** bit of the length integer is the sign bit, of course.  The next three bits
** are reserved.  One of those, the 0x10000000 bit, marks tainted strings.
*/
#define TH1_MX_STRLEN     0x0fffffff      /* Maximum length of a TH1-C string */
#define TH1_TAINT_BIT     0x10000000      /* The taint bit */
#define TH1_SIGN          0x80000000

/* Convert an integer into a string length.  Negative values remain negative */
#define TH1_LEN(X)        ((TH1_SIGN|TH1_MX_STRLEN)&(X))

/* Return true if the string is tainted */
#define TH1_TAINTED(X)    (((X)&TH1_TAINT_BIT)!=0)

/* Remove taint from a string */
#define TH1_RM_TAINT(X)   ((X)&~TH1_TAINT_BIT)

/* Add taint to a string */
#define TH1_ADD_TAINT(X)  ((X)|TH1_TAINT_BIT)

/* If B is tainted, make A tainted too */
#define TH1_XFER_TAINT(A,B)  (A)|=(TH1_TAINT_BIT&(B))

/* Check to see if a string is too big for TH1 */
#define TH1_SIZECHECK(N)  if((N)>TH1_MX_STRLEN){Th_OversizeString();}
void Th_OversizeString(void);

/*
** Before creating an interpreter, the application must allocate and
** populate an instance of the following structure. It must remain valid
** for the lifetime of the interpreter.
*/
typedef struct Th_Vtab Th_Vtab;
22
23
24
25
26
27
28






29
30
31
32
33
34
35

/*
** Create and delete interpreters.
*/
Th_Interp * Th_CreateInterp(Th_Vtab *);
void Th_DeleteInterp(Th_Interp *);







/*
** Evaluate an TH program in the stack frame identified by parameter
** iFrame, according to the following rules:
**
**   * If iFrame is 0, this means the current frame.
**
**   * If iFrame is negative, then the nth frame up the stack, where n is







>
>
>
>
>
>







68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

/*
** Create and delete interpreters.
*/
Th_Interp * Th_CreateInterp(Th_Vtab *);
void Th_DeleteInterp(Th_Interp *);

/*
** Report taint in the string zStr,nStr.  That string represents "zTitle"
** If non-zero is returned error out of the caller.
*/
int Th_ReportTaint(Th_Interp*,const char*,const char*zStr,int nStr);

/*
** Evaluate an TH program in the stack frame identified by parameter
** iFrame, according to the following rules:
**
**   * If iFrame is 0, this means the current frame.
**
**   * If iFrame is negative, then the nth frame up the stack, where n is
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
int Th_ExistsVar(Th_Interp *, const char *, int);
int Th_ExistsArrayVar(Th_Interp *, const char *, int);
int Th_GetVar(Th_Interp *, const char *, int);
int Th_SetVar(Th_Interp *, const char *, int, const char *, int);
int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int);
int Th_UnsetVar(Th_Interp *, const char *, int);

/*
** If interp has a variable with the given name, its value is returned
** and its length is returned via *nOut if nOut is not NULL.  If
** interp has no such var then NULL is returned without setting any
** error state and *nOut, if not NULL, is set to 0. The returned value
** is owned by the interpreter and may be invalidated the next time
** the interpreter is modified.
**
** zVarName must be NUL-terminated.
*/
const char * Th_MaybeGetVar(Th_Interp *interp, const char *zVarName,
                            int *nOut);

typedef int (*Th_CommandProc)(Th_Interp *, void *, int, const char **, int *);

/*
** Register new commands.
*/
int Th_CreateCommand(
  Th_Interp *interp,







<
<
<
<
<
<
<
<
<
<
<
<
<







106
107
108
109
110
111
112













113
114
115
116
117
118
119
int Th_ExistsVar(Th_Interp *, const char *, int);
int Th_ExistsArrayVar(Th_Interp *, const char *, int);
int Th_GetVar(Th_Interp *, const char *, int);
int Th_SetVar(Th_Interp *, const char *, int, const char *, int);
int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int);
int Th_UnsetVar(Th_Interp *, const char *, int);














typedef int (*Th_CommandProc)(Th_Interp *, void *, int, const char **, int *);

/*
** Register new commands.
*/
int Th_CreateCommand(
  Th_Interp *interp,
Changes to src/th_lang.c.
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
    return Th_WrongNumArgs(interp, "catch script ?varname?");
  }

  rc = Th_Eval(interp, 0, argv[1], -1);
  if( argc==3 ){
    int nResult;
    const char *zResult = Th_GetResult(interp, &nResult);
    Th_SetVar(interp, argv[2], argl[2], zResult, nResult);
  }

  Th_SetResultInt(interp, rc);
  return TH_OK;
}

/*







|







37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
    return Th_WrongNumArgs(interp, "catch script ?varname?");
  }

  rc = Th_Eval(interp, 0, argv[1], -1);
  if( argc==3 ){
    int nResult;
    const char *zResult = Th_GetResult(interp, &nResult);
    Th_SetVar(interp, argv[2], TH1_LEN(argl[2]), zResult, nResult);
  }

  Th_SetResultInt(interp, rc);
  return TH_OK;
}

/*
178
179
180
181
182
183
184

185
186
187
188
189
190

191
192
193


194
195
196
197
198
199
200
201
  char **azVar = 0;
  int *anVar;
  int nVar;
  char **azValue = 0;
  int *anValue;
  int nValue;
  int ii, jj;


  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "foreach varlist list script");
  }
  rc = Th_SplitList(interp, argv[1], argl[1], &azVar, &anVar, &nVar);
  if( rc ) return rc;

  rc = Th_SplitList(interp, argv[2], argl[2], &azValue, &anValue, &nValue);
  for(ii=0; rc==TH_OK && ii<=nValue-nVar; ii+=nVar){
    for(jj=0; jj<nVar; jj++){


      Th_SetVar(interp, azVar[jj], anVar[jj], azValue[ii+jj], anValue[ii+jj]);
    }
    rc = eval_loopbody(interp, argv[3], argl[3]);
  }
  if( rc==TH_BREAK ) rc = TH_OK;
  Th_Free(interp, azVar);
  Th_Free(interp, azValue);
  return rc;







>






>



>
>
|







178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
  char **azVar = 0;
  int *anVar;
  int nVar;
  char **azValue = 0;
  int *anValue;
  int nValue;
  int ii, jj;
  int bTaint = 0;

  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "foreach varlist list script");
  }
  rc = Th_SplitList(interp, argv[1], argl[1], &azVar, &anVar, &nVar);
  if( rc ) return rc;
  TH1_XFER_TAINT(bTaint, argl[2]);
  rc = Th_SplitList(interp, argv[2], argl[2], &azValue, &anValue, &nValue);
  for(ii=0; rc==TH_OK && ii<=nValue-nVar; ii+=nVar){
    for(jj=0; jj<nVar; jj++){
      int x = anValue[ii+jj];
      TH1_XFER_TAINT(x, bTaint);
      Th_SetVar(interp, azVar[jj], anVar[jj], azValue[ii+jj], x);
    }
    rc = eval_loopbody(interp, argv[3], argl[3]);
  }
  if( rc==TH_BREAK ) rc = TH_OK;
  Th_Free(interp, azVar);
  Th_Free(interp, azValue);
  return rc;
213
214
215
216
217
218
219

220
221

222
223
224

225
226
227
228
229
230
231
  int argc,
  const char **argv,
  int *argl
){
  char *zList = 0;
  int nList = 0;
  int i;


  for(i=1; i<argc; i++){

    Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
  }


  Th_SetResult(interp, zList, nList);
  Th_Free(interp, zList);

  return TH_OK;
}

/*







>


>



>







217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
  int argc,
  const char **argv,
  int *argl
){
  char *zList = 0;
  int nList = 0;
  int i;
  int bTaint = 0;

  for(i=1; i<argc; i++){
    TH1_XFER_TAINT(bTaint,argl[i]);
    Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
  }

  TH1_XFER_TAINT(nList, bTaint);
  Th_SetResult(interp, zList, nList);
  Th_Free(interp, zList);

  return TH_OK;
}

/*
242
243
244
245
246
247
248

249
250
251
252
253
254
255
256
257

258

259
260
261

262
263
264
265
266
267
268
  int argc,
  const char **argv,
  int *argl
){
  char *zList = 0;
  int nList = 0;
  int i, rc;


  if( argc<2 ){
    return Th_WrongNumArgs(interp, "lappend var ...");
  }
  rc = Th_GetVar(interp, argv[1], argl[1]);
  if( rc==TH_OK ){
    zList = Th_TakeResult(interp, &nList);
  }


  for(i=2; i<argc; i++){

    Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
  }


  Th_SetVar(interp, argv[1], argl[1], zList, nList);
  Th_SetResult(interp, zList, nList);
  Th_Free(interp, zList);

  return TH_OK;
}








>









>

>



>







249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
  int argc,
  const char **argv,
  int *argl
){
  char *zList = 0;
  int nList = 0;
  int i, rc;
  int bTaint = 0;

  if( argc<2 ){
    return Th_WrongNumArgs(interp, "lappend var ...");
  }
  rc = Th_GetVar(interp, argv[1], argl[1]);
  if( rc==TH_OK ){
    zList = Th_TakeResult(interp, &nList);
  }

  TH1_XFER_TAINT(bTaint, nList);
  for(i=2; i<argc; i++){
    TH1_XFER_TAINT(bTaint, argl[i]);
    Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
  }

  TH1_XFER_TAINT(nList, bTaint);
  Th_SetVar(interp, argv[1], argl[1], zList, nList);
  Th_SetResult(interp, zList, nList);
  Th_Free(interp, zList);

  return TH_OK;
}

281
282
283
284
285
286
287

288
289
290
291
292
293
294
295
296

297
298
299


300
301
302
303
304
305
306
307
){
  int iElem;
  int rc;

  char **azElem;
  int *anElem;
  int nCount;


  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "lindex list index");
  }

  if( TH_OK!=Th_ToInt(interp, argv[2], argl[2], &iElem) ){
    return TH_ERROR;
  }


  rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
  if( rc==TH_OK ){
    if( iElem<nCount && iElem>=0 ){


      Th_SetResult(interp, azElem[iElem], anElem[iElem]);
    }else{
      Th_SetResult(interp, 0, 0);
    }
    Th_Free(interp, azElem);
  }

  return rc;







>









>



>
>
|







292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
){
  int iElem;
  int rc;

  char **azElem;
  int *anElem;
  int nCount;
  int bTaint = 0;

  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "lindex list index");
  }

  if( TH_OK!=Th_ToInt(interp, argv[2], argl[2], &iElem) ){
    return TH_ERROR;
  }

  TH1_XFER_TAINT(bTaint, argl[1]);
  rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
  if( rc==TH_OK ){
    if( iElem<nCount && iElem>=0 ){
      int sz = anElem[iElem];
      TH1_XFER_TAINT(sz, bTaint);
      Th_SetResult(interp, azElem[iElem], sz);
    }else{
      Th_SetResult(interp, 0, 0);
    }
    Th_Free(interp, azElem);
  }

  return rc;
354
355
356
357
358
359
360

361
362
363
364
365
366
367
368
369
370

  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "lsearch list string");
  }

  rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
  if( rc==TH_OK ){

    Th_SetResultInt(interp, -1);
    for(i=0; i<nCount; i++){
      if( anElem[i]==argl[2] && 0==memcmp(azElem[i], argv[2], argl[2]) ){
        Th_SetResultInt(interp, i);
        break;
      }
    }
    Th_Free(interp, azElem);
  }








>


|







369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386

  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "lsearch list string");
  }

  rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
  if( rc==TH_OK ){
    int nn = TH1_LEN(argl[2]);
    Th_SetResultInt(interp, -1);
    for(i=0; i<nCount; i++){
      if( TH1_LEN(anElem[i])==nn && 0==memcmp(azElem[i], argv[2], nn) ){
        Th_SetResultInt(interp, i);
        break;
      }
    }
    Th_Free(interp, azElem);
  }

559
560
561
562
563
564
565
566

567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582

583

584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603

  char *zUsage = 0;                /* Build up a usage message here */
  int nUsage = 0;                  /* Number of bytes at zUsage */

  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "proc name arglist code");
  }
  if( Th_SplitList(interp, argv[2], argl[2], &azParam, &anParam, &nParam) ){

    return TH_ERROR;
  }

  /* Allocate the new ProcDefn structure. */
  nByte = sizeof(ProcDefn) +                        /* ProcDefn structure */
      (sizeof(char *) + sizeof(int)) * nParam +     /* azParam, anParam */
      (sizeof(char *) + sizeof(int)) * nParam +     /* azDefault, anDefault */
      argl[3] +                                     /* zProgram */
      argl[2];    /* Space for copies of parameter names and default values */
  p = (ProcDefn *)Th_Malloc(interp, nByte);

  /* If the last parameter in the parameter list is "args", then set the
  ** ProcDefn.hasArgs flag. The "args" parameter does not require an
  ** entry in the ProcDefn.azParam[] or ProcDefn.azDefault[] arrays.
  */
  if( nParam>0 ){

    if( anParam[nParam-1]==4 && 0==memcmp(azParam[nParam-1], "args", 4) ){

      p->hasArgs = 1;
      nParam--;
    }
  }

  p->nParam    = nParam;
  p->azParam   = (char **)&p[1];
  p->anParam   = (int *)&p->azParam[nParam];
  p->azDefault = (char **)&p->anParam[nParam];
  p->anDefault = (int *)&p->azDefault[nParam];
  p->zProgram = (char *)&p->anDefault[nParam];
  memcpy(p->zProgram, argv[3], argl[3]);
  p->nProgram = argl[3];
  zSpace = &p->zProgram[p->nProgram];

  for(i=0; i<nParam; i++){
    char **az;
    int *an;
    int n;
    if( Th_SplitList(interp, azParam[i], anParam[i], &az, &an, &n) ){







|
>







|
|







>
|
>











|
|







575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622

  char *zUsage = 0;                /* Build up a usage message here */
  int nUsage = 0;                  /* Number of bytes at zUsage */

  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "proc name arglist code");
  }
  if( Th_SplitList(interp, argv[2], TH1_LEN(argl[2]),
                   &azParam, &anParam, &nParam) ){
    return TH_ERROR;
  }

  /* Allocate the new ProcDefn structure. */
  nByte = sizeof(ProcDefn) +                        /* ProcDefn structure */
      (sizeof(char *) + sizeof(int)) * nParam +     /* azParam, anParam */
      (sizeof(char *) + sizeof(int)) * nParam +     /* azDefault, anDefault */
      TH1_LEN(argl[3]) +                            /* zProgram */
      TH1_LEN(argl[2]);   /* Space for copies of param names and dflt values */
  p = (ProcDefn *)Th_Malloc(interp, nByte);

  /* If the last parameter in the parameter list is "args", then set the
  ** ProcDefn.hasArgs flag. The "args" parameter does not require an
  ** entry in the ProcDefn.azParam[] or ProcDefn.azDefault[] arrays.
  */
  if( nParam>0 ){
    if( TH1_LEN(anParam[nParam-1])==4
     && 0==memcmp(azParam[nParam-1], "args", 4)
    ){
      p->hasArgs = 1;
      nParam--;
    }
  }

  p->nParam    = nParam;
  p->azParam   = (char **)&p[1];
  p->anParam   = (int *)&p->azParam[nParam];
  p->azDefault = (char **)&p->anParam[nParam];
  p->anDefault = (int *)&p->azDefault[nParam];
  p->zProgram = (char *)&p->anDefault[nParam];
  memcpy(p->zProgram, argv[3], TH1_LEN(argl[3]));
  p->nProgram = TH1_LEN(argl[3]);
  zSpace = &p->zProgram[p->nProgram];

  for(i=0; i<nParam; i++){
    char **az;
    int *an;
    int n;
    if( Th_SplitList(interp, azParam[i], anParam[i], &az, &an, &n) ){
670
671
672
673
674
675
676
677

678
679
680
681
682
683
684
  int argc,
  const char **argv,
  int *argl
){
  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "rename oldcmd newcmd");
  }
  return Th_RenameCommand(interp, argv[1], argl[1], argv[2], argl[2]);

}

/*
** TH Syntax:
**
**   break    ?value...?
**   continue ?value...?







|
>







689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
  int argc,
  const char **argv,
  int *argl
){
  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "rename oldcmd newcmd");
  }
  return Th_RenameCommand(interp, argv[1], TH1_LEN(argl[1]),
                          argv[2], TH1_LEN(argl[2]));
}

/*
** TH Syntax:
**
**   break    ?value...?
**   continue ?value...?
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
  int iRes = 0;

  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "string compare str1 str2");
  }

  zLeft = argv[2];
  nLeft = argl[2];
  zRight = argv[3];
  nRight = argl[3];

  for(i=0; iRes==0 && i<nLeft && i<nRight; i++){
    iRes = zLeft[i]-zRight[i];
  }
  if( iRes==0 ){
    iRes = nLeft-nRight;
  }







|

|







764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
  int iRes = 0;

  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "string compare str1 str2");
  }

  zLeft = argv[2];
  nLeft = TH1_LEN(argl[2]);
  zRight = argv[3];
  nRight = TH1_LEN(argl[3]);

  for(i=0; iRes==0 && i<nLeft && i<nRight; i++){
    iRes = zLeft[i]-zRight[i];
  }
  if( iRes==0 ){
    iRes = nLeft-nRight;
  }
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
  int nHaystack;
  int iRes = -1;

  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "string first needle haystack");
  }

  nNeedle = argl[2];
  nHaystack = argl[3];

  if( nNeedle && nHaystack && nNeedle<=nHaystack ){
    const char *zNeedle = argv[2];
    const char *zHaystack = argv[3];
    int i;

    for(i=0; i<=(nHaystack-nNeedle); i++){







|
|







797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
  int nHaystack;
  int iRes = -1;

  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "string first needle haystack");
  }

  nNeedle = TH1_LEN(argl[2]);
  nHaystack = TH1_LEN(argl[3]);

  if( nNeedle && nHaystack && nNeedle<=nHaystack ){
    const char *zNeedle = argv[2];
    const char *zHaystack = argv[3];
    int i;

    for(i=0; i<=(nHaystack-nNeedle); i++){
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825


826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870


871
872
873

874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
){
  int iIndex;

  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "string index string index");
  }

  if( argl[3]==3 && 0==memcmp("end", argv[3], 3) ){
    iIndex = argl[2]-1;
  }else if( Th_ToInt(interp, argv[3], argl[3], &iIndex) ){
    Th_ErrorMessage(
        interp, "Expected \"end\" or integer, got:", argv[3], argl[3]);
    return TH_ERROR;
  }

  if( iIndex>=0 && iIndex<argl[2] ){


    return Th_SetResult(interp, &argv[2][iIndex], 1);
  }else{
    return Th_SetResult(interp, 0, 0);
  }
}

/*
** TH Syntax:
**
**   string is CLASS STRING
*/
static int string_is_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "string is class string");
  }
  if( argl[2]==5 && 0==memcmp(argv[2], "alnum", 5) ){
    int i;
    int iRes = 1;

    for(i=0; i<argl[3]; i++){
      if( !th_isalnum(argv[3][i]) ){
        iRes = 0;
      }
    }

    return Th_SetResultInt(interp, iRes);
  }else if( argl[2]==6 && 0==memcmp(argv[2], "double", 6) ){
    double fVal;
    if( Th_ToDouble(interp, argv[3], argl[3], &fVal)==TH_OK ){
      return Th_SetResultInt(interp, 1);
    }
    return Th_SetResultInt(interp, 0);
  }else if( argl[2]==7 && 0==memcmp(argv[2], "integer", 7) ){
    int iVal;
    if( Th_ToInt(interp, argv[3], argl[3], &iVal)==TH_OK ){
      return Th_SetResultInt(interp, 1);
    }
    return Th_SetResultInt(interp, 0);
  }else if( argl[2]==4 && 0==memcmp(argv[2], "list", 4) ){
    if( Th_SplitList(interp, argv[3], argl[3], 0, 0, 0)==TH_OK ){
      return Th_SetResultInt(interp, 1);
    }
    return Th_SetResultInt(interp, 0);


  }else{
    Th_ErrorMessage(interp,
        "Expected alnum, double, integer, or list, got:", argv[2], argl[2]);

    return TH_ERROR;
  }
}

/*
** TH Syntax:
**
**   string last NEEDLE HAYSTACK
*/
static int string_last_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  int nNeedle;
  int nHaystack;
  int iRes = -1;

  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "string last needle haystack");
  }

  nNeedle = argl[2];
  nHaystack = argl[3];

  if( nNeedle && nHaystack && nNeedle<=nHaystack ){
    const char *zNeedle = argv[2];
    const char *zHaystack = argv[3];
    int i;

    for(i=nHaystack-nNeedle; i>=0; i--){







|
|






|
>
>
|
















|



|






|





|





|




>
>


|
>




















|
|







830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
){
  int iIndex;

  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "string index string index");
  }

  if( TH1_LEN(argl[3])==3 && 0==memcmp("end", argv[3], 3) ){
    iIndex = TH1_LEN(argl[2])-1;
  }else if( Th_ToInt(interp, argv[3], argl[3], &iIndex) ){
    Th_ErrorMessage(
        interp, "Expected \"end\" or integer, got:", argv[3], argl[3]);
    return TH_ERROR;
  }

  if( iIndex>=0 && iIndex<TH1_LEN(argl[2]) ){
    int sz = 1;
    TH1_XFER_TAINT(sz, argl[2]);
    return Th_SetResult(interp, &argv[2][iIndex], sz);
  }else{
    return Th_SetResult(interp, 0, 0);
  }
}

/*
** TH Syntax:
**
**   string is CLASS STRING
*/
static int string_is_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "string is class string");
  }
  if( TH1_LEN(argl[2])==5 && 0==memcmp(argv[2], "alnum", 5) ){
    int i;
    int iRes = 1;

    for(i=0; i<TH1_LEN(argl[3]); i++){
      if( !th_isalnum(argv[3][i]) ){
        iRes = 0;
      }
    }

    return Th_SetResultInt(interp, iRes);
  }else if( TH1_LEN(argl[2])==6 && 0==memcmp(argv[2], "double", 6) ){
    double fVal;
    if( Th_ToDouble(interp, argv[3], argl[3], &fVal)==TH_OK ){
      return Th_SetResultInt(interp, 1);
    }
    return Th_SetResultInt(interp, 0);
  }else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "integer", 7) ){
    int iVal;
    if( Th_ToInt(interp, argv[3], argl[3], &iVal)==TH_OK ){
      return Th_SetResultInt(interp, 1);
    }
    return Th_SetResultInt(interp, 0);
  }else if( TH1_LEN(argl[2])==4 && 0==memcmp(argv[2], "list", 4) ){
    if( Th_SplitList(interp, argv[3], argl[3], 0, 0, 0)==TH_OK ){
      return Th_SetResultInt(interp, 1);
    }
    return Th_SetResultInt(interp, 0);
  }else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "tainted", 7) ){
    return Th_SetResultInt(interp, TH1_TAINTED(argl[3]));
  }else{
    Th_ErrorMessage(interp,
        "Expected alnum, double, integer, list, or tainted, got:",
        argv[2], TH1_LEN(argl[2]));
    return TH_ERROR;
  }
}

/*
** TH Syntax:
**
**   string last NEEDLE HAYSTACK
*/
static int string_last_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  int nNeedle;
  int nHaystack;
  int iRes = -1;

  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "string last needle haystack");
  }

  nNeedle = TH1_LEN(argl[2]);
  nHaystack = TH1_LEN(argl[3]);

  if( nNeedle && nHaystack && nNeedle<=nHaystack ){
    const char *zNeedle = argv[2];
    const char *zHaystack = argv[3];
    int i;

    for(i=nHaystack-nNeedle; i>=0; i--){
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960

961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979


980
981
982
983
984
985
986
987
988
989
990
991
992
993

994
995
996
997
998
999
1000
1001
1002
1003
1004



1005
1006
1007
1008
1009


1010
1011
1012
1013
1014
1015
1016
1017
*/
static int string_length_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "string length string");
  }
  return Th_SetResultInt(interp, argl[2]);
}

/*
** TH Syntax:
**
**   string match PATTERN STRING
**
*/
static int string_match_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  extern char *fossil_strndup(const char*,int);
  extern void fossil_free(void*);
  char *zPat, *zStr;
  int rc;
  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "string match pattern string");
  }
  zPat = fossil_strndup(argv[2],argl[2]);
  zStr = fossil_strndup(argv[3],argl[3]);
  rc = sqlite3_strglob(zPat,zStr);
  fossil_free(zPat);
  fossil_free(zStr);
  return Th_SetResultInt(interp, !rc);
}

/*
** TH Syntax:
**
**   string range STRING FIRST LAST
*/
static int string_range_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  int iStart;
  int iEnd;


  if( argc!=5 ){
    return Th_WrongNumArgs(interp, "string range string first last");
  }

  if( argl[4]==3 && 0==memcmp("end", argv[4], 3) ){
    iEnd = argl[2];
  }else if( Th_ToInt(interp, argv[4], argl[4], &iEnd) ){
    Th_ErrorMessage(
        interp, "Expected \"end\" or integer, got:", argv[4], argl[4]);
    return TH_ERROR;
  }
  if( Th_ToInt(interp, argv[3], argl[3], &iStart) ){
    return TH_ERROR;
  }

  if( iStart<0 ) iStart = 0;
  if( iEnd>=argl[2] ) iEnd = argl[2]-1;
  if( iStart>iEnd ) iEnd = iStart-1;



  return Th_SetResult(interp, &argv[2][iStart], iEnd-iStart+1);
}

/*
** TH Syntax:
**
**   string repeat STRING COUNT
*/
static int string_repeat_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  int n;
  int i;

  int nByte;
  char *zByte;

  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "string repeat string n");
  }
  if( Th_ToInt(interp, argv[3], argl[3], &n) ){
    return TH_ERROR;
  }

  nByte = argl[2] * n;



  zByte = Th_Malloc(interp, nByte+1);
  for(i=0; i<nByte; i+=argl[2]){
    memcpy(&zByte[i], argv[2], argl[2]);
  }



  Th_SetResult(interp, zByte, nByte);
  Th_Free(interp, zByte);
  return TH_OK;
}

/*
** TH Syntax:
**







|


















|
|
















>





|
|


|







|

>
>

|












>
|









|
>
>
>

|
|


>
>
|







942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
*/
static int string_length_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "string length string");
  }
  return Th_SetResultInt(interp, TH1_LEN(argl[2]));
}

/*
** TH Syntax:
**
**   string match PATTERN STRING
**
*/
static int string_match_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  extern char *fossil_strndup(const char*,int);
  extern void fossil_free(void*);
  char *zPat, *zStr;
  int rc;
  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "string match pattern string");
  }
  zPat = fossil_strndup(argv[2],TH1_LEN(argl[2]));
  zStr = fossil_strndup(argv[3],TH1_LEN(argl[3]));
  rc = sqlite3_strglob(zPat,zStr);
  fossil_free(zPat);
  fossil_free(zStr);
  return Th_SetResultInt(interp, !rc);
}

/*
** TH Syntax:
**
**   string range STRING FIRST LAST
*/
static int string_range_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  int iStart;
  int iEnd;
  int sz;

  if( argc!=5 ){
    return Th_WrongNumArgs(interp, "string range string first last");
  }

  if( TH1_LEN(argl[4])==3 && 0==memcmp("end", argv[4], 3) ){
    iEnd = TH1_LEN(argl[2]);
  }else if( Th_ToInt(interp, argv[4], argl[4], &iEnd) ){
    Th_ErrorMessage(
        interp, "Expected \"end\" or integer, got:", argv[4], TH1_LEN(argl[4]));
    return TH_ERROR;
  }
  if( Th_ToInt(interp, argv[3], argl[3], &iStart) ){
    return TH_ERROR;
  }

  if( iStart<0 ) iStart = 0;
  if( iEnd>=TH1_LEN(argl[2]) ) iEnd = TH1_LEN(argl[2])-1;
  if( iStart>iEnd ) iEnd = iStart-1;
  sz = iEnd - iStart + 1;
  TH1_XFER_TAINT(sz, argl[2]);

  return Th_SetResult(interp, &argv[2][iStart], sz);
}

/*
** TH Syntax:
**
**   string repeat STRING COUNT
*/
static int string_repeat_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  int n;
  int i;
  int sz;
  long long int nByte;
  char *zByte;

  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "string repeat string n");
  }
  if( Th_ToInt(interp, argv[3], argl[3], &n) ){
    return TH_ERROR;
  }

  nByte = n;
  sz = TH1_LEN(argl[2]);
  nByte *= sz;
  TH1_SIZECHECK(nByte+1);
  zByte = Th_Malloc(interp, nByte+1);
  for(i=0; i<nByte; i+=sz){
    memcpy(&zByte[i], argv[2], sz);
  }

  n = nByte;
  TH1_XFER_TAINT(n, argl[2]);
  Th_SetResult(interp, zByte, n);
  Th_Free(interp, zByte);
  return TH_OK;
}

/*
** TH Syntax:
**
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038

1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
  int n;
  const char *z;

  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "string trim string");
  }
  z = argv[2];
  n = argl[2];
  if( argl[1]<5 || argv[1][4]=='l' ){
    while( n && th_isspace(z[0]) ){ z++; n--; }
  }
  if( argl[1]<5 || argv[1][4]=='r' ){
    while( n && th_isspace(z[n-1]) ){ n--; }
  }

  Th_SetResult(interp, z, n);
  return TH_OK;
}

/*
** TH Syntax:
**
**   info exists VARNAME
*/
static int info_exists_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  int rc;

  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "info exists var");
  }
  rc = Th_ExistsVar(interp, argv[2], argl[2]);
  Th_SetResultInt(interp, rc);
  return TH_OK;
}

/*
** TH Syntax:
**







|
|


|


>

















|







1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
  int n;
  const char *z;

  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "string trim string");
  }
  z = argv[2];
  n = TH1_LEN(argl[2]);
  if( TH1_LEN(argl[1])<5 || argv[1][4]=='l' ){
    while( n && th_isspace(z[0]) ){ z++; n--; }
  }
  if( TH1_LEN(argl[1])<5 || argv[1][4]=='r' ){
    while( n && th_isspace(z[n-1]) ){ n--; }
  }
  TH1_XFER_TAINT(n, argl[2]);
  Th_SetResult(interp, z, n);
  return TH_OK;
}

/*
** TH Syntax:
**
**   info exists VARNAME
*/
static int info_exists_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  int rc;

  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "info exists var");
  }
  rc = Th_ExistsVar(interp, argv[2], TH1_LEN(argl[2]));
  Th_SetResultInt(interp, rc);
  return TH_OK;
}

/*
** TH Syntax:
**
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  int rc;

  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "array exists var");
  }
  rc = Th_ExistsArrayVar(interp, argv[2], argl[2]);
  Th_SetResultInt(interp, rc);
  return TH_OK;
}

/*
** TH Syntax:
**
**   array names VARNAME
*/
static int array_names_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  int rc;
  char *zElem = 0;
  int nElem = 0;

  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "array names varname");
  }
  rc = Th_ListAppendArray(interp, argv[2], argl[2], &zElem, &nElem);
  if( rc!=TH_OK ){
    return rc;
  }
  Th_SetResult(interp, zElem, nElem);
  if( zElem ) Th_Free(interp, zElem);
  return TH_OK;
}







|



















|







1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  int rc;

  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "array exists var");
  }
  rc = Th_ExistsArrayVar(interp, argv[2], TH1_LEN(argl[2]));
  Th_SetResultInt(interp, rc);
  return TH_OK;
}

/*
** TH Syntax:
**
**   array names VARNAME
*/
static int array_names_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  int rc;
  char *zElem = 0;
  int nElem = 0;

  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "array names varname");
  }
  rc = Th_ListAppendArray(interp, argv[2], TH1_LEN(argl[2]), &zElem, &nElem);
  if( rc!=TH_OK ){
    return rc;
  }
  Th_SetResult(interp, zElem, nElem);
  if( zElem ) Th_Free(interp, zElem);
  return TH_OK;
}
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181

1182
1183
1184
1185
1186
1187

1188
1189

1190
1191
1192
1193
1194
1195
1196
  int argc,
  const char **argv,
  int *argl
){
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "unset var");
  }
  return Th_UnsetVar(interp, argv[1], argl[1]);
}

int Th_CallSubCommand(
  Th_Interp *interp,
  void *ctx,
  int argc,
  const char **argv,
  int *argl,
  const Th_SubCommand *aSub
){
  if( argc>1 ){
    int i;
    for(i=0; aSub[i].zName; i++){
      const char *zName = aSub[i].zName;
      if( th_strlen(zName)==argl[1] && 0==memcmp(zName, argv[1], argl[1]) ){

        return aSub[i].xProc(interp, ctx, argc, argv, argl);
      }
    }
  }
  if(argc<2){
    Th_ErrorMessage(interp, "Expected sub-command for", argv[0], argl[0]);

  }else{
    Th_ErrorMessage(interp, "Expected sub-command, got:", argv[1], argl[1]);

  }
  return TH_ERROR;
}

/*
** TH Syntax:
**







|














|
>





|
>

|
>







1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
  int argc,
  const char **argv,
  int *argl
){
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "unset var");
  }
  return Th_UnsetVar(interp, argv[1], TH1_LEN(argl[1]));
}

int Th_CallSubCommand(
  Th_Interp *interp,
  void *ctx,
  int argc,
  const char **argv,
  int *argl,
  const Th_SubCommand *aSub
){
  if( argc>1 ){
    int i;
    for(i=0; aSub[i].zName; i++){
      const char *zName = aSub[i].zName;
      if( th_strlen(zName)==TH1_LEN(argl[1])
       && 0==memcmp(zName, argv[1], TH1_LEN(argl[1])) ){
        return aSub[i].xProc(interp, ctx, argc, argv, argl);
      }
    }
  }
  if(argc<2){
    Th_ErrorMessage(interp, "Expected sub-command for",
                            argv[0], TH1_LEN(argl[0]));
  }else{
    Th_ErrorMessage(interp, "Expected sub-command, got:",
                             argv[1], TH1_LEN(argl[1]));
  }
  return TH_ERROR;
}

/*
** TH Syntax:
**
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
  int *argl
){
  int iFrame = -1;

  if( argc!=2 && argc!=3 ){
    return Th_WrongNumArgs(interp, "uplevel ?level? script...");
  }
  if( argc==3 && TH_OK!=thToFrame(interp, argv[1], argl[1], &iFrame) ){
    return TH_ERROR;
  }
  return Th_Eval(interp, iFrame, argv[argc-1], -1);
}

/*
** TH Syntax:







|







1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
  int *argl
){
  int iFrame = -1;

  if( argc!=2 && argc!=3 ){
    return Th_WrongNumArgs(interp, "uplevel ?level? script...");
  }
  if( argc==3 && TH_OK!=thToFrame(interp, argv[1], TH1_LEN(argl[1]), &iFrame) ){
    return TH_ERROR;
  }
  return Th_Eval(interp, iFrame, argv[argc-1], -1);
}

/*
** TH Syntax:
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355

1356
1357
1358
1359
1360
1361
1362
  int *argl
){
  int iVar = 1;
  int iFrame = -1;
  int rc = TH_OK;
  int i;

  if( TH_OK==thToFrame(0, argv[1], argl[1], &iFrame) ){
    iVar++;
  }
  if( argc==iVar || (argc-iVar)%2 ){
    return Th_WrongNumArgs(interp,
        "upvar frame othervar myvar ?othervar myvar...?");
  }
  for(i=iVar; rc==TH_OK && i<argc; i=i+2){
    rc = Th_LinkVar(interp, argv[i+1], argl[i+1], iFrame, argv[i], argl[i]);

  }
  return rc;
}

/*
** TH Syntax:
**







|







|
>







1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
  int *argl
){
  int iVar = 1;
  int iFrame = -1;
  int rc = TH_OK;
  int i;

  if( TH_OK==thToFrame(0, argv[1], TH1_LEN(argl[1]), &iFrame) ){
    iVar++;
  }
  if( argc==iVar || (argc-iVar)%2 ){
    return Th_WrongNumArgs(interp,
        "upvar frame othervar myvar ?othervar myvar...?");
  }
  for(i=iVar; rc==TH_OK && i<argc; i=i+2){
    rc = Th_LinkVar(interp, argv[i+1], TH1_LEN(argl[i+1]),
                    iFrame, argv[i], TH1_LEN(argl[i]));
  }
  return rc;
}

/*
** TH Syntax:
**
Changes to src/th_main.c.
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
*/
#define TH_INIT_NONE        ((u32)0x00000000) /* No flags. */
#define TH_INIT_NEED_CONFIG ((u32)0x00000001) /* Open configuration first? */
#define TH_INIT_FORCE_TCL   ((u32)0x00000002) /* Force Tcl to be enabled? */
#define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH1 commands re-added? */
#define TH_INIT_FORCE_SETUP ((u32)0x00000008) /* Force eval of setup script? */
#define TH_INIT_NO_REPO     ((u32)0x00000010) /* Skip opening repository. */
#define TH_INIT_NO_ENCODE   ((u32)0x00000020) /* Do not html-encode sendText()*/
                                              /* output. */
#define TH_INIT_MASK        ((u32)0x0000003F) /* All possible init flags. */

/*
** Useful and/or "well-known" combinations of flag values.
*/
#define TH_INIT_DEFAULT     (TH_INIT_NONE)      /* Default flags. */
#define TH_INIT_HOOK        (TH_INIT_NEED_CONFIG | TH_INIT_FORCE_SETUP)
#define TH_INIT_FORBID_MASK (TH_INIT_FORCE_TCL) /* Illegal from a script. */







<
<
|







29
30
31
32
33
34
35


36
37
38
39
40
41
42
43
*/
#define TH_INIT_NONE        ((u32)0x00000000) /* No flags. */
#define TH_INIT_NEED_CONFIG ((u32)0x00000001) /* Open configuration first? */
#define TH_INIT_FORCE_TCL   ((u32)0x00000002) /* Force Tcl to be enabled? */
#define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH1 commands re-added? */
#define TH_INIT_FORCE_SETUP ((u32)0x00000008) /* Force eval of setup script? */
#define TH_INIT_NO_REPO     ((u32)0x00000010) /* Skip opening repository. */


#define TH_INIT_MASK        ((u32)0x0000001F) /* All possible init flags. */

/*
** Useful and/or "well-known" combinations of flag values.
*/
#define TH_INIT_DEFAULT     (TH_INIT_NONE)      /* Default flags. */
#define TH_INIT_HOOK        (TH_INIT_NEED_CONFIG | TH_INIT_FORCE_SETUP)
#define TH_INIT_FORBID_MASK (TH_INIT_FORCE_TCL) /* Illegal from a script. */
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
  const char **argv,
  int *argl
){
  char *zOut;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "httpize STRING");
  }
  zOut = httpize((char*)argv[1], argl[1]);
  Th_SetResult(interp, zOut, -1);
  free(zOut);
  return TH_OK;
}

/*
** True if output is enabled.  False if disabled.







|







258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
  const char **argv,
  int *argl
){
  char *zOut;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "httpize STRING");
  }
  zOut = httpize((char*)argv[1], TH1_LEN(argl[1]));
  Th_SetResult(interp, zOut, -1);
  free(zOut);
  return TH_OK;
}

/*
** True if output is enabled.  False if disabled.
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
){
  int rc;
  if( argc<2 || argc>3 ){
    return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN");
  }
  rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput);
  if( g.thTrace ){
    Th_Trace("enable_output {%.*s} -> %d<br>\n", argl[1],argv[1],enableOutput);
  }
  return rc;
}

/*
** TH1 command: enable_htmlify ?BOOLEAN?
**
** Enable or disable the HTML escaping done by all output which
** originates from TH1 (via sendText()).
**
** If passed no arguments it instead returns 0 or 1 to indicate the
** current state.
*/
static int enableHtmlifyCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  int rc = 0, buul;
  if( argc>3 ){
    return Th_WrongNumArgs(interp,
                           "enable_htmlify [TRACE_LABEL] ?BOOLEAN?");
  }
  buul = (TH_INIT_NO_ENCODE & g.th1Flags) ? 0 : 1;
  Th_SetResultInt(g.interp, buul);
  if(argc>1){
    if( g.thTrace ){
      Th_Trace("enable_htmlify {%.*s} -> %d<br>\n",
               argl[1],argv[1],buul);
    }
    rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &buul);
    if(!rc){
      if(buul){
        g.th1Flags &= ~TH_INIT_NO_ENCODE;
      }else{
        g.th1Flags |= TH_INIT_NO_ENCODE;
      }
    }
  }
  return rc;
}

/*
** Returns a name for a TH1 return code.
*/







|
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







287
288
289
290
291
292
293
294



295




































296
297
298
299
300
301
302
){
  int rc;
  if( argc<2 || argc>3 ){
    return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN");
  }
  rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput);
  if( g.thTrace ){
    Th_Trace("enable_output {%.*s} -> %d<br>\n",



             TH1_LEN(argl[1]),argv[1],enableOutput);




































  }
  return rc;
}

/*
** Returns a name for a TH1 return code.
*/
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394




395
396
397
398
399
400
401
  return tmp;
}

/*
** Send text to the appropriate output: If pOut is not NULL, it is
** appended there, else to the console or to the CGI reply buffer.
** Escape all characters with special meaning to HTML if the encode
** parameter is true, with the exception that that flag is ignored if
** g.th1Flags has the TH_INIT_NO_ENCODE flag.
**
** If pOut is NULL and the global pThOut is not then that blob
** is used for output.
*/
static void sendText(Blob * pOut, const char *z, int n, int encode){
  if(0==pOut && pThOut!=0){
    pOut = pThOut;
  }
  if(TH_INIT_NO_ENCODE & g.th1Flags){
    encode = 0;
  }
  if( enableOutput && n ){
    if( n<0 ) n = strlen(z);




    if( encode ){
      z = htmlize(z, n);
      n = strlen(z);
    }
    if(pOut!=0){
      blob_append(pOut, z, n);
    }else if( g.cgiOutput ){







|
<




|



<
<
<

|
>
>
>
>







332
333
334
335
336
337
338
339

340
341
342
343
344
345
346
347



348
349
350
351
352
353
354
355
356
357
358
359
360
  return tmp;
}

/*
** Send text to the appropriate output: If pOut is not NULL, it is
** appended there, else to the console or to the CGI reply buffer.
** Escape all characters with special meaning to HTML if the encode
** parameter is true.

**
** If pOut is NULL and the global pThOut is not then that blob
** is used for output.
*/
static void sendText(Blob *pOut, const char *z, int n, int encode){
  if(0==pOut && pThOut!=0){
    pOut = pThOut;
  }



  if( enableOutput && n ){
    if( n<0 ){
      n = strlen(z);
    }else{
      n = TH1_LEN(n);
    }
    if( encode ){
      z = htmlize(z, n);
      n = strlen(z);
    }
    if(pOut!=0){
      blob_append(pOut, z, n);
    }else if( g.cgiOutput ){
523
524
525
526
527
528
529


530
531
532






533
534
535
536
537
538
539
540
static int putsCmd(
  Th_Interp *interp,
  void *pConvert,
  int argc,
  const char **argv,
  int *argl
){


  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "puts STRING");
  }






  sendText(0,(char*)argv[1], argl[1], *(unsigned int*)pConvert);
  return TH_OK;
}

/*
** TH1 command: redirect URL ?withMethod?
**
** Issues an HTTP redirect to the specified URL and then exits the process.







>
>



>
>
>
>
>
>
|







482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
static int putsCmd(
  Th_Interp *interp,
  void *pConvert,
  int argc,
  const char **argv,
  int *argl
){
  int encode = *(unsigned int*)pConvert;
  int n;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "puts STRING");
  }
  n = argl[1];
  if( encode==0 && n>0 && TH1_TAINTED(n) ){
    if( Th_ReportTaint(interp, "output string", argv[1], n) ){
      return TH_ERROR;
    }
  }
  sendText(0,(char*)argv[1], TH1_LEN(n), encode);
  return TH_OK;
}

/*
** TH1 command: redirect URL ?withMethod?
**
** Issues an HTTP redirect to the specified URL and then exits the process.
555
556
557
558
559
560
561





562
563
564
565
566
567
568
  if( argc!=2 && argc!=3 ){
    return Th_WrongNumArgs(interp, "redirect URL ?withMethod?");
  }
  if( argc==3 ){
    if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){
      return TH_ERROR;
    }





  }
  if( withMethod ){
    cgi_redirect_with_method(argv[1]);
  }else{
    cgi_redirect(argv[1]);
  }
  Th_SetResult(interp, argv[1], argl[1]); /* NOT REACHED */







>
>
>
>
>







522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
  if( argc!=2 && argc!=3 ){
    return Th_WrongNumArgs(interp, "redirect URL ?withMethod?");
  }
  if( argc==3 ){
    if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){
      return TH_ERROR;
    }
  }
  if( TH1_TAINTED(argl[1])
   && Th_ReportTaint(interp,"redirect URL",argv[1],argl[1])
  ){
    return TH_ERROR;
  }
  if( withMethod ){
    cgi_redirect_with_method(argv[1]);
  }else{
    cgi_redirect(argv[1]);
  }
  Th_SetResult(interp, argv[1], argl[1]); /* NOT REACHED */
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
  Blob src, title, body;
  char *zValue = 0;
  int nValue = 0;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "markdown STRING");
  }
  blob_zero(&src);
  blob_init(&src, (char*)argv[1], argl[1]);
  blob_zero(&title); blob_zero(&body);
  markdown_to_html(&src, &title, &body);
  Th_ListAppend(interp, &zValue, &nValue, blob_str(&title), blob_size(&title));
  Th_ListAppend(interp, &zValue, &nValue, blob_str(&body), blob_size(&body));
  Th_SetResult(interp, zValue, nValue);
  Th_Free(interp, zValue);
  return TH_OK;







|







630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
  Blob src, title, body;
  char *zValue = 0;
  int nValue = 0;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "markdown STRING");
  }
  blob_zero(&src);
  blob_init(&src, (char*)argv[1], TH1_LEN(argl[1]));
  blob_zero(&title); blob_zero(&body);
  markdown_to_html(&src, &title, &body);
  Th_ListAppend(interp, &zValue, &nValue, blob_str(&title), blob_size(&title));
  Th_ListAppend(interp, &zValue, &nValue, blob_str(&body), blob_size(&body));
  Th_SetResult(interp, zValue, nValue);
  Th_Free(interp, zValue);
  return TH_OK;
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
){
  int flags = WIKI_INLINE | WIKI_NOBADLINKS | *(unsigned int*)p;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "wiki STRING");
  }
  if( enableOutput ){
    Blob src;
    blob_init(&src, (char*)argv[1], argl[1]);
    wiki_convert(&src, 0, flags);
    blob_reset(&src);
  }
  return TH_OK;
}

/*







|







660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
){
  int flags = WIKI_INLINE | WIKI_NOBADLINKS | *(unsigned int*)p;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "wiki STRING");
  }
  if( enableOutput ){
    Blob src;
    blob_init(&src, (char*)argv[1], TH1_LEN(argl[1]));
    wiki_convert(&src, 0, flags);
    blob_reset(&src);
  }
  return TH_OK;
}

/*
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
  const char **argv,
  int *argl
){
  char *zOut;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "htmlize STRING");
  }
  zOut = htmlize((char*)argv[1], argl[1]);
  Th_SetResult(interp, zOut, -1);
  free(zOut);
  return TH_OK;
}

/*
** TH1 command: encode64 STRING







|







705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
  const char **argv,
  int *argl
){
  char *zOut;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "htmlize STRING");
  }
  zOut = htmlize((char*)argv[1], TH1_LEN(argl[1]));
  Th_SetResult(interp, zOut, -1);
  free(zOut);
  return TH_OK;
}

/*
** TH1 command: encode64 STRING
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
  const char **argv,
  int *argl
){
  char *zOut;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "encode64 STRING");
  }
  zOut = encode64((char*)argv[1], argl[1]);
  Th_SetResult(interp, zOut, -1);
  free(zOut);
  return TH_OK;
}

/*
** TH1 command: date
**
** Return a string which is the current time and date.  If the
** -local option is used, the date appears using localtime instead
** of UTC.
*/
static int dateCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  char *zOut;
  if( argc>=2 && argl[1]==6 && memcmp(argv[1],"-local",6)==0 ){
    zOut = db_text("??", "SELECT datetime('now',toLocal())");
  }else{
    zOut = db_text("??", "SELECT datetime('now')");
  }
  Th_SetResult(interp, zOut, -1);
  free(zOut);
  return TH_OK;







|




















|







727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
  const char **argv,
  int *argl
){
  char *zOut;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "encode64 STRING");
  }
  zOut = encode64((char*)argv[1], TH1_LEN(argl[1]));
  Th_SetResult(interp, zOut, -1);
  free(zOut);
  return TH_OK;
}

/*
** TH1 command: date
**
** Return a string which is the current time and date.  If the
** -local option is used, the date appears using localtime instead
** of UTC.
*/
static int dateCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  char *zOut;
  if( argc>=2 && TH1_LEN(argl[1])==6 && memcmp(argv[1],"-local",6)==0 ){
    zOut = db_text("??", "SELECT datetime('now',toLocal())");
  }else{
    zOut = db_text("??", "SELECT datetime('now')");
  }
  Th_SetResult(interp, zOut, -1);
  free(zOut);
  return TH_OK;
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
  char *zCapList = 0;
  int nCapList = 0;
  if( argc<2 ){
    return Th_WrongNumArgs(interp, "hascap STRING ...");
  }
  for(i=1; rc==1 && i<argc; i++){
    if( g.thTrace ){
      Th_ListAppend(interp, &zCapList, &nCapList, argv[i], argl[i]);
    }
    rc = login_has_capability((char*)argv[i],argl[i],*(int*)p);
  }
  if( g.thTrace ){
    Th_Trace("[%s %#h] => %d<br>\n", argv[0], nCapList, zCapList, rc);
    Th_Free(interp, zCapList);
  }
  Th_SetResultInt(interp, rc);
  return TH_OK;







|

|







780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
  char *zCapList = 0;
  int nCapList = 0;
  if( argc<2 ){
    return Th_WrongNumArgs(interp, "hascap STRING ...");
  }
  for(i=1; rc==1 && i<argc; i++){
    if( g.thTrace ){
      Th_ListAppend(interp, &zCapList, &nCapList, argv[i], TH1_LEN(argl[i]));
    }
    rc = login_has_capability((char*)argv[i],TH1_LEN(argl[i]),*(int*)p);
  }
  if( g.thTrace ){
    Th_Trace("[%s %#h] => %d<br>\n", argv[0], nCapList, zCapList, rc);
    Th_Free(interp, zCapList);
  }
  Th_SetResultInt(interp, rc);
  return TH_OK;
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
  int nCap;
  int rc;
  int i;

  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "capexpr EXPR");
  }
  rc = Th_SplitList(interp, argv[1], argl[1], &azCap, &anCap, &nCap);
  if( rc ) return rc;
  rc = 0;
  for(i=0; i<nCap; i++){
    if( azCap[i][0]=='!' ){
      rc = !login_has_capability(azCap[i]+1, anCap[i]-1, 0);
    }else if( azCap[i][0]=='@' ){
      rc = login_has_capability(azCap[i]+1, anCap[i]-1, LOGIN_ANON);







|







828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
  int nCap;
  int rc;
  int i;

  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "capexpr EXPR");
  }
  rc = Th_SplitList(interp, argv[1], TH1_LEN(argl[1]), &azCap, &anCap, &nCap);
  if( rc ) return rc;
  rc = 0;
  for(i=0; i<nCap; i++){
    if( azCap[i][0]=='!' ){
      rc = !login_has_capability(azCap[i]+1, anCap[i]-1, 0);
    }else if( azCap[i][0]=='@' ){
      rc = login_has_capability(azCap[i]+1, anCap[i]-1, LOGIN_ANON);
919
920
921
922
923
924
925

926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
  int rc = 1, i, j;
  unsigned int searchCap = search_restrict(SRCH_ALL);
  if( argc<2 ){
    return Th_WrongNumArgs(interp, "hascap STRING ...");
  }
  for(i=1; i<argc && rc; i++){
    int match = 0;

    for(j=0; j<argl[i]; j++){
      switch( argv[i][j] ){
        case 'c':  match |= searchCap & SRCH_CKIN;  break;
        case 'd':  match |= searchCap & SRCH_DOC;   break;
        case 't':  match |= searchCap & SRCH_TKT;   break;
        case 'w':  match |= searchCap & SRCH_WIKI;  break;
      }
    }
    if( !match ) rc = 0;
  }
  if( g.thTrace ){
    Th_Trace("[searchable %#h] => %d<br>\n", argl[1], argv[1], rc);
  }
  Th_SetResultInt(interp, rc);
  return TH_OK;
}

/*
** TH1 command: hasfeature STRING







>
|










|







891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
  int rc = 1, i, j;
  unsigned int searchCap = search_restrict(SRCH_ALL);
  if( argc<2 ){
    return Th_WrongNumArgs(interp, "hascap STRING ...");
  }
  for(i=1; i<argc && rc; i++){
    int match = 0;
    int nn = TH1_LEN(argl[i]);
    for(j=0; j<nn; j++){
      switch( argv[i][j] ){
        case 'c':  match |= searchCap & SRCH_CKIN;  break;
        case 'd':  match |= searchCap & SRCH_DOC;   break;
        case 't':  match |= searchCap & SRCH_TKT;   break;
        case 'w':  match |= searchCap & SRCH_WIKI;  break;
      }
    }
    if( !match ) rc = 0;
  }
  if( g.thTrace ){
    Th_Trace("[searchable %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc);
  }
  Th_SetResultInt(interp, rc);
  return TH_OK;
}

/*
** TH1 command: hasfeature STRING
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
    rc = 1;
  }
#endif
  else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){
    rc = 1;
  }
  if( g.thTrace ){
    Th_Trace("[hasfeature %#h] => %d<br>\n", argl[1], zArg, rc);
  }
  Th_SetResultInt(interp, rc);
  return TH_OK;
}


/*







|







1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
    rc = 1;
  }
#endif
  else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){
    rc = 1;
  }
  if( g.thTrace ){
    Th_Trace("[hasfeature %#h] => %d<br>\n", TH1_LEN(argl[1]), zArg, rc);
  }
  Th_SetResultInt(interp, rc);
  return TH_OK;
}


/*
1102
1103
1104
1105
1106
1107
1108

1109
1110
1111

1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  int rc = 0;
  int i;

  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "anycap STRING");
  }

  for(i=0; rc==0 && i<argl[1]; i++){
    rc = login_has_capability((char*)&argv[1][i],1,0);
  }
  if( g.thTrace ){
    Th_Trace("[anycap %#h] => %d<br>\n", argl[1], argv[1], rc);
  }
  Th_SetResultInt(interp, rc);
  return TH_OK;
}

/*
** TH1 command: combobox NAME TEXT-LIST NUMLINES







>



>
|



|







1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  int rc = 0;
  int i;
  int nn;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "anycap STRING");
  }
  nn = TH1_LEN(argl[1]);
  for(i=0; rc==0 && i<nn; i++){
    rc = login_has_capability((char*)&argv[1][i],1,0);
  }
  if( g.thTrace ){
    Th_Trace("[anycap %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc);
  }
  Th_SetResultInt(interp, rc);
  return TH_OK;
}

/*
** TH1 command: combobox NAME TEXT-LIST NUMLINES
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156

1157
1158
1159
1160
1161
1162
1163
){
  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES");
  }
  if( enableOutput ){
    int height;
    Blob name;
    int nValue;
    const char *zValue;
    char *z, *zH;
    int nElem;
    int *aszElem;
    char **azElem;
    int i;

    if( Th_ToInt(interp, argv[3], argl[3], &height) ) return TH_ERROR;
    Th_SplitList(interp, argv[2], argl[2], &azElem, &aszElem, &nElem);
    blob_init(&name, (char*)argv[1], argl[1]);
    zValue = Th_Fetch(blob_str(&name), &nValue);

    zH = htmlize(blob_buffer(&name), blob_size(&name));
    z = mprintf("<select id=\"%s\" name=\"%s\" size=\"%d\">", zH, zH, height);
    free(zH);
    sendText(0,z, -1, 0);
    free(z);
    blob_reset(&name);
    for(i=0; i<nElem; i++){







|








|
|

>







1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
){
  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES");
  }
  if( enableOutput ){
    int height;
    Blob name;
    int nValue = 0;
    const char *zValue;
    char *z, *zH;
    int nElem;
    int *aszElem;
    char **azElem;
    int i;

    if( Th_ToInt(interp, argv[3], argl[3], &height) ) return TH_ERROR;
    Th_SplitList(interp, argv[2], TH1_LEN(argl[2]), &azElem, &aszElem, &nElem);
    blob_init(&name, (char*)argv[1], TH1_LEN(argl[1]));
    zValue = Th_Fetch(blob_str(&name), &nValue);
    nValue = TH1_LEN(nValue);
    zH = htmlize(blob_buffer(&name), blob_size(&name));
    z = mprintf("<select id=\"%s\" name=\"%s\" size=\"%d\">", zH, zH, height);
    free(zH);
    sendText(0,z, -1, 0);
    free(z);
    blob_reset(&name);
    for(i=0; i<nElem; i++){
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
  int iMin, iMax;
  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "linecount STRING MAX MIN");
  }
  if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR;
  if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR;
  z = argv[1];
  size = argl[1];
  for(n=1, i=0; i<size; i++){
    if( z[i]=='\n' ){
      n++;
      if( n>=iMax ) break;
    }
  }
  if( n<iMin ) n = iMin;







|







1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
  int iMin, iMax;
  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "linecount STRING MAX MIN");
  }
  if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR;
  if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR;
  z = argv[1];
  size = TH1_LEN(argl[1]);
  for(n=1, i=0; i<size; i++){
    if( z[i]=='\n' ){
      n++;
      if( n>=iMax ) break;
    }
  }
  if( n<iMin ) n = iMin;
1405
1406
1407
1408
1409
1410
1411
1412

1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430


1431
1432
1433
1434
1435
1436
1437


1438
1439
1440
1441
1442
1443
1444
  }else if( fossil_strnicmp(argv[1], "user\0", 5)==0 ){
    Th_SetResult(interp, g.zLogin ? g.zLogin : zDefault, -1);
    return TH_OK;
  }else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){
    Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1);
    return TH_OK;
  }else{
    Th_ErrorMessage(interp, "unsupported global state:", argv[1], argl[1]);

    return TH_ERROR;
  }
}

/*
** TH1 command: getParameter NAME ?DEFAULT?
**
** Return the value of the specified query parameter or the specified default
** value when there is no matching query parameter.
*/
static int getParameterCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  const char *zDefault = 0;


  if( argc!=2 && argc!=3 ){
    return Th_WrongNumArgs(interp, "getParameter NAME ?DEFAULT?");
  }
  if( argc==3 ){
    zDefault = argv[2];
  }
  Th_SetResult(interp, cgi_parameter(argv[1], zDefault), -1);


  return TH_OK;
}

/*
** TH1 command: setParameter NAME VALUE
**
** Sets the value of the specified query parameter.







|
>


















>
>






|
>
>







1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
  }else if( fossil_strnicmp(argv[1], "user\0", 5)==0 ){
    Th_SetResult(interp, g.zLogin ? g.zLogin : zDefault, -1);
    return TH_OK;
  }else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){
    Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1);
    return TH_OK;
  }else{
    Th_ErrorMessage(interp, "unsupported global state:",
                            argv[1], TH1_LEN(argl[1]));
    return TH_ERROR;
  }
}

/*
** TH1 command: getParameter NAME ?DEFAULT?
**
** Return the value of the specified query parameter or the specified default
** value when there is no matching query parameter.
*/
static int getParameterCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  const char *zDefault = 0;
  const char *zVal;
  int sz;
  if( argc!=2 && argc!=3 ){
    return Th_WrongNumArgs(interp, "getParameter NAME ?DEFAULT?");
  }
  if( argc==3 ){
    zDefault = argv[2];
  }
  zVal = cgi_parameter(argv[1], zDefault);
  sz = th_strlen(zVal);
  Th_SetResult(interp, zVal, TH1_ADD_TAINT(sz));
  return TH_OK;
}

/*
** TH1 command: setParameter NAME VALUE
**
** Sets the value of the specified query parameter.
1846
1847
1848
1849
1850
1851
1852





































1853
1854
1855
1856
1857
1858
1859
  char zUTime[50];
  fossil_cpu_times(0, &x);
  sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x);
  Th_SetResult(interp, zUTime, -1);
  return TH_OK;
}







































/*
** TH1 command: randhex  N
**
** Return N*2 random hexadecimal digits with N<50.  If N is omitted,
** use a value of 10.
*/







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
  char zUTime[50];
  fossil_cpu_times(0, &x);
  sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x);
  Th_SetResult(interp, zUTime, -1);
  return TH_OK;
}

/*
** TH1 command: taint STRING
**
** Return a copy of STRING that is marked as tainted.
*/
static int taintCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "STRING");
  }
  Th_SetResult(interp, argv[1], TH1_ADD_TAINT(argl[1]));
  return TH_OK;
}

/*
** TH1 command: untaint STRING
**
** Return a copy of STRING that is marked as untainted.
*/
static int untaintCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "STRING");
  }
  Th_SetResult(interp, argv[1], TH1_LEN(argl[1]));
  return TH_OK;
}

/*
** TH1 command: randhex  N
**
** Return N*2 random hexadecimal digits with N<50.  If N is omitted,
** use a value of 10.
*/
1921
1922
1923
1924
1925
1926
1927

1928

1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943







1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
  const char *zTail;
  int n, i;
  int res = TH_OK;
  int nVar;
  char *zErr = 0;
  int noComplain = 0;


  if( argc>3 && argl[1]==11 && strncmp(argv[1], "-nocomplain", 11)==0 ){

    argc--;
    argv++;
    argl++;
    noComplain = 1;
  }
  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "query SQL CODE");
  }
  if( g.db==0 ){
    if( noComplain ) return TH_OK;
    Th_ErrorMessage(interp, "database is not open", 0, 0);
    return TH_ERROR;
  }
  zSql = argv[1];
  nSql = argl[1];







  while( res==TH_OK && nSql>0 ){
    zErr = 0;
    report_restrict_sql(&zErr);
    g.dbIgnoreErrors++;
    rc = sqlite3_prepare_v2(g.db, argv[1], argl[1], &pStmt, &zTail);
    g.dbIgnoreErrors--;
    report_unrestrict_sql();
    if( rc!=0 || zErr!=0 ){
      if( noComplain ) return TH_OK;
      Th_ErrorMessage(interp, "SQL error: ",
                      zErr ? zErr : sqlite3_errmsg(g.db), -1);
      return TH_ERROR;
    }
    n = (int)(zTail - zSql);
    zSql += n;
    nSql -= n;
    if( pStmt==0 ) continue;
    nVar = sqlite3_bind_parameter_count(pStmt);
    for(i=1; i<=nVar; i++){
      const char *zVar = sqlite3_bind_parameter_name(pStmt, i);
      int szVar = zVar ? th_strlen(zVar) : 0;
      if( szVar>1 && zVar[0]=='$'
       && Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){
        int nVal;
        const char *zVal = Th_GetResult(interp, &nVal);
        sqlite3_bind_text(pStmt, i, zVal, nVal, SQLITE_TRANSIENT);
      }
    }
    while( res==TH_OK && ignore_errors_step(pStmt)==SQLITE_ROW ){
      int nCol = sqlite3_column_count(pStmt);
      for(i=0; i<nCol; i++){
        const char *zCol = sqlite3_column_name(pStmt, i);
        int szCol = th_strlen(zCol);
        const char *zVal = (const char*)sqlite3_column_text(pStmt, i);
        int szVal = sqlite3_column_bytes(pStmt, i);
        Th_SetVar(interp, zCol, szCol, zVal, szVal);
      }
      if( g.thTrace ){
        Th_Trace("query_eval {<pre>%#h</pre>}<br>\n", argl[2], argv[2]);
      }
      res = Th_Eval(interp, 0, argv[2], argl[2]);
      if( g.thTrace ){
        int nTrRes;
        char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
        Th_Trace("[query_eval] => %h {%#h}<br>\n",
                 Th_ReturnCodeName(res, 0), nTrRes, zTrRes);
      }
      if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK;
    }
    rc = sqlite3_finalize(pStmt);
    if( rc!=SQLITE_OK ){
      if( noComplain ) return TH_OK;
      Th_ErrorMessage(interp, "SQL error: ", sqlite3_errmsg(g.db), -1);







>
|
>















>
>
>
>
>
>
>




|




















|









|


|

|




|







1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
  const char *zTail;
  int n, i;
  int res = TH_OK;
  int nVar;
  char *zErr = 0;
  int noComplain = 0;

  if( argc>3 && TH1_LEN(argl[1])==11
   && strncmp(argv[1], "-nocomplain", 11)==0
  ){
    argc--;
    argv++;
    argl++;
    noComplain = 1;
  }
  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "query SQL CODE");
  }
  if( g.db==0 ){
    if( noComplain ) return TH_OK;
    Th_ErrorMessage(interp, "database is not open", 0, 0);
    return TH_ERROR;
  }
  zSql = argv[1];
  nSql = argl[1];
  if( TH1_TAINTED(nSql) ){
    if( Th_ReportTaint(interp,"query SQL",zSql,nSql) ){
      return TH_ERROR;
    }
    nSql = TH1_LEN(nSql);
  }

  while( res==TH_OK && nSql>0 ){
    zErr = 0;
    report_restrict_sql(&zErr);
    g.dbIgnoreErrors++;
    rc = sqlite3_prepare_v2(g.db, argv[1], TH1_LEN(argl[1]), &pStmt, &zTail);
    g.dbIgnoreErrors--;
    report_unrestrict_sql();
    if( rc!=0 || zErr!=0 ){
      if( noComplain ) return TH_OK;
      Th_ErrorMessage(interp, "SQL error: ",
                      zErr ? zErr : sqlite3_errmsg(g.db), -1);
      return TH_ERROR;
    }
    n = (int)(zTail - zSql);
    zSql += n;
    nSql -= n;
    if( pStmt==0 ) continue;
    nVar = sqlite3_bind_parameter_count(pStmt);
    for(i=1; i<=nVar; i++){
      const char *zVar = sqlite3_bind_parameter_name(pStmt, i);
      int szVar = zVar ? th_strlen(zVar) : 0;
      if( szVar>1 && zVar[0]=='$'
       && Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){
        int nVal;
        const char *zVal = Th_GetResult(interp, &nVal);
        sqlite3_bind_text(pStmt, i, zVal, TH1_LEN(nVal), SQLITE_TRANSIENT);
      }
    }
    while( res==TH_OK && ignore_errors_step(pStmt)==SQLITE_ROW ){
      int nCol = sqlite3_column_count(pStmt);
      for(i=0; i<nCol; i++){
        const char *zCol = sqlite3_column_name(pStmt, i);
        int szCol = th_strlen(zCol);
        const char *zVal = (const char*)sqlite3_column_text(pStmt, i);
        int szVal = sqlite3_column_bytes(pStmt, i);
        Th_SetVar(interp, zCol, szCol, zVal, TH1_ADD_TAINT(szVal));
      }
      if( g.thTrace ){
        Th_Trace("query_eval {<pre>%#h</pre>}<br>\n",TH1_LEN(argl[2]),argv[2]);
      }
      res = Th_Eval(interp, 0, argv[2], TH1_LEN(argl[2]));
      if( g.thTrace ){
        int nTrRes;
        char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
        Th_Trace("[query_eval] => %h {%#h}<br>\n",
                 Th_ReturnCodeName(res, 0), TH1_LEN(nTrRes), zTrRes);
      }
      if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK;
    }
    rc = sqlite3_finalize(pStmt);
    if( rc!=SQLITE_OK ){
      if( noComplain ) return TH_OK;
      Th_ErrorMessage(interp, "SQL error: ", sqlite3_errmsg(g.db), -1);
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
    rc = TH_ERROR;
  }else{
    Th_SetResult(interp, 0, 0);
    rc = TH_OK;
  }
  if( g.thTrace ){
    Th_Trace("[setting %s%#h] => %d<br>\n", strict ? "strict " : "",
             argl[nArg], argv[nArg], rc);
  }
  return rc;
}

/*
** TH1 command: glob_match ?-one? ?--? patternList string
**







|







2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
    rc = TH_ERROR;
  }else{
    Th_SetResult(interp, 0, 0);
    rc = TH_OK;
  }
  if( g.thTrace ){
    Th_Trace("[setting %s%#h] => %d<br>\n", strict ? "strict " : "",
             TH1_LEN(argl[nArg]), argv[nArg], rc);
  }
  return rc;
}

/*
** TH1 command: glob_match ?-one? ?--? patternList string
**
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
  if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
  if( nArg+2!=argc ){
    return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
  }
  zErr = re_compile(&pRe, argv[nArg], noCase);
  if( !zErr ){
    Th_SetResultInt(interp, re_match(pRe,
        (const unsigned char *)argv[nArg+1], argl[nArg+1]));
    rc = TH_OK;
  }else{
    Th_SetResult(interp, zErr, -1);
    rc = TH_ERROR;
  }
  re_free(pRe);
  return rc;







|







2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
  if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
  if( nArg+2!=argc ){
    return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
  }
  zErr = re_compile(&pRe, argv[nArg], noCase);
  if( !zErr ){
    Th_SetResultInt(interp, re_match(pRe,
        (const unsigned char *)argv[nArg+1], TH1_LEN(argl[nArg+1])));
    rc = TH_OK;
  }else{
    Th_SetResult(interp, zErr, -1);
    rc = TH_ERROR;
  }
  re_free(pRe);
  return rc;
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
  Blob payload;
  ReCompiled *pRe = 0;
  UrlData urlData;

  if( argc<2 || argc>5 ){
    return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS);
  }
  if( fossil_strnicmp(argv[nArg], "-asynchronous", argl[nArg])==0 ){
    fAsynchronous = 1; nArg++;
  }
  if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
  if( nArg+1!=argc && nArg+2!=argc ){
    return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
  }
  memset(&urlData, '\0', sizeof(urlData));







|







2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
  Blob payload;
  ReCompiled *pRe = 0;
  UrlData urlData;

  if( argc<2 || argc>5 ){
    return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS);
  }
  if( fossil_strnicmp(argv[nArg], "-asynchronous", TH1_LEN(argl[nArg]))==0 ){
    fAsynchronous = 1; nArg++;
  }
  if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
  if( nArg+1!=argc && nArg+2!=argc ){
    return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
  }
  memset(&urlData, '\0', sizeof(urlData));
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
    Th_SetResult(interp, "url not allowed", -1);
    re_free(pRe);
    return TH_ERROR;
  }
  re_free(pRe);
  blob_zero(&payload);
  if( nArg+2==argc ){
    blob_append(&payload, argv[nArg+1], argl[nArg+1]);
    zType = "POST";
  }else{
    zType = "GET";
  }
  if( fAsynchronous ){
    const char *zSep, *zParams;
    Blob hdr;







|







2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
    Th_SetResult(interp, "url not allowed", -1);
    re_free(pRe);
    return TH_ERROR;
  }
  re_free(pRe);
  blob_zero(&payload);
  if( nArg+2==argc ){
    blob_append(&payload, argv[nArg+1], TH1_LEN(argl[nArg+1]));
    zType = "POST";
  }else{
    zType = "GET";
  }
  if( fAsynchronous ){
    const char *zSep, *zParams;
    Blob hdr;
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
  const char * zStr;
  int nStr, rc;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "captureTh1 STRING");
  }
  pOrig = Th_SetOutputBlob(&out);
  zStr = argv[1];
  nStr = argl[1];
  rc = Th_Eval(g.interp, 0, zStr, nStr);
  Th_SetOutputBlob(pOrig);
  if(0==rc){
    Th_SetResult(g.interp, blob_str(&out), blob_size(&out));
  }
  blob_reset(&out);
  return rc;







|







2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
  const char * zStr;
  int nStr, rc;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "captureTh1 STRING");
  }
  pOrig = Th_SetOutputBlob(&out);
  zStr = argv[1];
  nStr = TH1_LEN(argl[1]);
  rc = Th_Eval(g.interp, 0, zStr, nStr);
  Th_SetOutputBlob(pOrig);
  if(0==rc){
    Th_SetResult(g.interp, blob_str(&out), blob_size(&out));
  }
  blob_reset(&out);
  return rc;
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
    {"checkout",      checkoutCmd,          0},
    {"combobox",      comboboxCmd,          0},
    {"copybtn",       copybtnCmd,           0},
    {"date",          dateCmd,              0},
    {"decorate",      wikiCmd,              (void*)&aFlags[2]},
    {"defHeader",     defHeaderCmd,         0},
    {"dir",           dirCmd,               0},
    {"enable_htmlify",enableHtmlifyCmd,     0},
    {"enable_output", enableOutputCmd,      0},
    {"encode64",      encode64Cmd,          0},
    {"getParameter",  getParameterCmd,      0},
    {"glob_match",    globMatchCmd,         0},
    {"globalState",   globalStateCmd,       0},
    {"httpize",       httpizeCmd,           0},
    {"hascap",        hascapCmd,            (void*)&zeroInt},







<







2381
2382
2383
2384
2385
2386
2387

2388
2389
2390
2391
2392
2393
2394
    {"checkout",      checkoutCmd,          0},
    {"combobox",      comboboxCmd,          0},
    {"copybtn",       copybtnCmd,           0},
    {"date",          dateCmd,              0},
    {"decorate",      wikiCmd,              (void*)&aFlags[2]},
    {"defHeader",     defHeaderCmd,         0},
    {"dir",           dirCmd,               0},

    {"enable_output", enableOutputCmd,      0},
    {"encode64",      encode64Cmd,          0},
    {"getParameter",  getParameterCmd,      0},
    {"glob_match",    globMatchCmd,         0},
    {"globalState",   globalStateCmd,       0},
    {"httpize",       httpizeCmd,           0},
    {"hascap",        hascapCmd,            (void*)&zeroInt},
2385
2386
2387
2388
2389
2390
2391

2392
2393
2394

2395
2396
2397
2398
2399
2400
2401
    {"searchable",    searchableCmd,        0},
    {"setParameter",  setParameterCmd,      0},
    {"setting",       settingCmd,           0},
    {"styleFooter",   styleFooterCmd,       0},
    {"styleHeader",   styleHeaderCmd,       0},
    {"styleScript",   styleScriptCmd,       0},
    {"submenu",       submenuCmd,           0},

    {"tclReady",      tclReadyCmd,          0},
    {"trace",         traceCmd,             0},
    {"stime",         stimeCmd,             0},

    {"unversioned",   unversionedCmd,       0},
    {"utime",         utimeCmd,             0},
    {"verifyCsrf",    verifyCsrfCmd,        0},
    {"verifyLogin",   verifyLoginCmd,       0},
    {"wiki",          wikiCmd,              (void*)&aFlags[0]},
    {"wiki_assoc",    wikiAssocCmd,         0},
    {0, 0, 0}







>



>







2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
    {"searchable",    searchableCmd,        0},
    {"setParameter",  setParameterCmd,      0},
    {"setting",       settingCmd,           0},
    {"styleFooter",   styleFooterCmd,       0},
    {"styleHeader",   styleHeaderCmd,       0},
    {"styleScript",   styleScriptCmd,       0},
    {"submenu",       submenuCmd,           0},
    {"taint",         taintCmd,             0},
    {"tclReady",      tclReadyCmd,          0},
    {"trace",         traceCmd,             0},
    {"stime",         stimeCmd,             0},
    {"untaint",       untaintCmd,           0},
    {"unversioned",   unversionedCmd,       0},
    {"utime",         utimeCmd,             0},
    {"verifyCsrf",    verifyCsrfCmd,        0},
    {"verifyLogin",   verifyLoginCmd,       0},
    {"wiki",          wikiCmd,              (void*)&aFlags[0]},
    {"wiki_assoc",    wikiAssocCmd,         0},
    {0, 0, 0}
2492
2493
2494
2495
2496
2497
2498
















2499
2500
2501
2502
2503
2504
2505
  if( zValue ){
    if( g.thTrace ){
      Th_Trace("set %h {%h}<br>\n", zName, zValue);
    }
    Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue));
  }
}

















/*
** Appends an element to a TH1 list value.  This function is called by the
** transfer subsystem; therefore, it must be very careful to avoid doing
** any unnecessary work.  To that end, the TH1 subsystem will not be called
** or initialized if the list pointer is zero (i.e. which will be the case
** when TH1 transfer hooks are disabled).







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
  if( zValue ){
    if( g.thTrace ){
      Th_Trace("set %h {%h}<br>\n", zName, zValue);
    }
    Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue));
  }
}

/*
** Store a string value in a variable in the interpreter
** with the "taint" marking, so that TH1 knows that this
** variable contains content under the control of the remote
** user and presents a risk of XSS or SQL-injection attacks.
*/
void Th_StoreUnsafe(const char *zName, const char *zValue){
  Th_FossilInit(TH_INIT_DEFAULT);
  if( zValue ){
    if( g.thTrace ){
      Th_Trace("set %h [taint {%h}]<br>\n", zName, zValue);
    }
    Th_SetVar(g.interp, zName, -1, zValue, TH1_ADD_TAINT(strlen(zValue)));
  }
}

/*
** Appends an element to a TH1 list value.  This function is called by the
** transfer subsystem; therefore, it must be very careful to avoid doing
** any unnecessary work.  To that end, the TH1 subsystem will not be called
** or initialized if the list pointer is zero (i.e. which will be the case
** when TH1 transfer hooks are disabled).
2678
2679
2680
2681
2682
2683
2684

2685
2686
2687
2688
2689
2690
2691
  if( rc==TH_ERROR ){
    int nResult = 0;
    char *zResult = (char*)Th_GetResult(g.interp, &nResult);
    /*
    ** Make sure that the TH1 script error was not caused by a "missing"
    ** command hook handler as that is not actually an error condition.
    */

    if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){
      sendError(0,zResult, nResult, 0);
    }else{
      /*
      ** There is no command hook handler "installed".  This situation
      ** is NOT actually an error.
      */







>







2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
  if( rc==TH_ERROR ){
    int nResult = 0;
    char *zResult = (char*)Th_GetResult(g.interp, &nResult);
    /*
    ** Make sure that the TH1 script error was not caused by a "missing"
    ** command hook handler as that is not actually an error condition.
    */
    nResult = TH1_LEN(nResult);
    if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){
      sendError(0,zResult, nResult, 0);
    }else{
      /*
      ** There is no command hook handler "installed".  This situation
      ** is NOT actually an error.
      */
2765
2766
2767
2768
2769
2770
2771

2772
2773
2774
2775
2776
2777
2778
  if( rc==TH_ERROR ){
    int nResult = 0;
    char *zResult = (char*)Th_GetResult(g.interp, &nResult);
    /*
    ** Make sure that the TH1 script error was not caused by a "missing"
    ** webpage hook handler as that is not actually an error condition.
    */

    if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){
      sendError(0,zResult, nResult, 1);
    }else{
      /*
      ** There is no webpage hook handler "installed".  This situation
      ** is NOT actually an error.
      */







>







2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
  if( rc==TH_ERROR ){
    int nResult = 0;
    char *zResult = (char*)Th_GetResult(g.interp, &nResult);
    /*
    ** Make sure that the TH1 script error was not caused by a "missing"
    ** webpage hook handler as that is not actually an error condition.
    */
    nResult = TH1_LEN(nResult);
    if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){
      sendError(0,zResult, nResult, 1);
    }else{
      /*
      ** There is no webpage hook handler "installed".  This situation
      ** is NOT actually an error.
      */
2892
2893
2894
2895
2896
2897
2898




2899

2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
        nVar = n;
        encode = 0;
      }
      rc = Th_GetVar(g.interp, (char*)zVar, nVar);
      z += i+1+n;
      i = 0;
      zResult = (char*)Th_GetResult(g.interp, &n);




      sendText(pOut,(char*)zResult, n, encode);

    }else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){
      sendText(pOut,z, i, 0);
      z += i+5;
      for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){}
      if( g.thTrace ){
        Th_Trace("render_eval {<pre>%#h</pre>}<br>\n", i, z);
      }
      rc = Th_Eval(g.interp, 0, (const char*)z, i);
      if( g.thTrace ){
        int nTrRes;
        char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
        Th_Trace("[render_eval] => %h {%#h}<br>\n",
                 Th_ReturnCodeName(rc, 0), nTrRes, zTrRes);
      }
      if( rc!=TH_OK ) break;
      z += i;
      if( z[0] ){ z += 6; }
      i = 0;
    }else{
      i++;







>
>
>
>
|
>












|







2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
        nVar = n;
        encode = 0;
      }
      rc = Th_GetVar(g.interp, (char*)zVar, nVar);
      z += i+1+n;
      i = 0;
      zResult = (char*)Th_GetResult(g.interp, &n);
      if( !TH1_TAINTED(n)
       || encode 
       || Th_ReportTaint(g.interp, "inline variable", zVar, nVar)==TH_OK
      ){
        sendText(pOut,(char*)zResult, n, encode);
      }
    }else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){
      sendText(pOut,z, i, 0);
      z += i+5;
      for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){}
      if( g.thTrace ){
        Th_Trace("render_eval {<pre>%#h</pre>}<br>\n", i, z);
      }
      rc = Th_Eval(g.interp, 0, (const char*)z, i);
      if( g.thTrace ){
        int nTrRes;
        char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
        Th_Trace("[render_eval] => %h {%#h}<br>\n",
                 Th_ReturnCodeName(rc, 0), TH1_LEN(nTrRes), zTrRes);
      }
      if( rc!=TH_OK ) break;
      z += i;
      if( z[0] ){ z += 6; }
      i = 0;
    }else{
      i++;
2947
2948
2949
2950
2951
2952
2953
2954


2955
















2956
2957

















































2958
2959
2960
2961
2962
2963
2964
    ** Th_RenderToBlob() will output directly to the CGI buffer (in
    ** CGI mode) or stdout (in CLI mode). Recursive calls, however,
    ** e.g. via the "render" script function binding, need to use the
    ** pThOut blob in order to avoid out-of-order output if
    ** Th_SetOutputBlob() has been called. If it has not been called,
    ** pThOut will be 0, which will redirect the output to CGI/stdout,
    ** as appropriate. We need to pass on g.th1Flags for the case of
    ** recursive calls, so that, e.g., TH_INIT_NO_ENCODE does not get


    ** inadvertently toggled off by a recursive call.
















    */;
}


















































/*
** COMMAND: test-th-render
**
** Usage: %fossil test-th-render FILE
**
** Read the content of the file named "FILE" as if it were a header or







|
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
    ** Th_RenderToBlob() will output directly to the CGI buffer (in
    ** CGI mode) or stdout (in CLI mode). Recursive calls, however,
    ** e.g. via the "render" script function binding, need to use the
    ** pThOut blob in order to avoid out-of-order output if
    ** Th_SetOutputBlob() has been called. If it has not been called,
    ** pThOut will be 0, which will redirect the output to CGI/stdout,
    ** as appropriate. We need to pass on g.th1Flags for the case of
    ** recursive calls.
    */;
}

/*
** SETTING: vuln-report           width=8 default=log
**
** This setting controls Fossil's behavior when it encounters a potential
** XSS or SQL-injection vulnerability due to misuse of TH1 configuration
** scripts.  Choices are:
**
**    off            Do nothing.  Ignore the vulnerability.
**
**    log            Write a report of the problem into the error log.
**
**    block          Like "log" but also prevent the offending TH1 command
**                   from running.
**
**    fatal          Render an error message page instead of the requested
**                   page.
*/

/*
** Report misuse of a tainted string in TH1.
**
** The behavior depends on the vuln-report setting.  If "off", this routine
** is a no-op.  Otherwise, right a message into the error log.  If
** vuln-report is "log", that is all that happens.  But for any other
** value of vuln-report, a fatal error is raised.
*/
int Th_ReportTaint(
  Th_Interp *interp,       /* Report error here, if an error is reported */
  const char *zWhere,      /* Where the tainted string appears */
  const char *zStr,        /* The tainted string */
  int nStr                 /* Length of the tainted string */
){
  static const char *zDisp = 0;   /* Dispensation; what to do with the error */
  const char *zVulnType;          /* Type of vulnerability */

  if( zDisp==0 ) zDisp = db_get("vuln-report","log");
  if( is_false(zDisp) ) return 0;
  if( strstr(zWhere,"SQL")!=0 ){
    zVulnType = "SQL-injection";
  }else{
    zVulnType = "XSS";
  }
  nStr = TH1_LEN(nStr);
  fossil_errorlog("possible TH1 %s vulnerability due to tainted %s: \"%.*s\"",
                  zVulnType, zWhere, nStr, zStr);
  if( strcmp(zDisp,"log")==0 ){
    return 0;
  }
  if( strcmp(zDisp,"block")==0 ){
    char *z = mprintf("tainted %s: \"", zWhere);
    Th_ErrorMessage(interp, z, zStr, nStr);
    fossil_free(z);
  }else{
    char *z = mprintf("%#h", nStr, zStr);
    zDisp = "off";
    cgi_reset_content();
    style_submenu_enable(0);
    style_set_current_feature("error");
    style_header("Configuration Error");
    @ <p>Error in a TH1 configuration script: 
    @ tainted %h(zWhere): "%z(z)"
    style_finish_page();
    cgi_reply();
    fossil_exit(1);
  }
  return 1;
}

/*
** COMMAND: test-th-render
**
** Usage: %fossil test-th-render FILE
**
** Read the content of the file named "FILE" as if it were a header or
2990
2991
2992
2993
2994
2995
2996

2997
2998
2999
3000
3001
3002
3003
    g.useLocalauth = 1;
  }
  if( find_option("set-user-caps", 0, 0)!=0 ){
    const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
    login_set_capabilities(zCap ? zCap : "sx", 0);
    g.useLocalauth = 1;
  }

  verify_all_options();
  if( g.argc<3 ){
    usage("FILE");
  }
  blob_zero(&in);
  blob_read_from_file(&in, g.argv[2], ExtFILE);
  Th_Render(blob_str(&in));







>







3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
    g.useLocalauth = 1;
  }
  if( find_option("set-user-caps", 0, 0)!=0 ){
    const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
    login_set_capabilities(zCap ? zCap : "sx", 0);
    g.useLocalauth = 1;
  }
  db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
  verify_all_options();
  if( g.argc<3 ){
    usage("FILE");
  }
  blob_zero(&in);
  blob_read_from_file(&in, g.argv[2], ExtFILE);
  Th_Render(blob_str(&in));
3042
3043
3044
3045
3046
3047
3048

3049
3050
3051
3052
3053
3054
3055
    g.useLocalauth = 1;
  }
  if( find_option("set-user-caps", 0, 0)!=0 ){
    const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
    login_set_capabilities(zCap ? zCap : "sx", 0);
    g.useLocalauth = 1;
  }

  verify_all_options();
  if( g.argc!=3 ){
    usage("script");
  }
  if(file_isfile(g.argv[2], ExtFILE)){
    blob_read_from_file(&code, g.argv[2], ExtFILE);
    zCode = blob_str(&code);







>







3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
    g.useLocalauth = 1;
  }
  if( find_option("set-user-caps", 0, 0)!=0 ){
    const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
    login_set_capabilities(zCap ? zCap : "sx", 0);
    g.useLocalauth = 1;
  }
  db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
  verify_all_options();
  if( g.argc!=3 ){
    usage("script");
  }
  if(file_isfile(g.argv[2], ExtFILE)){
    blob_read_from_file(&code, g.argv[2], ExtFILE);
    zCode = blob_str(&code);
Changes to src/th_tcl.c.
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
** arguments from TH1 to Tcl.
*/
#define USE_ARGV_TO_OBJV() \
  int objc;                \
  Tcl_Obj **objv;          \
  int obji;

#define COPY_ARGV_TO_OBJV()                                         \
  objc = argc-1;                                                    \
  objv = (Tcl_Obj **)ckalloc((unsigned)(objc * sizeof(Tcl_Obj *))); \
  for(obji=1; obji<argc; obji++){                                   \
    objv[obji-1] = Tcl_NewStringObj(argv[obji], argl[obji]);        \
    Tcl_IncrRefCount(objv[obji-1]);                                 \
  }

#define FREE_ARGV_TO_OBJV()         \
  for(obji=1; obji<argc; obji++){   \
    Tcl_DecrRefCount(objv[obji-1]); \
    objv[obji-1] = 0;               \
  }                                 \







|
|
|
|
|
|







39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
** arguments from TH1 to Tcl.
*/
#define USE_ARGV_TO_OBJV() \
  int objc;                \
  Tcl_Obj **objv;          \
  int obji;

#define COPY_ARGV_TO_OBJV()                                           \
  objc = argc-1;                                                      \
  objv = (Tcl_Obj **)ckalloc((unsigned)(objc * sizeof(Tcl_Obj *)));   \
  for(obji=1; obji<argc; obji++){                                     \
    objv[obji-1] = Tcl_NewStringObj(argv[obji], TH1_LEN(argl[obji])); \
    Tcl_IncrRefCount(objv[obji-1]);                                   \
  }

#define FREE_ARGV_TO_OBJV()         \
  for(obji=1; obji<argc; obji++){   \
    Tcl_DecrRefCount(objv[obji-1]); \
    objv[obji-1] = 0;               \
  }                                 \
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
** when the Tcl library is being loaded dynamically by a stubs-enabled
** application (i.e. the inverse of using a stubs-enabled package).  These are
** the only Tcl API functions that MUST be called prior to being able to call
** Tcl_InitStubs (i.e. because it requires a Tcl interpreter).  For complete
** cleanup if the Tcl stubs initialization fails somehow, the Tcl_DeleteInterp
** and Tcl_Finalize function types are also required.
*/
typedef void (tcl_FindExecutableProc) (const char *);
typedef Tcl_Interp *(tcl_CreateInterpProc) (void);
typedef void (tcl_DeleteInterpProc) (Tcl_Interp *);
typedef void (tcl_FinalizeProc) (void);

/*
** The function types for the "hook" functions to be called before and after a
** TH1 command makes a call to evaluate a Tcl script.  If the "pre" function







|







181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
** when the Tcl library is being loaded dynamically by a stubs-enabled
** application (i.e. the inverse of using a stubs-enabled package).  These are
** the only Tcl API functions that MUST be called prior to being able to call
** Tcl_InitStubs (i.e. because it requires a Tcl interpreter).  For complete
** cleanup if the Tcl stubs initialization fails somehow, the Tcl_DeleteInterp
** and Tcl_Finalize function types are also required.
*/
typedef const char *(tcl_FindExecutableProc) (const char *);
typedef Tcl_Interp *(tcl_CreateInterpProc) (void);
typedef void (tcl_DeleteInterpProc) (Tcl_Interp *);
typedef void (tcl_FinalizeProc) (void);

/*
** The function types for the "hook" functions to be called before and after a
** TH1 command makes a call to evaluate a Tcl script.  If the "pre" function
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
** Creates and initializes a Tcl interpreter for use with the specified TH1
** interpreter.  Stores the created Tcl interpreter in the Tcl context supplied
** by the caller.  This must be declared here because quite a few functions in
** this file need to use it before it can be defined.
*/
static int createTclInterp(Th_Interp *interp, void *pContext);

/*
** Returns the TH1 return code corresponding to the specified Tcl
** return code.
*/
static int getTh1ReturnCode(
  int rc /* The Tcl return code value to convert. */
){
  switch( rc ){
    case /*0*/ TCL_OK:       return /*0*/ TH_OK;
    case /*1*/ TCL_ERROR:    return /*1*/ TH_ERROR;
    case /*2*/ TCL_RETURN:   return /*3*/ TH_RETURN;
    case /*3*/ TCL_BREAK:    return /*2*/ TH_BREAK;
    case /*4*/ TCL_CONTINUE: return /*4*/ TH_CONTINUE;
    default /*?*/:           return /*?*/ rc;
  }
}

/*
** Returns the Tcl return code corresponding to the specified TH1
** return code.
*/
static int getTclReturnCode(
  int rc /* The TH1 return code value to convert. */
){







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







319
320
321
322
323
324
325

















326
327
328
329
330
331
332
** Creates and initializes a Tcl interpreter for use with the specified TH1
** interpreter.  Stores the created Tcl interpreter in the Tcl context supplied
** by the caller.  This must be declared here because quite a few functions in
** this file need to use it before it can be defined.
*/
static int createTclInterp(Th_Interp *interp, void *pContext);


















/*
** Returns the Tcl return code corresponding to the specified TH1
** return code.
*/
static int getTclReturnCode(
  int rc /* The TH1 return code value to convert. */
){
385
386
387
388
389
390
391


392
393
394
395
396
397
398
399
400
401
402


403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
** If the length pointer is NULL, the length will not be stored.
*/
static char *getTclResult(
  Tcl_Interp *pInterp,
  int *pN
){
  Tcl_Obj *resultPtr;



  if( !pInterp ){ /* This should not happen. */
    if( pN ) *pN = 0;
    return 0;
  }
  resultPtr = Tcl_GetObjResult(pInterp);
  if( !resultPtr ){ /* This should not happen either? */
    if( pN ) *pN = 0;
    return 0;
  }
  return Tcl_GetStringFromObj(resultPtr, pN);


}

/*
** Tcl context information used by TH1.  This structure definition has been
** copied from and should be kept in sync with the one in "main.c".
*/
struct TclContext {
  int argc;           /* Number of original arguments. */
  char **argv;        /* Full copy of the original arguments. */
  void *hLibrary;     /* The Tcl library module handle. */
  tcl_FindExecutableProc *xFindExecutable; /* Tcl_FindExecutable() pointer. */
  tcl_CreateInterpProc *xCreateInterp;     /* Tcl_CreateInterp() pointer. */
  tcl_DeleteInterpProc *xDeleteInterp;     /* Tcl_DeleteInterp() pointer. */
  tcl_FinalizeProc *xFinalize;             /* Tcl_Finalize() pointer. */
  Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */
  int useObjProc;     /* Non-zero if an objProc can be called directly. */
  int useTip285;      /* Non-zero if TIP #285 is available. */
  const char *setup;  /* The optional Tcl setup script. */
  tcl_NotifyProc *xPreEval;  /* Optional, called before Tcl_Eval*(). */
  void *pPreContext;         /* Optional, provided to xPreEval(). */
  tcl_NotifyProc *xPostEval; /* Optional, called after Tcl_Eval*(). */
  void *pPostContext;        /* Optional, provided to xPostEval(). */
};

/*
** This function calls the configured xPreEval or xPostEval functions, if any.
** May have arbitrary side-effects.  This function returns the result of the
** called notification function or the value of the rc argument if there is no
** notification function configured.
*/
static int notifyPreOrPostEval(
  int bIsPost,
  Th_Interp *interp,
  void *ctx,
  int argc,
  const char **argv,
  int *argl,
  int rc
){
  struct TclContext *tclContext = (struct TclContext *)ctx;
  tcl_NotifyProc *xNotifyProc;

  if( !tclContext ){
    Th_ErrorMessage(interp,
        "invalid Tcl context", (const char *)"", 0);
    return TH_ERROR;
  }
  xNotifyProc = bIsPost ? tclContext->xPostEval : tclContext->xPreEval;
  if( xNotifyProc ){
    rc = xNotifyProc(bIsPost ?
        tclContext->pPostContext : tclContext->pPreContext,
        interp, ctx, argc, argv, argl, rc);
  }
  return rc;
}

/*
** TH1 command: tclEval arg ?arg ...?
**
** Evaluates the Tcl script and returns its result verbatim.  If a Tcl script
** error is generated, it will be transformed into a TH1 script error.  The
** Tcl interpreter will be created automatically if it has not been already.
*/







>
>










|
>
>


















<
<
<
<


<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407




408
409
































410
411
412
413
414
415
416
** If the length pointer is NULL, the length will not be stored.
*/
static char *getTclResult(
  Tcl_Interp *pInterp,
  int *pN
){
  Tcl_Obj *resultPtr;
  Tcl_Size n;
  char *zRes;

  if( !pInterp ){ /* This should not happen. */
    if( pN ) *pN = 0;
    return 0;
  }
  resultPtr = Tcl_GetObjResult(pInterp);
  if( !resultPtr ){ /* This should not happen either? */
    if( pN ) *pN = 0;
    return 0;
  }
  zRes = Tcl_GetStringFromObj(resultPtr, &n);
  *pN = (int)n;
  return zRes;
}

/*
** Tcl context information used by TH1.  This structure definition has been
** copied from and should be kept in sync with the one in "main.c".
*/
struct TclContext {
  int argc;           /* Number of original arguments. */
  char **argv;        /* Full copy of the original arguments. */
  void *hLibrary;     /* The Tcl library module handle. */
  tcl_FindExecutableProc *xFindExecutable; /* Tcl_FindExecutable() pointer. */
  tcl_CreateInterpProc *xCreateInterp;     /* Tcl_CreateInterp() pointer. */
  tcl_DeleteInterpProc *xDeleteInterp;     /* Tcl_DeleteInterp() pointer. */
  tcl_FinalizeProc *xFinalize;             /* Tcl_Finalize() pointer. */
  Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */
  int useObjProc;     /* Non-zero if an objProc can be called directly. */
  int useTip285;      /* Non-zero if TIP #285 is available. */
  const char *setup;  /* The optional Tcl setup script. */




};

































/*
** TH1 command: tclEval arg ?arg ...?
**
** Evaluates the Tcl script and returns its result verbatim.  If a Tcl script
** error is generated, it will be transformed into a TH1 script error.  The
** Tcl interpreter will be created automatically if it has not been already.
*/
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
    return Th_WrongNumArgs(interp, "tclEval arg ?arg ...?");
  }
  tclInterp = GET_CTX_TCL_INTERP(ctx);
  if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
    Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
    return TH_ERROR;
  }
  rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc);
  if( rc!=TH_OK ){
    return rc;
  }
  Tcl_Preserve((ClientData)tclInterp);
  if( argc==2 ){
    objPtr = Tcl_NewStringObj(argv[1], argl[1]);
    Tcl_IncrRefCount(objPtr);
    rc = Tcl_EvalObjEx(tclInterp, objPtr, 0);
    Tcl_DecrRefCount(objPtr); objPtr = 0;
  }else{
    USE_ARGV_TO_OBJV();
    COPY_ARGV_TO_OBJV();
    objPtr = Tcl_ConcatObj(objc, objv);
    Tcl_IncrRefCount(objPtr);
    rc = Tcl_EvalObjEx(tclInterp, objPtr, 0);
    Tcl_DecrRefCount(objPtr); objPtr = 0;
    FREE_ARGV_TO_OBJV();
  }
  zResult = getTclResult(tclInterp, &nResult);
  Th_SetResult(interp, zResult, nResult);
  Tcl_Release((ClientData)tclInterp);
  rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl,
                           getTh1ReturnCode(rc));
  return rc;
}

/*
** TH1 command: tclExpr arg ?arg ...?
**
** Evaluates the Tcl expression and returns its result verbatim.  If a Tcl







<
<
<
<


|















<
<







434
435
436
437
438
439
440




441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458


459
460
461
462
463
464
465
    return Th_WrongNumArgs(interp, "tclEval arg ?arg ...?");
  }
  tclInterp = GET_CTX_TCL_INTERP(ctx);
  if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
    Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
    return TH_ERROR;
  }




  Tcl_Preserve((ClientData)tclInterp);
  if( argc==2 ){
    objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
    Tcl_IncrRefCount(objPtr);
    rc = Tcl_EvalObjEx(tclInterp, objPtr, 0);
    Tcl_DecrRefCount(objPtr); objPtr = 0;
  }else{
    USE_ARGV_TO_OBJV();
    COPY_ARGV_TO_OBJV();
    objPtr = Tcl_ConcatObj(objc, objv);
    Tcl_IncrRefCount(objPtr);
    rc = Tcl_EvalObjEx(tclInterp, objPtr, 0);
    Tcl_DecrRefCount(objPtr); objPtr = 0;
    FREE_ARGV_TO_OBJV();
  }
  zResult = getTclResult(tclInterp, &nResult);
  Th_SetResult(interp, zResult, nResult);
  Tcl_Release((ClientData)tclInterp);


  return rc;
}

/*
** TH1 command: tclExpr arg ?arg ...?
**
** Evaluates the Tcl expression and returns its result verbatim.  If a Tcl
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569

570

571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
    return Th_WrongNumArgs(interp, "tclExpr arg ?arg ...?");
  }
  tclInterp = GET_CTX_TCL_INTERP(ctx);
  if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
    Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
    return TH_ERROR;
  }
  rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc);
  if( rc!=TH_OK ){
    return rc;
  }
  Tcl_Preserve((ClientData)tclInterp);
  if( argc==2 ){
    objPtr = Tcl_NewStringObj(argv[1], argl[1]);
    Tcl_IncrRefCount(objPtr);
    rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
    Tcl_DecrRefCount(objPtr); objPtr = 0;
  }else{
    USE_ARGV_TO_OBJV();
    COPY_ARGV_TO_OBJV();
    objPtr = Tcl_ConcatObj(objc, objv);
    Tcl_IncrRefCount(objPtr);
    rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
    Tcl_DecrRefCount(objPtr); objPtr = 0;
    FREE_ARGV_TO_OBJV();
  }
  if( rc==TCL_OK ){

    zResult = Tcl_GetStringFromObj(resultObjPtr, &nResult);

  }else{
    zResult = getTclResult(tclInterp, &nResult);
  }
  Th_SetResult(interp, zResult, nResult);
  if( rc==TCL_OK ){
    Tcl_DecrRefCount(resultObjPtr); resultObjPtr = 0;
  }
  Tcl_Release((ClientData)tclInterp);
  rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl,
                           getTh1ReturnCode(rc));
  return rc;
}

/*
** TH1 command: tclInvoke command ?arg ...?
**
** Invokes the Tcl command using the supplied arguments.  No additional







<
<
<
<


|













>
|
>



|




<
<







488
489
490
491
492
493
494




495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521


522
523
524
525
526
527
528
    return Th_WrongNumArgs(interp, "tclExpr arg ?arg ...?");
  }
  tclInterp = GET_CTX_TCL_INTERP(ctx);
  if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
    Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
    return TH_ERROR;
  }




  Tcl_Preserve((ClientData)tclInterp);
  if( argc==2 ){
    objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
    Tcl_IncrRefCount(objPtr);
    rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
    Tcl_DecrRefCount(objPtr); objPtr = 0;
  }else{
    USE_ARGV_TO_OBJV();
    COPY_ARGV_TO_OBJV();
    objPtr = Tcl_ConcatObj(objc, objv);
    Tcl_IncrRefCount(objPtr);
    rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
    Tcl_DecrRefCount(objPtr); objPtr = 0;
    FREE_ARGV_TO_OBJV();
  }
  if( rc==TCL_OK ){
    Tcl_Size szResult = 0;
    zResult = Tcl_GetStringFromObj(resultObjPtr, &szResult);
    nResult = (int)szResult;
  }else{
    zResult = getTclResult(tclInterp, &nResult);
  }
  Th_SetResult(interp, zResult, (int)nResult);
  if( rc==TCL_OK ){
    Tcl_DecrRefCount(resultObjPtr); resultObjPtr = 0;
  }
  Tcl_Release((ClientData)tclInterp);


  return rc;
}

/*
** TH1 command: tclInvoke command ?arg ...?
**
** Invokes the Tcl command using the supplied arguments.  No additional
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
    return Th_WrongNumArgs(interp, "tclInvoke command ?arg ...?");
  }
  tclInterp = GET_CTX_TCL_INTERP(ctx);
  if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
    Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
    return TH_ERROR;
  }
  rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc);
  if( rc!=TH_OK ){
    return rc;
  }
  Tcl_Preserve((ClientData)tclInterp);
#if !defined(USE_TCL_EVALOBJV) || !USE_TCL_EVALOBJV
  if( GET_CTX_TCL_USEOBJPROC(ctx) ){
    Tcl_Command command;
    Tcl_CmdInfo cmdInfo;
    Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], argl[1]);
    Tcl_IncrRefCount(objPtr);
    command = Tcl_GetCommandFromObj(tclInterp, objPtr);
    if( !command || Tcl_GetCommandInfoFromToken(command, &cmdInfo)==0 ){
      Th_ErrorMessage(interp, "Tcl command not found:", argv[1], argl[1]);
      Tcl_DecrRefCount(objPtr); objPtr = 0;
      Tcl_Release((ClientData)tclInterp);
      return TH_ERROR;







<
<
<
<





|







549
550
551
552
553
554
555




556
557
558
559
560
561
562
563
564
565
566
567
568
    return Th_WrongNumArgs(interp, "tclInvoke command ?arg ...?");
  }
  tclInterp = GET_CTX_TCL_INTERP(ctx);
  if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
    Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
    return TH_ERROR;
  }




  Tcl_Preserve((ClientData)tclInterp);
#if !defined(USE_TCL_EVALOBJV) || !USE_TCL_EVALOBJV
  if( GET_CTX_TCL_USEOBJPROC(ctx) ){
    Tcl_Command command;
    Tcl_CmdInfo cmdInfo;
    Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
    Tcl_IncrRefCount(objPtr);
    command = Tcl_GetCommandFromObj(tclInterp, objPtr);
    if( !command || Tcl_GetCommandInfoFromToken(command, &cmdInfo)==0 ){
      Th_ErrorMessage(interp, "Tcl command not found:", argv[1], argl[1]);
      Tcl_DecrRefCount(objPtr); objPtr = 0;
      Tcl_Release((ClientData)tclInterp);
      return TH_ERROR;
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
    COPY_ARGV_TO_OBJV();
    rc = Tcl_EvalObjv(tclInterp, objc, objv, 0);
    FREE_ARGV_TO_OBJV();
  }
  zResult = getTclResult(tclInterp, &nResult);
  Th_SetResult(interp, zResult, nResult);
  Tcl_Release((ClientData)tclInterp);
  rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl,
                           getTh1ReturnCode(rc));
  return rc;
}

/*
** TH1 command: tclIsSafe
**
** Returns non-zero if the Tcl interpreter is "safe".  The Tcl interpreter







<
<







584
585
586
587
588
589
590


591
592
593
594
595
596
597
    COPY_ARGV_TO_OBJV();
    rc = Tcl_EvalObjv(tclInterp, objc, objv, 0);
    FREE_ARGV_TO_OBJV();
  }
  zResult = getTclResult(tclInterp, &nResult);
  Th_SetResult(interp, zResult, nResult);
  Tcl_Release((ClientData)tclInterp);


  return rc;
}

/*
** TH1 command: tclIsSafe
**
** Returns non-zero if the Tcl interpreter is "safe".  The Tcl interpreter
765
766
767
768
769
770
771

772
773
774
775
776
777
778
779
780
781
782
783
784

785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804

805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
  ClientData clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *const objv[]
){
  Th_Interp *th1Interp;
  int nArg;

  const char *arg;
  int rc;

  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "arg");
    return TCL_ERROR;
  }
  th1Interp = (Th_Interp *)clientData;
  if( !th1Interp ){
    Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
    return TCL_ERROR;
  }
  arg = Tcl_GetStringFromObj(objv[1], &nArg);

  rc = Th_Eval(th1Interp, 0, arg, nArg);
  arg = Th_GetResult(th1Interp, &nArg);
  Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg));
  return getTclReturnCode(rc);
}

/*
** Tcl command: th1Expr arg
**
** Evaluates the TH1 expression and returns its result verbatim.  If a TH1
** script error is generated, it will be transformed into a Tcl script error.
*/
static int Th1ExprObjCmd(
  ClientData clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *const objv[]
){
  Th_Interp *th1Interp;
  int nArg;

  const char *arg;
  int rc;

  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "arg");
    return TCL_ERROR;
  }
  th1Interp = (Th_Interp *)clientData;
  if( !th1Interp ){
    Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
    return TCL_ERROR;
  }
  arg = Tcl_GetStringFromObj(objv[1], &nArg);
  rc = Th_Expr(th1Interp, arg, nArg);
  arg = Th_GetResult(th1Interp, &nArg);
  Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg));
  return getTclReturnCode(rc);
}

/*
** Array of Tcl integration commands.  Used when adding or removing the Tcl
** integration commands from TH1.
*/







>












|
>


|

















>












|
|

|







700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
  ClientData clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *const objv[]
){
  Th_Interp *th1Interp;
  int nArg;
  Tcl_Size szArg;
  const char *arg;
  int rc;

  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "arg");
    return TCL_ERROR;
  }
  th1Interp = (Th_Interp *)clientData;
  if( !th1Interp ){
    Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
    return TCL_ERROR;
  }
  arg = Tcl_GetStringFromObj(objv[1], &szArg);
  nArg = (int)szArg;
  rc = Th_Eval(th1Interp, 0, arg, nArg);
  arg = Th_GetResult(th1Interp, &nArg);
  Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg)));
  return getTclReturnCode(rc);
}

/*
** Tcl command: th1Expr arg
**
** Evaluates the TH1 expression and returns its result verbatim.  If a TH1
** script error is generated, it will be transformed into a Tcl script error.
*/
static int Th1ExprObjCmd(
  ClientData clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *const objv[]
){
  Th_Interp *th1Interp;
  int nArg;
  Tcl_Size szArg;
  const char *arg;
  int rc;

  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "arg");
    return TCL_ERROR;
  }
  th1Interp = (Th_Interp *)clientData;
  if( !th1Interp ){
    Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
    return TCL_ERROR;
  }
  arg = Tcl_GetStringFromObj(objv[1], &szArg);
  rc = Th_Expr(th1Interp, arg, (int)szArg);
  arg = Th_GetResult(th1Interp, &nArg);
  Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg)));
  return getTclReturnCode(rc);
}

/*
** Array of Tcl integration commands.  Used when adding or removing the Tcl
** integration commands from TH1.
*/
Changes to src/timeline.c.
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
  /* Finish preliminary processing of tag match queries. */
  matchStyle = match_style(zMatchStyle, MS_EXACT);
  if( zTagName ){
    zType = "ci";
    if( matchStyle==MS_EXACT ){
      /* For exact maching, inhibit links to the selected tag. */
      zThisTag = zTagName;
      Th_Store("current_checkin", zTagName);
    }

    /* Display a checkbox to enable/disable display of related check-ins. */
    if( advancedMenu ){
      style_submenu_checkbox("rel", "Related", 0, 0);
    }








|







1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
  /* Finish preliminary processing of tag match queries. */
  matchStyle = match_style(zMatchStyle, MS_EXACT);
  if( zTagName ){
    zType = "ci";
    if( matchStyle==MS_EXACT ){
      /* For exact maching, inhibit links to the selected tag. */
      zThisTag = zTagName;
      Th_StoreUnsafe("current_checkin", zTagName);
    }

    /* Display a checkbox to enable/disable display of related check-ins. */
    if( advancedMenu ){
      style_submenu_checkbox("rel", "Related", 0, 0);
    }

Changes to src/tkt.c.
186
187
188
189
190
191
192

193
194
195
196
197
198
199

200
201
202
203
204
205
206
207
208
209
210
211
212
213
214

215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
** Otherwise, db_reveal() is a no-op and the content remains
** obscured.
*/
static void initializeVariablesFromDb(void){
  const char *zName;
  Stmt q;
  int i, n, size, j;


  zName = PD("name","-none-");
  db_prepare(&q, "SELECT datetime(tkt_mtime,toLocal()) AS tkt_datetime, "
                 "datetime(tkt_ctime,toLocal()) AS tkt_datetime_creation, "
                 "julianday('now') - tkt_mtime, "
                 "julianday('now') - tkt_ctime, *"
                 "  FROM ticket WHERE tkt_uuid GLOB '%q*'",

                 zName);
  if( db_step(&q)==SQLITE_ROW ){
    n = db_column_count(&q);
    for(i=0; i<n; i++){
      const char *zVal = db_column_text(&q, i);
      const char *zName = db_column_name(&q, i);
      char *zRevealed = 0;
      if( zVal==0 ){
        zVal = "";
      }else if( strncmp(zName, "private_", 8)==0 ){
        zVal = zRevealed = db_reveal(zVal);
      }
      if( (j = fieldId(zName))>=0 ){
        aField[j].zValue = mprintf("%s", zVal);
      }else if( memcmp(zName, "tkt_", 4)==0 && Th_Fetch(zName, &size)==0 ){

        Th_Store(zName, zVal);
      }
      free(zRevealed);
    }
    Th_Store("tkt_mage", human_readable_age(db_column_double(&q, 2)));
    Th_Store("tkt_cage", human_readable_age(db_column_double(&q, 3)));
  }
  db_finalize(&q);
  for(i=0; i<nField; i++){
    if( Th_Fetch(aField[i].zName, &size)==0 ){
      Th_Store(aField[i].zName, aField[i].zValue);
    }
  }
}

/*
** Transfer all CGI parameters to variables in the interpreter.
*/
static void initializeVariablesFromCGI(void){
  int i;
  const char *z;

  for(i=0; (z = cgi_parameter_name(i))!=0; i++){
    Th_Store(z, P(z));
  }
}

/*
** Information about a single J-card
*/
struct jCardInfo {







>



|

|

>















>










|












|







186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
** Otherwise, db_reveal() is a no-op and the content remains
** obscured.
*/
static void initializeVariablesFromDb(void){
  const char *zName;
  Stmt q;
  int i, n, size, j;
  const char *zCTimeColumn = haveTicketCTime ? "tkt_ctime" : "tkt_mtime";

  zName = PD("name","-none-");
  db_prepare(&q, "SELECT datetime(tkt_mtime,toLocal()) AS tkt_datetime, "
                 "datetime(%s,toLocal()) AS tkt_datetime_creation, "
                 "julianday('now') - tkt_mtime, "
                 "julianday('now') - %s, *"
                 "  FROM ticket WHERE tkt_uuid GLOB '%q*'",
                 zCTimeColumn/*safe-for-%s*/, zCTimeColumn/*safe-for-%s*/,
                 zName);
  if( db_step(&q)==SQLITE_ROW ){
    n = db_column_count(&q);
    for(i=0; i<n; i++){
      const char *zVal = db_column_text(&q, i);
      const char *zName = db_column_name(&q, i);
      char *zRevealed = 0;
      if( zVal==0 ){
        zVal = "";
      }else if( strncmp(zName, "private_", 8)==0 ){
        zVal = zRevealed = db_reveal(zVal);
      }
      if( (j = fieldId(zName))>=0 ){
        aField[j].zValue = mprintf("%s", zVal);
      }else if( memcmp(zName, "tkt_", 4)==0 && Th_Fetch(zName, &size)==0 ){
        /* TICKET table columns that begin with "tkt_" are always safe */
        Th_Store(zName, zVal);
      }
      free(zRevealed);
    }
    Th_Store("tkt_mage", human_readable_age(db_column_double(&q, 2)));
    Th_Store("tkt_cage", human_readable_age(db_column_double(&q, 3)));
  }
  db_finalize(&q);
  for(i=0; i<nField; i++){
    if( Th_Fetch(aField[i].zName, &size)==0 ){
      Th_StoreUnsafe(aField[i].zName, aField[i].zValue);
    }
  }
}

/*
** Transfer all CGI parameters to variables in the interpreter.
*/
static void initializeVariablesFromCGI(void){
  int i;
  const char *z;

  for(i=0; (z = cgi_parameter_name(i))!=0; i++){
    Th_StoreUnsafe(z, P(z));
  }
}

/*
** Information about a single J-card
*/
struct jCardInfo {
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
  int idx;

  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "append_field FIELD STRING");
  }
  if( g.thTrace ){
    Th_Trace("append_field %#h {%#h}<br>\n",
              argl[1], argv[1], argl[2], argv[2]);
  }
  for(idx=0; idx<nField; idx++){
    if( memcmp(aField[idx].zName, argv[1], argl[1])==0
        && aField[idx].zName[argl[1]]==0 ){
      break;
    }
  }
  if( idx>=nField ){
    Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]);
    return TH_ERROR;
  }







|


|
|







819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
  int idx;

  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "append_field FIELD STRING");
  }
  if( g.thTrace ){
    Th_Trace("append_field %#h {%#h}<br>\n",
              TH1_LEN(argl[1]), argv[1], TH1_LEN(argl[2]), argv[2]);
  }
  for(idx=0; idx<nField; idx++){
    if( memcmp(aField[idx].zName, argv[1], TH1_LEN(argl[1]))==0
        && aField[idx].zName[TH1_LEN(argl[1])]==0 ){
      break;
    }
  }
  if( idx>=nField ){
    Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]);
    return TH_ERROR;
  }
936
937
938
939
940
941
942

943
944
945
946
947
948
949
  }
  for(i=0; i<nField; i++){
    const char *zValue;
    int nValue;
    if( aField[i].zAppend ) continue;
    zValue = Th_Fetch(aField[i].zName, &nValue);
    if( zValue ){

      while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; }
      if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0)
       || memcmp(zValue, aField[i].zValue, nValue)!=0
       ||(int)strlen(aField[i].zValue)!=nValue
      ){
        if( memcmp(aField[i].zName, "private_", 8)==0 ){
          zValue = db_conceal(zValue, nValue);







>







939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
  }
  for(i=0; i<nField; i++){
    const char *zValue;
    int nValue;
    if( aField[i].zAppend ) continue;
    zValue = Th_Fetch(aField[i].zName, &nValue);
    if( zValue ){
      nValue = TH1_LEN(nValue);
      while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; }
      if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0)
       || memcmp(zValue, aField[i].zValue, nValue)!=0
       ||(int)strlen(aField[i].zValue)!=nValue
      ){
        if( memcmp(aField[i].zName, "private_", 8)==0 ){
          zValue = db_conceal(zValue, nValue);
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
  if( g.zLogin && g.zLogin[0] ){
    uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.zLogin);
    if( uid ){
      char * zEmail =
        db_text(0, "SELECT find_emailaddr(info) FROM user WHERE uid=%d",
                uid);
      if( zEmail ){
        Th_Store("private_contact", zEmail);
        fossil_free(zEmail);
      }
    }
  }
  Th_Store("login", login_name());
  Th_Store("date", db_text(0, "SELECT datetime('now')"));
  Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd,
                   (void*)&zNewUuid, 0);
  if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br>\n", -1);
  if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zNewUuid ){
    if( P("submitandnew") ){
      cgi_redirect(mprintf("%R/tktnew/%s", zNewUuid));







|




|







1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
  if( g.zLogin && g.zLogin[0] ){
    uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.zLogin);
    if( uid ){
      char * zEmail =
        db_text(0, "SELECT find_emailaddr(info) FROM user WHERE uid=%d",
                uid);
      if( zEmail ){
        Th_StoreUnsafe("private_contact", zEmail);
        fossil_free(zEmail);
      }
    }
  }
  Th_StoreUnsafe("login", login_name());
  Th_Store("date", db_text(0, "SELECT datetime('now')"));
  Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd,
                   (void*)&zNewUuid, 0);
  if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br>\n", -1);
  if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zNewUuid ){
    if( P("submitandnew") ){
      cgi_redirect(mprintf("%R/tktnew/%s", zNewUuid));
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
  getAllTicketFields();
  initializeVariablesFromCGI();
  initializeVariablesFromDb();
  if( g.zPath[0]=='d' ) showAllFields();
  form_begin(0, "%R/%s", g.zPath);
  @ <input type="hidden" name="name" value="%s(zName)">
  zScript = ticket_editpage_code();
  Th_Store("login", login_name());
  Th_Store("date", db_text(0, "SELECT datetime('now')"));
  Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0);
  Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0);
  if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br>\n", -1);
  if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){
    cgi_redirect(mprintf("%R/tktview/%s", zName));
    return;







|







1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
  getAllTicketFields();
  initializeVariablesFromCGI();
  initializeVariablesFromDb();
  if( g.zPath[0]=='d' ) showAllFields();
  form_begin(0, "%R/%s", g.zPath);
  @ <input type="hidden" name="name" value="%s(zName)">
  zScript = ticket_editpage_code();
  Th_StoreUnsafe("login", login_name());
  Th_Store("date", db_text(0, "SELECT datetime('now')"));
  Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0);
  Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0);
  if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br>\n", -1);
  if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){
    cgi_redirect(mprintf("%R/tktview/%s", zName));
    return;
Changes to src/tktsetup.c.
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
@ <table cellpadding="5">
@ <tr><td class="tktDspLabel">Ticket&nbsp;Hash:</td>
@ <th1>
@ if {[info exists tkt_uuid]} {
@   html "<td class='tktDspValue' colspan='3'>"
@   copybtn hash-tk 0 $tkt_uuid 2
@   if {[hascap s]} {
@     html " ($tkt_id)"
@   }
@   html "</td></tr>\n"
@ } else {
@   if {[hascap s]} {
@     html "<td class='tktDspValue' colspan='3'>Deleted "
@     html "(0)</td></tr>\n"
@   } else {







|







479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
@ <table cellpadding="5">
@ <tr><td class="tktDspLabel">Ticket&nbsp;Hash:</td>
@ <th1>
@ if {[info exists tkt_uuid]} {
@   html "<td class='tktDspValue' colspan='3'>"
@   copybtn hash-tk 0 $tkt_uuid 2
@   if {[hascap s]} {
@     puts " ($tkt_id)"
@   }
@   html "</td></tr>\n"
@ } else {
@   if {[hascap s]} {
@     html "<td class='tktDspValue' colspan='3'>Deleted "
@     html "(0)</td></tr>\n"
@   } else {
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
@ </td>
@ <td class="tktDspLabel">Resolution:</td><td class="tktDspValue">
@ $<resolution>
@ </td></tr>
@ <tr><td class="tktDspLabel">Last&nbsp;Modified:</td><td class="tktDspValue">
@ <th1>
@ if {[info exists tkt_datetime]} {
@   html $tkt_datetime
@ }
@ if {[info exists tkt_mage]} {
@   html "<br>$tkt_mage"
@ }
@ </th1>
@ </td>
@ <td class="tktDspLabel">Created:</td><td class="tktDspValue">
@ <th1>
@ if {[info exists tkt_datetime_creation]} {
@   html $tkt_datetime_creation
@ }
@ if {[info exists tkt_cage]} {
@   html "<br>$tkt_cage"
@ }
@ </th1>
@ </td></tr>
@ <th1>enable_output [hascap e]</th1>
@   <tr>
@   <td class="tktDspLabel">Contact:</td><td class="tktDspValue" colspan="3">
@   $<private_contact>
@   </td>
@   </tr>
@ <th1>enable_output 1</th1>
@ <tr><td class="tktDspLabel">Version&nbsp;Found&nbsp;In:</td>
@ <td colspan="3" valign="top" class="tktDspValue">
@ <th1>
@ set versionlink ""
@ set urlfoundin [httpize $foundin]
@ set tagpattern {^[-0-9A-Za-z_\\.]+$}
@ if [regexp $tagpattern $foundin] {
@   query {SELECT count(*) AS match FROM tag
@          WHERE tagname=concat('sym-',$foundin)} {
@     if {$match} {set versionlink "/timeline?t=$urlfoundin"}
@   }
@ }
@ set hashpattern {^[0-9a-f]+$}
@ if [regexp $hashpattern $foundin] {
@   set pattern $foundin*
@   query {SELECT count(*) AS match FROM blob WHERE uuid GLOB $pattern} {
@     if {$match} {set versionlink "/info/$urlfoundin"}
@   }
@ }
@ if {$versionlink eq ""} {
@   puts $foundin
@ } else {
@   html "<a href=\""
@   puts $versionlink







|


|






|


|



















|






|







520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
@ </td>
@ <td class="tktDspLabel">Resolution:</td><td class="tktDspValue">
@ $<resolution>
@ </td></tr>
@ <tr><td class="tktDspLabel">Last&nbsp;Modified:</td><td class="tktDspValue">
@ <th1>
@ if {[info exists tkt_datetime]} {
@   puts $tkt_datetime
@ }
@ if {[info exists tkt_mage]} {
@   html "<br>[htmlize $tkt_mage] ago"
@ }
@ </th1>
@ </td>
@ <td class="tktDspLabel">Created:</td><td class="tktDspValue">
@ <th1>
@ if {[info exists tkt_datetime_creation]} {
@   puts $tkt_datetime_creation
@ }
@ if {[info exists tkt_cage]} {
@   html "<br>[htmlize $tkt_cage] ago"
@ }
@ </th1>
@ </td></tr>
@ <th1>enable_output [hascap e]</th1>
@   <tr>
@   <td class="tktDspLabel">Contact:</td><td class="tktDspValue" colspan="3">
@   $<private_contact>
@   </td>
@   </tr>
@ <th1>enable_output 1</th1>
@ <tr><td class="tktDspLabel">Version&nbsp;Found&nbsp;In:</td>
@ <td colspan="3" valign="top" class="tktDspValue">
@ <th1>
@ set versionlink ""
@ set urlfoundin [httpize $foundin]
@ set tagpattern {^[-0-9A-Za-z_\\.]+$}
@ if [regexp $tagpattern $foundin] {
@   query {SELECT count(*) AS match FROM tag
@          WHERE tagname=concat('sym-',$foundin)} {
@     if {$match} {set versionlink "timeline?t=$urlfoundin"}
@   }
@ }
@ set hashpattern {^[0-9a-f]+$}
@ if [regexp $hashpattern $foundin] {
@   set pattern $foundin*
@   query {SELECT count(*) AS match FROM blob WHERE uuid GLOB $pattern} {
@     if {$match} {set versionlink "info/$urlfoundin"}
@   }
@ }
@ if {$versionlink eq ""} {
@   puts $foundin
@ } else {
@   html "<a href=\""
@   puts $versionlink
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
@   } else {
@     html "<tr><td class='tktDspLabel' style='text-align:left'>\n"
@     html "User Comments:</td></tr>\n"
@     html "<tr><td colspan='5' class='tktDspValue'>\n"
@     set seenRow 1
@   }
@   html "<span class='tktDspCommenter'>"
@   html "[htmlize $xlogin]"
@   if {$xlogin ne $xusername && [string length $xusername]>0} {
@     html " (claiming to be [htmlize $xusername])"
@   }
@   html " added on $xdate:"
@   html "</span>\n"
@   if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
@     set r [randhex]
@     if {$xmimetype ne "text/plain"} {html "([htmlize $xmimetype])\n"}
@     wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
@   } elseif {$xmimetype eq "text/x-fossil-wiki"} {
@     wiki "<p>\n[string trimright $xcomment]\n</p>\n"
@   } elseif {$xmimetype eq "text/x-markdown"} {
@     html [lindex [markdown $xcomment] 1]
@   } elseif {$xmimetype eq "text/html"} {
@     wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki>\n"







|

|

|



|







612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
@   } else {
@     html "<tr><td class='tktDspLabel' style='text-align:left'>\n"
@     html "User Comments:</td></tr>\n"
@     html "<tr><td colspan='5' class='tktDspValue'>\n"
@     set seenRow 1
@   }
@   html "<span class='tktDspCommenter'>"
@   puts $xlogin
@   if {$xlogin ne $xusername && [string length $xusername]>0} {
@     puts " (claiming to be $xusername)"
@   }
@   puts " added on $xdate:"
@   html "</span>\n"
@   if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
@     set r [randhex]
@     if {$xmimetype ne "text/plain"} {puts "($xmimetype)\n"}
@     wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
@   } elseif {$xmimetype eq "text/x-fossil-wiki"} {
@     wiki "<p>\n[string trimright $xcomment]\n</p>\n"
@   } elseif {$xmimetype eq "text/x-markdown"} {
@     html [lindex [markdown $xcomment] 1]
@   } elseif {$xmimetype eq "text/html"} {
@     wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki>\n"
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
@     html "<tr><td colspan='2'><hr></td></tr>\n"
@     html "<tr><td colspan='2' class='tktDspLabel' style='text-align:left'>\n"
@     html "Previous User Comments:</td></tr>\n"
@     html "<tr><td colspan='2' class='tktDspValue'>\n"
@     set seenRow 1
@   }
@   html "<span class='tktDspCommenter'>"
@   html "[htmlize $xlogin]"
@   if {$xlogin ne $xusername && [string length $xusername]>0} {
@     html " (claiming to be [htmlize $xusername])"
@   }
@   html " added on $xdate:"
@   html "</span>\n"
@   if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
@     set r [randhex]
@     if {$xmimetype ne "text/plain"} {html "([htmlize $xmimetype])\n"}
@     wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
@   } elseif {$xmimetype eq "text/x-fossil-wiki"} {
@     wiki "<p>\n[string trimright $xcomment]\n</p>\n"
@   } elseif {$xmimetype eq "text/x-markdown"} {
@     html [lindex [markdown $xcomment] 1]
@   } elseif {$xmimetype eq "text/html"} {
@     wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki>\n"







|

|

|



|







799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
@     html "<tr><td colspan='2'><hr></td></tr>\n"
@     html "<tr><td colspan='2' class='tktDspLabel' style='text-align:left'>\n"
@     html "Previous User Comments:</td></tr>\n"
@     html "<tr><td colspan='2' class='tktDspValue'>\n"
@     set seenRow 1
@   }
@   html "<span class='tktDspCommenter'>"
@   puts $xlogin
@   if {$xlogin ne $xusername && [string length $xusername]>0} {
@     puts " (claiming to be $xusername)"
@   }
@   puts " added on $xdate:"
@   html "</span>\n"
@   if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
@     set r [randhex]
@     if {$xmimetype ne "text/plain"} {puts "($xmimetype)\n"}
@     wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
@   } elseif {$xmimetype eq "text/x-fossil-wiki"} {
@     wiki "<p>\n[string trimright $xcomment]\n</p>\n"
@   } elseif {$xmimetype eq "text/x-markdown"} {
@     html [lindex [markdown $xcomment] 1]
@   } elseif {$xmimetype eq "text/html"} {
@     wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki>\n"
Changes to src/user.c.
324
325
326
327
328
329
330
331
332
333
334







335
336
337
338
339
340
341
**
**        Query or set the capabilities for user USERNAME
**
** > fossil user contact USERNAME ?CONTACT-INFO?
**
**        Query or set contact information for user USERNAME
**
** > fossil user default ?USERNAME?
**
**        Query or set the default user.  The default user is the
**        user for command-line interaction.







**
** > fossil user list | ls
**
**        List all users known to the repository
**
** > fossil user new ?USERNAME? ?CONTACT-INFO? ?PASSWORD?
**







|


|
>
>
>
>
>
>
>







324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
**
**        Query or set the capabilities for user USERNAME
**
** > fossil user contact USERNAME ?CONTACT-INFO?
**
**        Query or set contact information for user USERNAME
**
** > fossil user default ?OPTIONS? ?USERNAME?
**
**        Query or set the default user.  The default user is the
**        user for command-line interaction.  If USERNAME is an
**        empty string, then the default user is unset from the
**        repository and will subsequently be determined by the -U
**        command-line option or by environment variables
**        FOSSIL_USER, USER, LOGNAME, or USERNAME, in that order.
**        OPTIONS:
**
**            -v|--verbose        Show how the default user is computed
**
** > fossil user list | ls
**
**        List all users known to the repository
**
** > fossil user new ?USERNAME? ?CONTACT-INFO? ?PASSWORD?
**
383
384
385
386
387
388
389


390
391
392






393
394
395
396
397
398
399
400





















401
402
403
404
405
406
407
      "INSERT INTO user(login,pw,cap,info,mtime)"
      "VALUES(%B,%Q,%B,%B,now())",
      &login, zPw, &caps, &contact
    );
    db_protect_pop();
    free(zPw);
  }else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){


    if( g.argc==3 ){
      user_select();
      fossil_print("%s\n", g.zLogin);






    }else{
      if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.argv[3]) ){
        fossil_fatal("no such user: %s", g.argv[3]);
      }
      if( g.localOpen ){
        db_lset("default-user", g.argv[3]);
      }else{
        db_set("default-user", g.argv[3], 0);





















      }
    }
  }else if(( n>=2 && strncmp(g.argv[2],"list",n)==0 ) ||
           ( n>=2 && strncmp(g.argv[2],"ls",n)==0 )){
    Stmt q;
    db_prepare(&q, "SELECT login, info FROM user ORDER BY login");
    while( db_step(&q)==SQLITE_ROW ){







>
>
|
|
|
>
>
>
>
>
>
|
|
|
|
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
      "INSERT INTO user(login,pw,cap,info,mtime)"
      "VALUES(%B,%Q,%B,%B,now())",
      &login, zPw, &caps, &contact
    );
    db_protect_pop();
    free(zPw);
  }else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){
    int eVerbose = find_option("verbose","v",0)!=0;
    verify_all_options();
    if( g.argc>3 ){
      const char *zUser = g.argv[3];
      if( fossil_strcmp(zUser,"")==0 || fossil_stricmp(zUser,"nobody")==0 ){
        db_begin_transaction();
        if( g.localOpen ){
          db_multi_exec("DELETE FROM vvar WHERE name='default-user'");
        }
        db_unset("default-user",0);
        db_commit_transaction();
      }else{
        if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.argv[3]) ){
          fossil_fatal("no such user: %s", g.argv[3]);
        }
        if( g.localOpen ){
          db_lset("default-user", g.argv[3]);
        }else{
          db_set("default-user", g.argv[3], 0);
        }
      }
    }
    if( g.argc==3 || eVerbose ){
      int eHow = user_select();
      const char *zHow = "???";
      switch( eHow ){
        case 1:  zHow = "-U option"; break;
        case 2:  zHow = "previously set"; break;
        case 3:  zHow = "local check-out"; break;
        case 4:  zHow = "repository"; break;
        case 5:  zHow = "FOSSIL_USER"; break;
        case 6:  zHow = "USER"; break;
        case 7:  zHow = "LOGNAME"; break;
        case 8:  zHow = "USERNAME"; break;
        case 9:  zHow = "URL"; break;
      }
      if( eVerbose ){
        fossil_print("%s (determined by %s)\n", g.zLogin, zHow);
      }else{
        fossil_print("%s\n", g.zLogin);
      }
    }
  }else if(( n>=2 && strncmp(g.argv[2],"list",n)==0 ) ||
           ( n>=2 && strncmp(g.argv[2],"ls",n)==0 )){
    Stmt q;
    db_prepare(&q, "SELECT login, info FROM user ORDER BY login");
    while( db_step(&q)==SQLITE_ROW ){
494
495
496
497
498
499
500


501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
}

/*
** Figure out what user is at the controls.
**
**   (1)  Use the --user and -U command-line options.
**


**   (2)  If the local database is open, check in VVAR.
**
**   (3)  Check the default user in the repository
**
**   (4)  Try the FOSSIL_USER environment variable.
**
**   (5)  Try the USER environment variable.
**
**   (6)  Try the LOGNAME environment variable.
**
**   (7)  Try the USERNAME environment variable.
**
**   (8)  Check if the user can be extracted from the remote URL.
**
** The user name is stored in g.zLogin.  The uid is in g.userUid.
*/
void user_select(void){
  UrlData url;
  if( g.userUid ) return;
  if( g.zLogin ){
    if( attempt_user(g.zLogin)==0 ){
      fossil_fatal("no such user: %s", g.zLogin);
    }else{
      return;
    }
  }

  if( g.localOpen && attempt_user(db_lget("default-user",0)) ) return;

  if( attempt_user(db_get("default-user", 0)) ) return;

  if( attempt_user(fossil_getenv("FOSSIL_USER")) ) return;

  if( attempt_user(fossil_getenv("USER")) ) return;

  if( attempt_user(fossil_getenv("LOGNAME")) ) return;

  if( attempt_user(fossil_getenv("USERNAME")) ) return;

  memset(&url, 0, sizeof(url));
  url_parse_local(0, URL_USE_CONFIG, &url);
  if( url.user && attempt_user(url.user) ) return;

  fossil_print(
    "Cannot figure out who you are!  Consider using the --user\n"
    "command line option, setting your USER environment variable,\n"
    "or setting a default user with \"fossil user default USER\".\n"
  );
  fossil_fatal("cannot determine user");







>
>
|

|

|

|

|

|

|



|

|




|



|

|

|

|

|

|



|







530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
}

/*
** Figure out what user is at the controls.
**
**   (1)  Use the --user and -U command-line options.
**
**   (2)  The name used for login (if there was a login).
**
**   (3)  If the local database is open, check in VVAR.
**
**   (4)  Check the default-user in the repository
**
**   (5)  Try the FOSSIL_USER environment variable.
**
**   (6)  Try the USER environment variable.
**
**   (7)  Try the LOGNAME environment variable.
**
**   (8)  Try the USERNAME environment variable.
**
**   (9)  Check if the user can be extracted from the remote URL.
**
** The user name is stored in g.zLogin.  The uid is in g.userUid.
*/
int user_select(void){
  UrlData url;
  if( g.userUid ) return 1;
  if( g.zLogin ){
    if( attempt_user(g.zLogin)==0 ){
      fossil_fatal("no such user: %s", g.zLogin);
    }else{
      return 2;
    }
  }

  if( g.localOpen && attempt_user(db_lget("default-user",0)) ) return 3;

  if( attempt_user(db_get("default-user", 0)) ) return 4;

  if( attempt_user(fossil_getenv("FOSSIL_USER")) ) return 5;

  if( attempt_user(fossil_getenv("USER")) ) return 6;

  if( attempt_user(fossil_getenv("LOGNAME")) ) return 7;

  if( attempt_user(fossil_getenv("USERNAME")) ) return 8;

  memset(&url, 0, sizeof(url));
  url_parse_local(0, URL_USE_CONFIG, &url);
  if( url.user && attempt_user(url.user) ) return 9;

  fossil_print(
    "Cannot figure out who you are!  Consider using the --user\n"
    "command line option, setting your USER environment variable,\n"
    "or setting a default user with \"fossil user default USER\".\n"
  );
  fossil_fatal("cannot determine user");
Changes to src/xfer.c.
1114
1115
1116
1117
1118
1119
1120












1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
    int sz = db_column_int(&uvq,3);
    if( zHash==0 ){ sz = 0; zHash = "-"; }
    blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n",
                 zName, mtime, zHash, sz);
  }
  db_finalize(&uvq);
}













/*
** Called when there is an attempt to transfer private content to and
** from a server without authorization.
*/
static void server_private_xfer_not_authorized(void){
  @ error not\sauthorized\sto\ssync\sprivate\scontent
}

/*
** Return the common TH1 code to evaluate prior to evaluating any other
** TH1 transfer notification scripts.
*/
const char *xfer_common_code(void){







>
>
>
>
>
>
>
>
>
>
>
>






|







1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
    int sz = db_column_int(&uvq,3);
    if( zHash==0 ){ sz = 0; zHash = "-"; }
    blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n",
                 zName, mtime, zHash, sz);
  }
  db_finalize(&uvq);
}

/*
** Return a string that contains supplemental information about a
** "not authorized" error.  The string might be empty if no additional
** information is available.
*/
static char *whyNotAuth(void){
  if( g.useLocalauth && db_get_boolean("localauth",0)!=0 ){
    return "\\sbecause\\sthe\\s'localauth'\\ssetting\\sis\\senabled";
  }
  return "";
}

/*
** Called when there is an attempt to transfer private content to and
** from a server without authorization.
*/
static void server_private_xfer_not_authorized(void){
  @ error not\sauthorized\sto\ssync\sprivate\scontent%s(whyNotAuth())
}

/*
** Return the common TH1 code to evaluate prior to evaluating any other
** TH1 transfer notification scripts.
*/
const char *xfer_common_code(void){
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
    **   file HASH DELTASRC SIZE \n CONTENT
    **
    ** Server accepts a file from the client.
    */
    if( blob_eq(&xfer.aToken[0], "file") ){
      if( !isPush ){
        cgi_reset_content();
        @ error not\sauthorized\sto\swrite
        nErr++;
        break;
      }
      xfer_accept_file(&xfer, 0, pzUuidList, pnUuidList);
      if( blob_size(&xfer.err) ){
        cgi_reset_content();
        @ error %T(blob_str(&xfer.err))
        nErr++;
        break;
      }
    }else

    /*   cfile HASH USIZE CSIZE \n CONTENT
    **   cfile HASH DELTASRC USIZE CSIZE \n CONTENT
    **
    ** Server accepts a compressed file from the client.
    */
    if( blob_eq(&xfer.aToken[0], "cfile") ){
      if( !isPush ){
        cgi_reset_content();
        @ error not\sauthorized\sto\swrite
        nErr++;
        break;
      }
      xfer_accept_compressed_file(&xfer, pzUuidList, pnUuidList);
      if( blob_size(&xfer.err) ){
        cgi_reset_content();
        @ error %T(blob_str(&xfer.err))







|




















|







1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
    **   file HASH DELTASRC SIZE \n CONTENT
    **
    ** Server accepts a file from the client.
    */
    if( blob_eq(&xfer.aToken[0], "file") ){
      if( !isPush ){
        cgi_reset_content();
        @ error not\sauthorized\sto\swrite%s(whyNotAuth())
        nErr++;
        break;
      }
      xfer_accept_file(&xfer, 0, pzUuidList, pnUuidList);
      if( blob_size(&xfer.err) ){
        cgi_reset_content();
        @ error %T(blob_str(&xfer.err))
        nErr++;
        break;
      }
    }else

    /*   cfile HASH USIZE CSIZE \n CONTENT
    **   cfile HASH DELTASRC USIZE CSIZE \n CONTENT
    **
    ** Server accepts a compressed file from the client.
    */
    if( blob_eq(&xfer.aToken[0], "cfile") ){
      if( !isPush ){
        cgi_reset_content();
        @ error not\sauthorized\sto\swrite%s(whyNotAuth())
        nErr++;
        break;
      }
      xfer_accept_compressed_file(&xfer, pzUuidList, pnUuidList);
      if( blob_size(&xfer.err) ){
        cgi_reset_content();
        @ error %T(blob_str(&xfer.err))
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
        nErr++;
        break;
      }
      login_check_credentials();
      if( blob_eq(&xfer.aToken[0], "pull") ){
        if( !g.perm.Read ){
          cgi_reset_content();
          @ error not\sauthorized\sto\sread
          nErr++;
          break;
        }
        isPull = 1;
      }else{
        if( !g.perm.Write ){
          if( !isPull ){
            cgi_reset_content();
            @ error not\sauthorized\sto\swrite
            nErr++;
          }else{
            @ message pull\sonly\s-\snot\sauthorized\sto\spush
          }
        }else{
          isPush = 1;
        }
      }
    }else

    /*    clone   ?PROTOCOL-VERSION?  ?SEQUENCE-NUMBER?
    **
    ** The client knows nothing.  Tell all.
    */
    if( blob_eq(&xfer.aToken[0], "clone") ){
      int iVers;
      login_check_credentials();
      if( !g.perm.Clone ){
        cgi_reset_content();
        @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
        @ error not\sauthorized\sto\sclone
        nErr++;
        break;
      }
      if( db_get_boolean("uv-sync",0) && !uvCatalogSent ){
        @ pragma uv-pull-only
        send_unversioned_catalog(&xfer);
        uvCatalogSent = 1;







|








|


|

















|







1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
        nErr++;
        break;
      }
      login_check_credentials();
      if( blob_eq(&xfer.aToken[0], "pull") ){
        if( !g.perm.Read ){
          cgi_reset_content();
          @ error not\sauthorized\sto\sread%s(whyNotAuth())
          nErr++;
          break;
        }
        isPull = 1;
      }else{
        if( !g.perm.Write ){
          if( !isPull ){
            cgi_reset_content();
            @ error not\sauthorized\sto\swrite%s(whyNotAuth())
            nErr++;
          }else{
            @ message pull\sonly\s-\snot\sauthorized\sto\spush%s(whyNotAuth())
          }
        }else{
          isPush = 1;
        }
      }
    }else

    /*    clone   ?PROTOCOL-VERSION?  ?SEQUENCE-NUMBER?
    **
    ** The client knows nothing.  Tell all.
    */
    if( blob_eq(&xfer.aToken[0], "clone") ){
      int iVers;
      login_check_credentials();
      if( !g.perm.Clone ){
        cgi_reset_content();
        @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
        @ error not\sauthorized\sto\sclone%s(whyNotAuth())
        nErr++;
        break;
      }
      if( db_get_boolean("uv-sync",0) && !uvCatalogSent ){
        @ pragma uv-pull-only
        send_unversioned_catalog(&xfer);
        uvCatalogSent = 1;
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
        xfer_fatal_error("invalid config record");
        return;
      }
      blob_zero(&content);
      blob_extract(xfer.pIn, size, &content);
      if( !g.perm.Admin ){
        cgi_reset_content();
        @ error not\sauthorized\sto\spush\sconfiguration
        nErr++;
        break;
      }
      configure_receive(zName, &content, CONFIGSET_ALL);
      blob_reset(&content);
      blob_seek(xfer.pIn, 1, BLOB_SEEK_CUR);
    }else







|







1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
        xfer_fatal_error("invalid config record");
        return;
      }
      blob_zero(&content);
      blob_extract(xfer.pIn, size, &content);
      if( !g.perm.Admin ){
        cgi_reset_content();
        @ error not\sauthorized\sto\spush\sconfiguration%s(whyNotAuth())
        nErr++;
        break;
      }
      configure_receive(zName, &content, CONFIGSET_ALL);
      blob_reset(&content);
      blob_seek(xfer.pIn, 1, BLOB_SEEK_CUR);
    }else
Changes to test/tester.tcl.
354
355
356
357
358
359
360

361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377




378
379

380

381
382
383
384
385
386
387
      max-upload \
      mimetypes \
      mtime-changes \
      mv-rm-files \
      pgp-command \
      preferred-diff-type \
      proxy \

      redirect-to-https \
      relative-paths \
      repo-cksum \
      repolist-skin \
      robot-restrict \
      robots-txt \
      safe-html \
      self-pw-reset \
      self-register \
      sitemap-extra \
      ssh-command \
      ssl-ca-location \
      ssl-identity \
      tclsh \
      th1-setup \
      th1-uri-regexp \
      ticket-default-report \




      timeline-utc \
      user-color-map \

      uv-sync \

      web-browser]

  fossil test-th-eval "hasfeature legacyMvRm"

  if {[normalize_result] eq "1"} {
    lappend result mv-rm-files
  }







>

















>
>
>
>


>

>







354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
      max-upload \
      mimetypes \
      mtime-changes \
      mv-rm-files \
      pgp-command \
      preferred-diff-type \
      proxy \
      raw-bgcolor \
      redirect-to-https \
      relative-paths \
      repo-cksum \
      repolist-skin \
      robot-restrict \
      robots-txt \
      safe-html \
      self-pw-reset \
      self-register \
      sitemap-extra \
      ssh-command \
      ssl-ca-location \
      ssl-identity \
      tclsh \
      th1-setup \
      th1-uri-regexp \
      ticket-default-report \
      timeline-hard-newlines \
      timeline-plaintext \
      timeline-truncate-at-blank \
      timeline-tslink-info \
      timeline-utc \
      user-color-map \
      verify-comments \
      uv-sync \
      vuln-report \
      web-browser]

  fossil test-th-eval "hasfeature legacyMvRm"

  if {[normalize_result] eq "1"} {
    lappend result mv-rm-files
  }
Added test/th1-taint.test.








































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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
74
75
76
77
78
79
80
81
82
83
84
#
# Copyright (c) 2025 D. Richard Hipp
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the Simplified BSD License (also
# known as the "2-Clause License" or "FreeBSD License".)
#
# This program is distributed in the hope that it will be useful,
# but without any warranty; without even the implied warranty of
# merchantability or fitness for a particular purpose.
#
# Author contact information:
#   drh@hwaci.com
#   http://www.hwaci.com/drh/
#
############################################################################
#
# TH1 Commands
#

set path [file dirname [info script]]; test_setup

proc taint-test {testnum th1script expected} {
  global fossilexe
  set rc [catch {exec $fossilexe test-th-eval $th1script} got]
  if {$rc} {
    test th1-taint-$testnum 0
    puts $got
    return
  }
  if {$got ne $expected} {
    test th1-taint-$testnum 0
    puts " Expected: $expected"
    puts " Got:      $got"
  } else {
    test th1-taint-$testnum 1
  }
}

taint-test 10 {string is tainted abcd} 0
taint-test 20 {string is tainted [taint abcd]} 1
taint-test 30 {string is tainted [untaint [taint abcd]]} 0
taint-test 40 {string is tainted [untaint abcde]} 0
taint-test 50 {string is tainted "abc[taint def]ghi"} 1
taint-test 60 {set t1 [taint abc]; string is tainted "123 $t1 456"} 1

taint-test 100 {set t1 [taint abc]; lappend t1 def; string is tainted $t1} 1
taint-test 110 {set t1 abc; lappend t1 [taint def]; string is tainted $t1} 1

taint-test 200 {string is tainted [list abc def ghi]} 0
taint-test 210 {string is tainted [list [taint abc] def ghi]} 1
taint-test 220 {string is tainted [list abc [taint def] ghi]} 1
taint-test 230 {string is tainted [list abc def [taint ghi]]} 1

taint-test 300 {
  set res {}
  foreach x [list abc [taint def] ghi] {
    lappend res [string is tainted $x]
  }
  set res
} {1 1 1}
taint-test 310 {
  set res {}
  foreach {x y} [list abc [taint def] ghi jkl] {
    lappend res [string is tainted $x] [string is tainted $y]
  }
  set res
} {1 1 1 1}

taint-test 400 {string is tainted [lindex "abc [taint def] ghi" 0]} 1
taint-test 410 {string is tainted [lindex "abc [taint def] ghi" 1]} 1
taint-test 420 {string is tainted [lindex "abc [taint def] ghi" 2]} 1
taint-test 430 {string is tainted [lindex "abc [taint def] ghi" 3]} 0

taint-test 500 {string is tainted [string index [taint abcdefg] 3]} 1
  
taint-test 600 {string is tainted [string range [taint abcdefg] 3 5]} 1

taint-test 700 {string is tainted [string trim [taint "  abcdefg  "]]} 1
taint-test 710 {string is tainted [string trimright [taint "  abcdefg  "]]} 1
taint-test 720 {string is tainted [string trimleft [taint "  abcdefg  "]]} 1
  

test_cleanup
Changes to test/th1.test.
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
test th1-defHeader-2 {[string match *<body> [normalize_result]] || \
    [string match "*<body class=\"\$current_feature\
                    rpage-\$requested_page\
                    cpage-\$canonical_page\">" [normalize_result]]}

###############################################################################

fossil test-th-eval "styleHeader {Page Title Here}"
test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

test_in_checkout th1-header-2 {
  fossil test-th-eval --open-config "styleHeader {Page Title Here}"
} {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]}

###############################################################################

fossil test-th-eval "styleFooter"
test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

fossil test-th-eval --open-config "styleFooter"
test th1-footer-2 {$RESULT eq {}}

###############################################################################







|
|









|
|







793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
test th1-defHeader-2 {[string match *<body> [normalize_result]] || \
    [string match "*<body class=\"\$current_feature\
                    rpage-\$requested_page\
                    cpage-\$canonical_page\">" [normalize_result]]}

###############################################################################

#fossil test-th-eval "styleHeader {Page Title Here}"
#test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

test_in_checkout th1-header-2 {
  fossil test-th-eval --open-config "styleHeader {Page Title Here}"
} {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]}

###############################################################################

#fossil test-th-eval "styleFooter"
#test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

fossil test-th-eval --open-config "styleFooter"
test th1-footer-2 {$RESULT eq {}}

###############################################################################
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924

fossil test-th-eval "artifact"
test th1-artifact-1 {$RESULT eq \
    {TH_ERROR: wrong # args: should be "artifact ID ?FILENAME?"}}

###############################################################################

fossil test-th-eval "artifact tip"
test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

test_in_checkout th1-artifact-3 {
  fossil test-th-eval --open-config "artifact tip"
} {[regexp -- {F test/th1\.test [0-9a-f]{40,64}} $RESULT]}

###############################################################################

fossil test-th-eval "artifact 0000000000"
test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

fossil test-th-eval --open-config "artifact 0000000000"
test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}}

###############################################################################

fossil test-th-eval "artifact tip test/th1.test"
test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

test_in_checkout th1-artifact-7 {
  fossil test-th-eval --open-config "artifact tip test/th1.test"
} {[regexp -- {th1-artifact-7} $RESULT]}

###############################################################################

fossil test-th-eval "artifact 0000000000 test/th1.test"
test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

fossil test-th-eval --open-config "artifact 0000000000 test/th1.test"
test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}}

###############################################################################







|
|









|
|








|
|









|
|







877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924

fossil test-th-eval "artifact"
test th1-artifact-1 {$RESULT eq \
    {TH_ERROR: wrong # args: should be "artifact ID ?FILENAME?"}}

###############################################################################

#fossil test-th-eval "artifact tip"
#test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

test_in_checkout th1-artifact-3 {
  fossil test-th-eval --open-config "artifact tip"
} {[regexp -- {F test/th1\.test [0-9a-f]{40,64}} $RESULT]}

###############################################################################

#fossil test-th-eval "artifact 0000000000"
#test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

fossil test-th-eval --open-config "artifact 0000000000"
test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}}

###############################################################################

#fossil test-th-eval "artifact tip test/th1.test"
#test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

test_in_checkout th1-artifact-7 {
  fossil test-th-eval --open-config "artifact tip test/th1.test"
} {[regexp -- {th1-artifact-7} $RESULT]}

###############################################################################

#fossil test-th-eval "artifact 0000000000 test/th1.test"
#test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

fossil test-th-eval --open-config "artifact 0000000000 test/th1.test"
test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}}

###############################################################################
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
    test th1-globalState-2 {$RESULT eq \
        [fossil test-th-eval --open-config checkout]}
  }
}

###############################################################################

fossil test-th-eval "globalState configuration"
test th1-globalState-3 {[string length $RESULT] == 0}

###############################################################################

fossil test-th-eval --open-config "globalState configuration"
test th1-globalState-4 {[string length $RESULT] > 0}

###############################################################################







|
|







945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
    test th1-globalState-2 {$RESULT eq \
        [fossil test-th-eval --open-config checkout]}
  }
}

###############################################################################

#fossil test-th-eval "globalState configuration"
#test th1-globalState-3 {[string length $RESULT] == 0}

###############################################################################

fossil test-th-eval --open-config "globalState configuration"
test th1-globalState-4 {[string length $RESULT] > 0}

###############################################################################
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079

1080
1081
1082
1083
1084
1085
1086
###############################################################################

fossil test-th-eval "globalState flags"
test th1-globalState-16 {$RESULT eq "0"}

###############################################################################

fossil test-th-eval "reinitialize; globalState configuration"
test th1-reinitialize-1 {$RESULT eq ""}

###############################################################################

fossil test-th-eval "reinitialize 1; globalState configuration"
test th1-reinitialize-2 {$RESULT ne ""}

###############################################################################

#
# NOTE: This test will fail if the command names are added to TH1, or
#       moved from Tcl builds to plain or the reverse. Sorting the
#       command lists eliminates a dependence on order.
#
fossil test-th-eval "info commands"
set sorted_result [lsort $RESULT]
protOut "Sorted: $sorted_result"
set base_commands {anoncap anycap array artifact break breakpoint \
      builtin_request_js capexpr captureTh1 catch cgiHeaderLine checkout \
      combobox continue copybtn date decorate defHeader dir enable_htmlify \
      enable_output encode64 error expr for foreach getParameter glob_match \
      globalState hascap hasfeature html htmlize http httpize if info \
      insertCsrf lappend lindex linecount list llength lsearch markdown nonce \
      proc puts query randhex redirect regexp reinitialize rename render \
      repository return searchable set setParameter setting stime string \
      styleFooter styleHeader styleScript submenu tclReady trace unset \
      unversioned uplevel upvar utime verifyCsrf verifyLogin wiki}
set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe}
if {$th1Tcl} {
  test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]}
} else {
  test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]}
}


###############################################################################

fossil test-th-eval "info vars"

if {$th1Hooks} {
  test th1-info-vars-1 {[lsort $RESULT] eq \







|
|













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







1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078

1079
1080
1081
1082
1083
1084
1085
1086
###############################################################################

fossil test-th-eval "globalState flags"
test th1-globalState-16 {$RESULT eq "0"}

###############################################################################

#fossil test-th-eval "reinitialize; globalState configuration"
#test th1-reinitialize-1 {$RESULT eq ""}

###############################################################################

fossil test-th-eval "reinitialize 1; globalState configuration"
test th1-reinitialize-2 {$RESULT ne ""}

###############################################################################

#
# NOTE: This test will fail if the command names are added to TH1, or
#       moved from Tcl builds to plain or the reverse. Sorting the
#       command lists eliminates a dependence on order.
#
#fossil test-th-eval "info commands"
#set sorted_result [lsort $RESULT]
#protOut "Sorted: $sorted_result"
#set base_commands {anoncap anycap array artifact break breakpoint \
#     builtin_request_js capexpr captureTh1 catch cgiHeaderLine checkout \
#      combobox continue copybtn date decorate defHeader dir \
#      enable_output encode64 error expr for foreach getParameter glob_match \
#      globalState hascap hasfeature html htmlize http httpize if info \
#      insertCsrf lappend lindex linecount list llength lsearch markdown nonce \
#      proc puts query randhex redirect regexp reinitialize rename render \
#      repository return searchable set setParameter setting stime string \
#      styleFooter styleHeader styleScript submenu tclReady trace unset \
#      unversioned uplevel upvar utime verifyCsrf verifyLogin wiki}
#set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe}
#if {$th1Tcl} {
#  test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]}
#} else {
#  test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]}

#}

###############################################################################

fossil test-th-eval "info vars"

if {$th1Hooks} {
  test th1-info-vars-1 {[lsort $RESULT] eq \
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
test th1-string-is-3 {$RESULT eq \
{TH_ERROR: wrong # args: should be "string is class string"}}

###############################################################################

fossil test-th-eval {string is other 123}
test th1-string-is-4 {$RESULT eq \
"TH_ERROR: Expected alnum, double, integer, or list, got: other"}

###############################################################################

fossil test-th-eval {string is alnum 123}
test th1-string-is-5 {$RESULT eq "1"}

###############################################################################







|







1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
test th1-string-is-3 {$RESULT eq \
{TH_ERROR: wrong # args: should be "string is class string"}}

###############################################################################

fossil test-th-eval {string is other 123}
test th1-string-is-4 {$RESULT eq \
"TH_ERROR: Expected alnum, double, integer, list, or tainted, got: other"}

###############################################################################

fossil test-th-eval {string is alnum 123}
test th1-string-is-5 {$RESULT eq "1"}

###############################################################################
Changes to www/cgi.wiki.
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
If no repository has such a non-zero repolist-skin setting, then
the repository list is generic HTML without any decoration, with
the page title taken from the <tt>FOSSIL_REPOLIST_TITLE</tt>
environment variable. The variable can be defined in the CGI
control file using the [#setenv|<tt>setenv:</tt>] statement.

The "Project Description" and "Login-Group" columns on the repolist page
are optional.  They are hidden by default.  Show them by putting value "1"
in the global settings "show-repolist-desc" and "show-repolist-lg", or
by setting the <tt>FOSSIL_REPOLIST_SHOW</tt> environment variable to
a string that contains substrings "description" and/or "login-group".

The repolist-generated page recurses into subdirectories and will list
all <tt>*.fossil</tt> files found, with the following exceptions:

   *  Filenames starting with a period are treated as "hidden" and skipped.








|
<
|







79
80
81
82
83
84
85
86

87
88
89
90
91
92
93
94
If no repository has such a non-zero repolist-skin setting, then
the repository list is generic HTML without any decoration, with
the page title taken from the <tt>FOSSIL_REPOLIST_TITLE</tt>
environment variable. The variable can be defined in the CGI
control file using the [#setenv|<tt>setenv:</tt>] statement.

The "Project Description" and "Login-Group" columns on the repolist page
are optional.  They are hidden by default.  Show them by

etting the <tt>FOSSIL_REPOLIST_SHOW</tt> environment variable to
a string that contains substrings "description" and/or "login-group".

The repolist-generated page recurses into subdirectories and will list
all <tt>*.fossil</tt> files found, with the following exceptions:

   *  Filenames starting with a period are treated as "hidden" and skipped.

Changes to www/changes.wiki.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<title>Change Log</title>

<h2 id='v2_26'>Changes for version 2.26 (pending)</h2>

  *  Enhancements to [/help?cmd=diff|fossil diff] and similar:
     <ol type="a">
     <li> The --from can optionally accept a directory name as its argument,
          and uses files under that directory as the baseline for the diff.
     <li> For "gdiff", if no [/help?cmd=gdiff-command|gdiff-command setting]
          is defined, Fossil tries to do a --tk diff if "tclsh" and "wish"
          are available, or a --by diff if not.
     <li> The "Reload" button is added to --tk diffs, to bring the displayed
          diff up to date with the latest changes on disk.
     <li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show
          diffs of multiple files.
     </ol>
  *  Added the [/help?cmd=/ckout|/ckout web page] to provide information
     about pending changes in a working check-out
  *  Enhancements to the [/help?cmd=ui|fossil ui] command:
     <ol type="a">
     <li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its
          start page.  Or, if the new "--from PATH" option is present, the
          default start page becomes "/ckout?exbase=PATH".
     <li> The new "--extpage FILENAME" option opens the named file as if it
          where in a [./serverext.wiki|CGI extension].  Example usage: the
          person editing this change log has
          "fossil ui --extpage www/changes.wiki" running and hence can
          press "Reload" on the web browser to view edits.



     </ol>
  *  Enhancements to [/help?cmd=merge|fossil merge]:
     <ol type="a">
     <li> Added the [/help?cmd=merge-info|fossil merge-info] command and
          especially the --tk option to that command, to provide analysis
          of the most recent merge or update operation.
     <li> When a merge conflict occurs, a new section is added to the conflict
          text that shows Fossil's suggested resolution to the conflict.
     </ol>
  *  Enhancements to [/help?cmd=commit|fossil commit]:
     <ol type="a">
     <li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks)
          in the check-in comment, it will alert the developer and give
          him or her the opportunity to edit the comment before continuing.
          This feature is controllable by the
          [/help?cmd=verify-comments|verify-comments setting].
     <li> The new "--if-changes" option causes the commit to become
          a quiet no-op if there are no pending changes.
     <li> Added the ability to sign check-ins with SSH keys.
     <li> Issue a warning if a user tries to commit on a check-in where the
          branch has been changed.
     <li> The interactive checkin comment prompt shows the formatting rules
          set for that repository.
     <li> Add the "--editor" option.
     </ol>
  *  Deprecate the --comfmtflags and --comment-format global options and
     no longer list them in the built-in help, but keep them working for
     backwards compatibility.
     Alternative TTY comment formatting can still be specified using the
     [/help?cmd=comment-format|comment-format setting], if desired.  The
     default comment format is now called "canonical", not "legacy".
  *  Enhancements to the [/help?cmd=/timeline|/timeline page]:
     <ol type="a">
     <li> Added the "ml=" ("Merge-in List") query parameter that works
          like "rl=" ("Related List") but adds "mionly" style related
          check-ins instead of the full "rel" style.
     <li> For "tl=", "rl=", and "ml=", the order of the branches in the
          graph now tries to match the order of the branches named in
          the list.


|
<
|











|

|









>
>
>

|







|















|





|







1
2
3

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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
<title>Change Log</title>

<h2 id='v2_26'>Changes for version 2.26 (pending)</h2><ol>

 <li>Enhancements to [/help?cmd=diff|fossil diff] and similar:
     <ol type="a">
     <li> The --from can optionally accept a directory name as its argument,
          and uses files under that directory as the baseline for the diff.
     <li> For "gdiff", if no [/help?cmd=gdiff-command|gdiff-command setting]
          is defined, Fossil tries to do a --tk diff if "tclsh" and "wish"
          are available, or a --by diff if not.
     <li> The "Reload" button is added to --tk diffs, to bring the displayed
          diff up to date with the latest changes on disk.
     <li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show
          diffs of multiple files.
     </ol>
 <li>Added the [/help?cmd=/ckout|/ckout web page] to provide information
     about pending changes in a working check-out
 <li>Enhancements to the [/help?cmd=ui|fossil ui] command:
     <ol type="a">
     <li> Defaults to using the new [/help?cmd=/ckout|/ckout page] as its
          start page.  Or, if the new "--from PATH" option is present, the
          default start page becomes "/ckout?exbase=PATH".
     <li> The new "--extpage FILENAME" option opens the named file as if it
          where in a [./serverext.wiki|CGI extension].  Example usage: the
          person editing this change log has
          "fossil ui --extpage www/changes.wiki" running and hence can
          press "Reload" on the web browser to view edits.
     <li> Accept both IPv4 and IPv6 connections on all platforms, including
          Windows and OpenBSD.  This also applies to the "fossil server"
          command.
     </ol>
 <li>Enhancements to [/help?cmd=merge|fossil merge]:
     <ol type="a">
     <li> Added the [/help?cmd=merge-info|fossil merge-info] command and
          especially the --tk option to that command, to provide analysis
          of the most recent merge or update operation.
     <li> When a merge conflict occurs, a new section is added to the conflict
          text that shows Fossil's suggested resolution to the conflict.
     </ol>
 <li>Enhancements to [/help?cmd=commit|fossil commit]:
     <ol type="a">
     <li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks)
          in the check-in comment, it will alert the developer and give
          him or her the opportunity to edit the comment before continuing.
          This feature is controllable by the
          [/help?cmd=verify-comments|verify-comments setting].
     <li> The new "--if-changes" option causes the commit to become
          a quiet no-op if there are no pending changes.
     <li> Added the ability to sign check-ins with SSH keys.
     <li> Issue a warning if a user tries to commit on a check-in where the
          branch has been changed.
     <li> The interactive checkin comment prompt shows the formatting rules
          set for that repository.
     <li> Add the "--editor" option.
     </ol>
 <li>Deprecate the --comfmtflags and --comment-format global options and
     no longer list them in the built-in help, but keep them working for
     backwards compatibility.
     Alternative TTY comment formatting can still be specified using the
     [/help?cmd=comment-format|comment-format setting], if desired.  The
     default comment format is now called "canonical", not "legacy".
 <li>Enhancements to the [/help?cmd=/timeline|/timeline page]:
     <ol type="a">
     <li> Added the "ml=" ("Merge-in List") query parameter that works
          like "rl=" ("Related List") but adds "mionly" style related
          check-ins instead of the full "rel" style.
     <li> For "tl=", "rl=", and "ml=", the order of the branches in the
          graph now tries to match the order of the branches named in
          the list.
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142














143
144
145
146
147
148
149
150
151
          case the timeline shows those check-ins that are both ancestors
          of p= and descendants of d=.
     <li> The saturation and intensity of user-specified checkin and branch
          background colors are automatically adjusted to keep the colors
          compatible with the current skin, unless the
          [/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on.
     </ol>
  *  The [/help?cmd=/docfile|/docfile webpage] was added.  It works like
     /doc but keeps the title of markdown documents with the document rather
     that moving it up to the page title.
  *  Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
     and debugging
  *  Added the "artifact_to_json(NAME)" SQL function that returns a JSON
     decoding of the artifact described by NAME.
  *  Improvements to the [/help?cmd=patch|fossil patch] command:
     <ol type="a">
     <li> Fix a bug in "fossil patch create" that causes
          [/help?cmd=revert|fossil revert] operations that happened
          on individualfiles after a [/help?cmd=merge|fossil merge]
          to be omitted from the patch.
     <li>  Added the [/help?cmd=patch|patch alias] command for managing
           aliases for remote checkout names.
     </ol>
  *  Enhancements to on-line help and the [/help?cmd=help|fossil help] command:
     <ol type="a">
     <li> Add the ability to search the help text, either in the UI
          (on the [/help?cmd=/search|/search page]) or from the command-line
          (using the "[/help?cmd=search|fossil search -h PATTERN]" command.)
     <li> Accepts an optional SUBCOMMAND argument following the
          COMMAND argument and only shows results for the specified
          subcommand, not the entire command.
     <li> The -u (--usage) option shows only the command-line syntax
     <li> The -o (--options) option shows only the command-line options
     </ol>
  *  Enhancements to the ticket system:
     <ol type="a">
     <li> Added the ability to attach wiki pages to a ticket for extended
          descriptions.
     <li> Added submenu to the 'View Ticket' page, to use it as
          template for a new ticket.
     <li> Added button 'Submit and New' to create multiple tickets
          in a row.
     <li> Link the version field in ticket view to a matching checkin or tag.
     <li> Show creation time in report and ticket view.
     <li> Show previous comments in edit ticket as reference.
     </ol>
  *  Added the "hash" query parameter to the
     [/help?cmd=/whatis|/whatis webpage].
  *  Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription]
     which alerts subscribers when an admin creates a new user or
     when a user's permissions change.
  *  Show project description on repository list.
  *  Make [/help?cmd=/chat|/chat] better-behaved during server outages, reducing
     the frequency of reconnection attempts over time and providing feedback
     to the user when the connection is down.














  *  Diverse minor fixes and additions.


<h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2>

  *  The "[/help?cmd=ui|fossil ui /]" command now works even for repositories
     that have non-ASCII filenames
  *  Add the [/help?cmd=tree|fossil tree] command.
  *  On case-insensitive filesystems, store files using the filesystem's







|


|

|

|








|










|











|

|


|
|


>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|







90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
          case the timeline shows those check-ins that are both ancestors
          of p= and descendants of d=.
     <li> The saturation and intensity of user-specified checkin and branch
          background colors are automatically adjusted to keep the colors
          compatible with the current skin, unless the
          [/help?cmd=raw-bgcolor|raw-bgcolor setting] is turned on.
     </ol>
 <li>The [/help?cmd=/docfile|/docfile webpage] was added.  It works like
     /doc but keeps the title of markdown documents with the document rather
     that moving it up to the page title.
 <li>Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
     and debugging
 <li>Added the "artifact_to_json(NAME)" SQL function that returns a JSON
     decoding of the artifact described by NAME.
 <li>Improvements to the [/help?cmd=patch|fossil patch] command:
     <ol type="a">
     <li> Fix a bug in "fossil patch create" that causes
          [/help?cmd=revert|fossil revert] operations that happened
          on individualfiles after a [/help?cmd=merge|fossil merge]
          to be omitted from the patch.
     <li>  Added the [/help?cmd=patch|patch alias] command for managing
           aliases for remote checkout names.
     </ol>
 <li>Enhancements to on-line help and the [/help?cmd=help|fossil help] command:
     <ol type="a">
     <li> Add the ability to search the help text, either in the UI
          (on the [/help?cmd=/search|/search page]) or from the command-line
          (using the "[/help?cmd=search|fossil search -h PATTERN]" command.)
     <li> Accepts an optional SUBCOMMAND argument following the
          COMMAND argument and only shows results for the specified
          subcommand, not the entire command.
     <li> The -u (--usage) option shows only the command-line syntax
     <li> The -o (--options) option shows only the command-line options
     </ol>
 <li>Enhancements to the ticket system:
     <ol type="a">
     <li> Added the ability to attach wiki pages to a ticket for extended
          descriptions.
     <li> Added submenu to the 'View Ticket' page, to use it as
          template for a new ticket.
     <li> Added button 'Submit and New' to create multiple tickets
          in a row.
     <li> Link the version field in ticket view to a matching checkin or tag.
     <li> Show creation time in report and ticket view.
     <li> Show previous comments in edit ticket as reference.
     </ol>
 <li>Added the "hash" query parameter to the
     [/help?cmd=/whatis|/whatis webpage].
 <li>Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription]
     which alerts subscribers when an admin creates a new user or
     when a user's permissions change.
 <li>Show project description on repository list.
 <li>Make [/help?cmd=/chat|/chat] better-behaved during server outages, reducing
     the frequency of reconnection attempts over time and providing feedback
     to the user when the connection is down.
 <li>The [/doc/trunk/www/th1.md|TH1 script language] is enhanced for improved
     security:
     <ol type="a">
     <li> TH1 now makes a distinction between
          [/doc/trunk/www/th1.md#taint|tainted and untainted string values].
          This makes it more difficult to write custom TH1 scripts that
          contain XSS or SQL-injection bugs.  The
          [/help?cmd=vuln-report|vuln-report] setting was added to control
          what Fossil does when it encounters a potential TH1
          security problem.
     <li> The "--th" option was removed from the [/help?cmd=pikchr|fossil pikchr]
          command.
     <li> The "enable_htmlify" TH1 command was removed.
     </ol>
 <li>Many other minor fixes and additions.
</ol>

<h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2>

  *  The "[/help?cmd=ui|fossil ui /]" command now works even for repositories
     that have non-ASCII filenames
  *  Add the [/help?cmd=tree|fossil tree] command.
  *  On case-insensitive filesystems, store files using the filesystem's
Changes to www/quickstart.wiki.
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

<pre><b>fossil version
This is fossil version 2.25 [8f798279d5] 2024-11-06 12:59:09 UTC
</b></pre>

<h2 id="workflow" name="fslclone">General Work Flow</h2>

Fossil works with repository files (a database in a single file with the project's
complete history) and with checked-out local trees (the working directory
you use to do your work). 
(See [./glossary.md | the glossary] for more background.)
The workflow looks like this:

<ul>
    <li>Create or clone a repository file.  ([/help/init|fossil init] or
        [/help/clone | fossil clone])
    <li>Check out a local tree.  ([/help/open | fossil open])
    <li>Perform operations on the repository (including repository
        configuration).
</ul>

Fossil can be entirely driven from the command line. Many features
can also be conveniently accessed from the built-in web user interface.

The following sections give a brief overview of these
operations.

<h2 id="new">Starting A New Project</h2>

To start a new project with fossil create a new empty repository
this way: ([/help/init | more info])

<pre><b>fossil init</b> <i>repository-filename</i>
</pre>

You can name the database anything you like, and you can place it anywhere in the filesystem.
The <tt>.fossil</tt> extension is traditional, but it is only required if you are going to use the 
<tt>[/help/server | fossil server DIRECTORY]</tt> feature.







|
<
<
|
|

















|
<







17
18
19
20
21
22
23
24


25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

45
46
47
48
49
50
51

<pre><b>fossil version
This is fossil version 2.25 [8f798279d5] 2024-11-06 12:59:09 UTC
</b></pre>

<h2 id="workflow" name="fslclone">General Work Flow</h2>

Fossil works with [./glossary.md#repository | repository files]


and [./glossary.md#check-out | check-out directories] using a
workflow like this:

<ul>
    <li>Create or clone a repository file.  ([/help/init|fossil init] or
        [/help/clone | fossil clone])
    <li>Check out a local tree.  ([/help/open | fossil open])
    <li>Perform operations on the repository (including repository
        configuration).
</ul>

Fossil can be entirely driven from the command line. Many features
can also be conveniently accessed from the built-in web user interface.

The following sections give a brief overview of these
operations.

<h2 id="new">Starting A New Project</h2>

To start a new project with Fossil, [/help/init | create a new empty repository]:


<pre><b>fossil init</b> <i>repository-filename</i>
</pre>

You can name the database anything you like, and you can place it anywhere in the filesystem.
The <tt>.fossil</tt> extension is traditional, but it is only required if you are going to use the 
<tt>[/help/server | fossil server DIRECTORY]</tt> feature.
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97


<h2 id="clone">Cloning An Existing Repository</h2>

Most fossil operations interact with a repository that is on the
local disk drive, not on a remote system.  Hence, before accessing
a remote repository it is necessary to make a local copy of that
repository.  Making a local copy of a remote repository is called
"cloning".

Clone a remote repository as follows: ([/help/clone | more info])

<pre><b>fossil clone</b> <i>URL repository-filename</i>
</pre>

The <i>URL</i> specifies the fossil repository
you want to clone.  The <i>repository-filename</i> is the new local
filename into which the cloned repository will be written.  For







|
|

|







77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94


<h2 id="clone">Cloning An Existing Repository</h2>

Most fossil operations interact with a repository that is on the
local disk drive, not on a remote system.  Hence, before accessing
a remote repository it is necessary to make a local copy of that
repository, a process called
"[/help/clone | cloning]".

This is done as follows:

<pre><b>fossil clone</b> <i>URL repository-filename</i>
</pre>

The <i>URL</i> specifies the fossil repository
you want to clone.  The <i>repository-filename</i> is the new local
filename into which the cloned repository will be written.  For
105
106
107
108
109
110
111
112
113








114
115
116
117
118
119
120
Clone done, sent: 2424  received: 42965725  ip: 10.10.10.0
Rebuilding repository meta-data...
100% complete...
Extra delta compression... 
Vacuuming the database... 
project-id: 94259BB9F186226D80E49D1FA2DB29F935CCA0333
server-id:  016595e9043054038a9ea9bc526d7f33f7ac0e42
admin-user: exampleuser (password is "yoWgDR42iv")>
</b></pre>









If the remote repository requires a login, include a
userid in the URL like this:

<pre><b>fossil clone https://</b><i>remoteuserid</i><b>@www.example.org/ myclone.fossil</b></pre>

You will be prompted separately for the password.







|

>
>
>
>
>
>
>
>







102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
Clone done, sent: 2424  received: 42965725  ip: 10.10.10.0
Rebuilding repository meta-data...
100% complete...
Extra delta compression... 
Vacuuming the database... 
project-id: 94259BB9F186226D80E49D1FA2DB29F935CCA0333
server-id:  016595e9043054038a9ea9bc526d7f33f7ac0e42
admin-user: exampleuser (intial remote-access password is "yoWgDR42iv")>
</b></pre>

This <i>exampleuser</i> will be used by Fossil as the author of commits when
you checkin changes to the repository.  It is also used by Fossil when you
make your repository available to others using the built-in server mode by
running <tt>[/help/server | fossil server]</tt> and will also be used when
running <tt>[/help/ui | fossil ui]</tt> to view the repository through 
the Fossil UI.  See the quick start topic for setting up a
<a href="#server">server</a> for more details.

If the remote repository requires a login, include a
userid in the URL like this:

<pre><b>fossil clone https://</b><i>remoteuserid</i><b>@www.example.org/ myclone.fossil</b></pre>

You will be prompted separately for the password.
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
[https://www.mercurial-scm.org/|Mercurial].
Fossil can also import [https://subversion.apache.org/|Subversion projects] directly.

<h2 id="checkout">Checking Out A Local Tree</h2>

To work on a project in fossil, you need to check out a local
copy of the source tree.  Create the directory you want to be
the root of your tree and cd into that directory.  Then
do this: ([/help/open | more info])

<pre><b>fossil open</b> <i>repository-filename</i></pre>

for example:

<pre><b>fossil open ../myclone.fossil
    BUILD.txt
    COPYRIGHT-BSD2.txt
    README.md
      ︙
</tt></b></pre>

(or "fossil open ..\myclone.fossil" on Windows).

This leaves you with the newest version of the tree
checked out.
From anywhere underneath the root of your local tree, you
can type commands like the following to find out the status of
your local tree:

<pre>







|
<



|








<
<







156
157
158
159
160
161
162
163

164
165
166
167
168
169
170
171
172
173
174
175


176
177
178
179
180
181
182
[https://www.mercurial-scm.org/|Mercurial].
Fossil can also import [https://subversion.apache.org/|Subversion projects] directly.

<h2 id="checkout">Checking Out A Local Tree</h2>

To work on a project in fossil, you need to check out a local
copy of the source tree.  Create the directory you want to be
the root of your tree, <tt>cd</tt> into that directory, and then:


<pre><b>fossil open</b> <i>repository-filename</i></pre>

For example:

<pre><b>fossil open ../myclone.fossil
    BUILD.txt
    COPYRIGHT-BSD2.txt
    README.md
      ︙
</tt></b></pre>



This leaves you with the newest version of the tree
checked out.
From anywhere underneath the root of your local tree, you
can type commands like the following to find out the status of
your local tree:

<pre>
318
319
320
321
322
323
324
325
326
327
328
329
330
331

332
333
334
335
336
337
338
339

340
341
342
343
344
345
346
347
348
349




350






351








352
353
354
355



356
357
358
359
360
361
362
the first checkin, and the default for any time a branch name is needed but not
specified.

This will get you started on identifying checkins. The
<a href="./checkin_names.wiki">Checkin Names document</a> is a complete reference, including
how timestamps can also be used.

<h2 id="config">Configuring Your Local Repository</h2>

When you create a new repository, either by cloning an existing
project or create a new project of your own, you usually want to do some
local configuration.  This is easily accomplished using the web-server
that is built into fossil.  Start the fossil web server like this:
([/help/ui | more info])


<pre>
<b>fossil ui</b> <i>repository-filename</i>
</pre>

You can omit the <i>repository-filename</i> from the command above
if you are inside a checked-out local tree.


This starts a web server then automatically launches your
web browser and makes it point to this web server.  If your system
has an unusual configuration, fossil might not be able to figure out
how to start your web browser.  In that case, first tell fossil
where to find your web browser using a command like this:

<pre>
<b>fossil setting web-browser</b> <i>path-to-web-browser</i>
</pre>





By default, fossil does not require a login for HTTP connections






coming in from the IP loopback address 127.0.0.1.  You can, and perhaps








should, change this after you create a few users.

When you are finished configuring, just press Control-C or use
the <b>kill</b> command to shut down the mini-server.




<h2 id="sharing">Sharing Changes</h2>

When [./concepts.wiki#workflow|autosync] is turned off,
the changes you [/help/commit | commit] are only
on your local repository.
To share those changes with other repositories, do:







|

|
<
|
<
<
>





|


>
|
<
|
|
|





>
>
>
>
|
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
|

|
|
>
>
>







320
321
322
323
324
325
326
327
328
329

330


331
332
333
334
335
336
337
338
339
340
341

342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
the first checkin, and the default for any time a branch name is needed but not
specified.

This will get you started on identifying checkins. The
<a href="./checkin_names.wiki">Checkin Names document</a> is a complete reference, including
how timestamps can also be used.

<h2 id="config">Accessing Your Local Repository's Web User Interface</h2>

After you create a new repository, you usually want to do some local

configuration.  This is most easily accomplished by firing up the Fossil


UI:

<pre>
<b>fossil ui</b> <i>repository-filename</i>
</pre>

You can shorten that to just [/help/ui | <b>fossil ui</b>]
if you are inside a checked-out local tree.

This command starts an internal web server, after which Fossil
automatically launches your default browser, pointed at itself,

presenting a special view of the repository, its web user interface.

You may override Fossil's logic for selecting the default browser so:

<pre>
<b>fossil setting web-browser</b> <i>path-to-web-browser</i>
</pre>

When launched this way, Fossil binds its internal web server to the IP
loopback address, 127.0.0.1, which it treats specially, bypassing all
user controls, effectively giving visitors the
[./caps/admin-v-setup.md#apsu | all-powerful Setup capabliity].

Why is that a good idea, you ask? Because it is a safe
presumption that only someone with direct file access to the repository
database file could be using the resulting web interface. Anyone who can
modify the repo DB directly could give themselves any and all access
with a SQL query, or even by direct file manipulation; no amount of
access control matters to such a user.

(Contrast the [#server | many <i>other</i> ways] of setting Fossil up
as an HTTP server, where the repo DB is on the other side of the HTTP
server wall, inaccessible by all means other than Fossil's own
mediation. For this reason, the "localhost bypasses access control"
policy does <i>not</i> apply to these other interfaces. That is a very
good thing, since without this difference in policy, it would be unsafe
to bind a [/help?cmd=server | <b>fossil server</b>] instance to
localhost on a high-numbered port and then reverse-proxy it out to the
world via HTTPS, a practice this author does engage in, with confidence.)

Once you are finished configuring Fossil, you may safely Control-C out
of the <b>fossil&nbsp;ui</b> command to shut down this privileged
built-in web server. Moreover, you may by grace of SQLite do this <i>at
any time</i>: all changes are either committed durably to the repo DB or
rolled back, in their totality. This includes configuration changes.

<h2 id="sharing">Sharing Changes</h2>

When [./concepts.wiki#workflow|autosync] is turned off,
the changes you [/help/commit | commit] are only
on your local repository.
To share those changes with other repositories, do:
462
463
464
465
466
467
468

469
470
471
472
473
474
475
476

477
478
479
480
481
482
483
484
485
486


487
488
489
490
491
492
493
494
495
496
497
498
499
500
501

502
503

504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
mistake.  Undo and redo only work for changes that have
not yet been checked in using commit and there is only a single
level of undo/redo.


<h2 id="server">Setting Up A Server</h2>


Fossil can act as a stand-alone web server using one of these
commands:

<pre>
<b>[/help/server | fossil server]</b> <i>repository-filename</i>
<b>[/help/ui | fossil ui]</b> <i>repository-filename</i>
</pre>


The <i>repository-filename</i> can be omitted when these commands
are run from within an open check-out, which is a particularly useful
shortcut with the <b>fossil ui</b> command.

The <b>ui</b> command is intended for accessing the web user interface
from a local desktop. (We sometimes call this mode "Fossil UI.")
The <b>ui</b> command differs from the
<b>server</b> command by binding to the loopback IP
address only (thus making the web UI visible only on the
local machine) and by automatically starting your default web browser,


pointing it at the running UI
server. The localhost restriction exists because it also gives anyone
who can access the resulting web UI full control over the
repository. (This is the [./caps/admin-v-setup.md#apsu | all-powerful
Setup capabliity].)

For cross-machine collaboration, use the <b>server</b> command instead,
which binds on all IP addresses, does not try to start a web browser,
and enforces [./caps/ | Fossil's role-based access control system].

Servers are also easily configured as:

<ul>
<li>[./server/any/inetd.md|inetd]
<li>[./server/debian/service.md|systemd]

<li>[./server/any/cgi.md|CGI]
<li>[./server/any/scgi.md|SCGI]

</ul>

…along with [./server/#matrix | several other options].

The [./selfhost.wiki | self-hosting fossil repositories] use
CGI.

You might <i>need</i> to set up a server, whether you know it yet or
not.  See the [./server/whyuseaserver.wiki | Benefits of a Fossil Server]
article for details.

<h2 id="proxy">HTTP Proxies</h2>

If you are behind a restrictive firewall that requires you to use
an HTTP proxy to reach the internet, then you can configure the proxy
in three different ways.  You can tell fossil about your proxy using
a command-line option on commands that use the network,







>
|
<



<


>
|
|
<

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

<
<
<
|
|


|
<
>
|
|
>




<
<
|
|
<
|







483
484
485
486
487
488
489
490
491

492
493
494

495
496
497
498
499

500


501


502
503
504
505


506

507



508
509
510
511
512

513
514
515
516
517
518
519
520


521
522

523
524
525
526
527
528
529
530
mistake.  Undo and redo only work for changes that have
not yet been checked in using commit and there is only a single
level of undo/redo.


<h2 id="server">Setting Up A Server</h2>

In addition to the inward-facing <b>fossil ui</b> mode covered [#config
| above], Fossil can also act as an outward-facing web server:


<pre>
<b>[/help/server | fossil server]</b> <i>repository-filename</i>

</pre>

Just as with <b>fossil ui</b>, you may omit the
<i>repository-filename</i> parameter when running this from within an open
check-out.




<i>Unlike</i> <b>fossil ui</b> mode, Fossil binds to all network


interfaces by default in this mode, and it enforces the configured
[./caps/ | role-based access controls]. Further, because it is meant to
provide external web service, it doesn't try to launch a local web
browser pointing to a "Fossil UI" presentation; external visitors see


your repository's configured home page instead.





To serve varying needs, there are additional ways to serve a Fossil repo
to external users:

<ul>
<li>[./server/any/cgi.md|CGI], as used by Fossil's [./selfhost.wiki |

    self-hosting repositories]
<li>[./server/any/scgi.md|SCGI]
<li>[./server/any/inetd.md|inetd]
<li>[./server/debian/service.md|systemd]
</ul>

…along with [./server/#matrix | several other options].



We recommend that you read the [./server/whyuseaserver.wiki | Benefits
of a Fossil Server] article, because you might <i>need</i> to do this

and not yet know it.

<h2 id="proxy">HTTP Proxies</h2>

If you are behind a restrictive firewall that requires you to use
an HTTP proxy to reach the internet, then you can configure the proxy
in three different ways.  You can tell fossil about your proxy using
a command-line option on commands that use the network,
Changes to www/th1.md.
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
of the command, is `if`.
The second token is `$current eq "dev"` - an expression.  (The outer {...}
are removed from each token by the command parser.)  The third token
is the `puts "hello"`, with its whitespace and newlines.  The fourth token
is `else` and the fifth and last token is `puts "world"`.

The `if` command evaluates its first argument (the second token)
as an expression, and if that expression is true, evaluates its
second argument (the third token) as a TH1 script.
If the expression is false and the third argument is `else`, then
the fourth argument is evaluated as a TH1 expression.

So, you see, even though the example above spans five lines, it is really
just a single command.








|







66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
of the command, is `if`.
The second token is `$current eq "dev"` - an expression.  (The outer {...}
are removed from each token by the command parser.)  The third token
is the `puts "hello"`, with its whitespace and newlines.  The fourth token
is `else` and the fifth and last token is `puts "world"`.

The `if` command evaluates its first argument (the second token)
as an expression, and if that expression is true, it evaluates its
second argument (the third token) as a TH1 script.
If the expression is false and the third argument is `else`, then
the fourth argument is evaluated as a TH1 expression.

So, you see, even though the example above spans five lines, it is really
just a single command.

104
105
106
107
108
109
110







































111
112
113
114
115
116
117
    return [lindex [regexp -line -inline -nocase -- \
        {^uuid:\s+([0-9A-F]{40}) } [eval [getFossilCommand \
        $repository "" info trunk]]] end]

Those backslashes allow the command to wrap nicely within a standard
terminal width while telling the interpreter to consider those three
lines as a single command.









































Summary of Core TH1 Commands
----------------------------

The original Tcl language (after which TH1 is modeled) has a very rich
repertoire of commands.  TH1, as it is designed to be minimalist and







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
    return [lindex [regexp -line -inline -nocase -- \
        {^uuid:\s+([0-9A-F]{40}) } [eval [getFossilCommand \
        $repository "" info trunk]]] end]

Those backslashes allow the command to wrap nicely within a standard
terminal width while telling the interpreter to consider those three
lines as a single command.

<a id="taint"></a>Tainted And Untainted Strings
-----------------------------------------------

Beginning with Fossil version 2.26 (circa 2025), TH1 distinguishes between
"tainted" and "untainted" strings.  Tainted strings are strings that are
derived from user inputs that might contain text that is designed to subvert
the script.  Untainted strings are known to come from secure sources and
are assumed to contain no malicious content.

Beginning with Fossil version 2.26, and depending on the value of the
[vuln-report setting](/help?cmd=vuln-report), TH1 will prevent tainted
strings from being used in ways that might lead to XSS or SQL-injection
attacks.  This feature helps to ensure that XSS and SQL-injection
vulnerabilities are not *accidentally* added to Fossil when
custom TH1 scripts for headers or footers or tickets are added to a
repository.  Note that the tainted/untainted distinction in strings does
not make it impossible to introduce XSS and SQL-injections vulnerabilities
using poorly-written TH1 scripts; it just makes it more difficult and
less likely to happen by accident.  Developers must still consider the
security implications TH1 customizations they add to Fossil, and take
appropriate precautions when writing custom TH1.  Peer review of TH1
script changes is encouraged.

In Fossil version 2.26, if the vuln-report setting is set to "block"
or "fatal", the [html](#html) and [query](#query) TH1 commands will
fail with an error if their argument is a tainted string.  This helps
to prevent XSS and SQL-injection attacks, respectively.  Note that
the default value of the vuln-report setting is "log", which allows those
commands to continue working and only writes a warning message into the
error log.  <b>Future versions of Fossil may change the default value
of the vuln-report setting to "block" or "fatal".</b>  Fossil users
with customized TH1 scripts are encouraged to audit their customizations
and fix any potential vulnerabilities soon, so as to avoid breakage
caused by future upgrades.  <b>Future versions of Fossil might also
place additional restrictions on the use of tainted strings.</b>
For example, it is likely that future versions of Fossil will disallow
using tainted strings as script, for example as the body of a "for"
loop or of a "proc".


Summary of Core TH1 Commands
----------------------------

The original Tcl language (after which TH1 is modeled) has a very rich
repertoire of commands.  TH1, as it is designed to be minimalist and
145
146
147
148
149
150
151



152
153
154
155
156
157
158
  *  string index STRING INDEX
  *  string is CLASS STRING
  *  string last NEEDLE HAYSTACK ?START-INDEX?
  *  string match PATTERN STRING
  *  string length STRING
  *  string range STRING FIRST LAST
  *  string repeat STRING COUNT



  *  unset VARNAME
  *  uplevel ?LEVEL? SCRIPT
  *  upvar ?FRAME? OTHERVAR MYVAR ?OTHERVAR MYVAR?

All of the above commands work as in the original Tcl.  Refer to the
<a href="https://www.tcl-lang.org/man/tcl/contents.htm">Tcl documentation</a>
for details.







>
>
>







184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
  *  string index STRING INDEX
  *  string is CLASS STRING
  *  string last NEEDLE HAYSTACK ?START-INDEX?
  *  string match PATTERN STRING
  *  string length STRING
  *  string range STRING FIRST LAST
  *  string repeat STRING COUNT
  *  string trim STRING
  *  string trimleft STRING
  *  string trimright STRING
  *  unset VARNAME
  *  uplevel ?LEVEL? SCRIPT
  *  upvar ?FRAME? OTHERVAR MYVAR ?OTHERVAR MYVAR?

All of the above commands work as in the original Tcl.  Refer to the
<a href="https://www.tcl-lang.org/man/tcl/contents.htm">Tcl documentation</a>
for details.
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
  *  [checkout](#checkout)
  *  [combobox](#combobox)
  *  [copybtn](#copybtn)
  *  [date](#date)
  *  [decorate](#decorate)
  *  [defHeader](#defHeader)
  *  [dir](#dir)
  *  [enable\_htmlify](#enable_htmlify)
  *  [enable\_output](#enable_output)
  *  [encode64](#encode64)
  *  [getParameter](#getParameter)
  *  [glob\_match](#glob_match)
  *  [globalState](#globalState)
  *  [hascap](#hascap)
  *  [hasfeature](#hasfeature)







<







222
223
224
225
226
227
228

229
230
231
232
233
234
235
  *  [checkout](#checkout)
  *  [combobox](#combobox)
  *  [copybtn](#copybtn)
  *  [date](#date)
  *  [decorate](#decorate)
  *  [defHeader](#defHeader)
  *  [dir](#dir)

  *  [enable\_output](#enable_output)
  *  [encode64](#encode64)
  *  [getParameter](#getParameter)
  *  [glob\_match](#glob_match)
  *  [globalState](#globalState)
  *  [hascap](#hascap)
  *  [hasfeature](#hasfeature)
212
213
214
215
216
217
218

219
220
221
222
223
224
225

226
227
228
229
230
231
232
  *  [setParameter](#setParameter)
  *  [setting](#setting)
  *  [stime](#stime)
  *  [styleHeader](#styleHeader)
  *  [styleFooter](#styleFooter)
  *  [styleScript](#styleScript)
  *  [submenu](#submenu)

  *  [tclEval](#tclEval)
  *  [tclExpr](#tclExpr)
  *  [tclInvoke](#tclInvoke)
  *  [tclIsSafe](#tclIsSafe)
  *  [tclMakeSafe](#tclMakeSafe)
  *  [tclReady](#tclReady)
  *  [trace](#trace)

  *  [unversioned content](#unversioned_content)
  *  [unversioned list](#unversioned_list)
  *  [utime](#utime)
  *  [verifyCsrf](#verifyCsrf)
  *  [verifyLogin](#verifyLogin)
  *  [wiki](#wiki)
  *  [wiki_assoc](#wiki_assoc)







>







>







253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
  *  [setParameter](#setParameter)
  *  [setting](#setting)
  *  [stime](#stime)
  *  [styleHeader](#styleHeader)
  *  [styleFooter](#styleFooter)
  *  [styleScript](#styleScript)
  *  [submenu](#submenu)
  *  [taint](#taintCmd)
  *  [tclEval](#tclEval)
  *  [tclExpr](#tclExpr)
  *  [tclInvoke](#tclInvoke)
  *  [tclIsSafe](#tclIsSafe)
  *  [tclMakeSafe](#tclMakeSafe)
  *  [tclReady](#tclReady)
  *  [trace](#trace)
  *  [untaint](#untaintCmd)
  *  [unversioned content](#unversioned_content)
  *  [unversioned list](#unversioned_list)
  *  [utime](#utime)
  *  [verifyCsrf](#verifyCsrf)
  *  [verifyLogin](#verifyLogin)
  *  [wiki](#wiki)
  *  [wiki_assoc](#wiki_assoc)
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442

Returns a list containing all files in CHECKIN. If GLOB is given only
the files matching the pattern GLOB within CHECKIN will be returned.
If DETAILS is non-zero, the result will be a list-of-lists, with each
element containing at least three elements: the file name, the file
size (in bytes), and the file last modification time (relative to the
time zone configured for the repository).

<a id="enable_htmlify"></a>TH1 enable\_htmlify Command
------------------------------------------------------

  *  enable\_htmlify
  *  enable\_htmlify ?TRACE-LABEL? BOOLEAN

By default, certain output from `puts` and similar commands is escaped
for HTML. The first call form returns the current state of that
feature: `1` for on and `0` for off. The second call form enables
(non-0) or disables (0) that feature and returns the *pre-call* state
of that feature (so that a second call can pass that value to restore
it to its previous state). The optional `TRACE-LABEL` argument causes
the TH1 tracing output (if enabled) to add a marker when the second
form of this command is invoked, and includes that label and the
boolean argument's value in the trace. If tracing is disabled, that
argument has no effect.


<a id="enable_output"></a>TH1 enable\_output Command
------------------------------------------------------

  *  enable\_output BOOLEAN

Enable or disable sending output when the combobox, copybtn, puts, or wiki







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







454
455
456
457
458
459
460


















461
462
463
464
465
466
467

Returns a list containing all files in CHECKIN. If GLOB is given only
the files matching the pattern GLOB within CHECKIN will be returned.
If DETAILS is non-zero, the result will be a list-of-lists, with each
element containing at least three elements: the file name, the file
size (in bytes), and the file last modification time (relative to the
time zone configured for the repository).



















<a id="enable_output"></a>TH1 enable\_output Command
------------------------------------------------------

  *  enable\_output BOOLEAN

Enable or disable sending output when the combobox, copybtn, puts, or wiki
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544


545
546
547
548
549
550
551

<a id="html"></a>TH1 html Command
-----------------------------------

  *  html STRING

Outputs the STRING literally.  It is assumed that STRING contains
valid HTML, or that if it text then any characters that are
significant to HTML (such as `<`, `>`, `'`, or `&`) have already
been escaped, perhaps by the htmlize command.  Use the
[puts](#puts) command to output text that might contain unescaped
HTML markup.

**Beware of XSS attacks!**  If the STRING value to the html command
can be controlled by a hostile user, then he might be able to sneak
in malicious HTML or Javascript which could result in a
cross-site scripting (XSS) attack.  Be careful that all text that
in STRING that might come from user input has been sanitized by the
[htmlize](#htmlize) command or similar.



<a id="htmlize"></a>TH1 htmlize Command
-----------------------------------------

  *  htmlize STRING

Escape all characters of STRING which have special meaning in HTML.







|

|








|
>
>







551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578

<a id="html"></a>TH1 html Command
-----------------------------------

  *  html STRING

Outputs the STRING literally.  It is assumed that STRING contains
valid HTML, or that if STRING contains any characters that are
significant to HTML (such as `<`, `>`, `'`, or `&`) have already
been escaped, perhaps by the [htmlize](#htmlize) command.  Use the
[puts](#puts) command to output text that might contain unescaped
HTML markup.

**Beware of XSS attacks!**  If the STRING value to the html command
can be controlled by a hostile user, then he might be able to sneak
in malicious HTML or Javascript which could result in a
cross-site scripting (XSS) attack.  Be careful that all text that
in STRING that might come from user input has been sanitized by the
[htmlize](#htmlize) command or similar.  In recent versions of Fossil,
the STRING value must be [untainted](#taint) or else the "html" command 
will fail.

<a id="htmlize"></a>TH1 htmlize Command
-----------------------------------------

  *  htmlize STRING

Escape all characters of STRING which have special meaning in HTML.
608
609
610
611
612
613
614
615


616
617
618
619
620
621
622
623
624
625
626
627



628
629
630
631
632
633
634
-----------------------------------

  *  puts STRING

Outputs STRING.  Characters within STRING that have special meaning
in HTML are escaped prior to being output.  Thus is it safe for STRING
to be derived from user inputs.  See also the [html](#html) command
which behaves similarly except does not escape HTML markup.



<a id="query"></a>TH1 query Command
-------------------------------------

  *  query ?-nocomplain? SQL CODE

Runs the SQL query given by the SQL argument.  For each row in the result
set, run CODE.

In SQL, parameters such as $var are filled in using the value of variable
"var".  Result values are stored in variables with the column name prior
to each invocation of CODE.




**Beware of SQL injections in the `query` command!**
The SQL argument to the query command should always be literal SQL
text enclosed in {...}.  The SQL argument should never be a double-quoted
string or the value of a \$variable, as those constructs can lead to
an SQL Injection attack.  If you need to include the values of one or
more TH1 variables as part of the SQL, then put \$variable inside the







|
>
>











|
>
>
>







635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
-----------------------------------

  *  puts STRING

Outputs STRING.  Characters within STRING that have special meaning
in HTML are escaped prior to being output.  Thus is it safe for STRING
to be derived from user inputs.  See also the [html](#html) command
which behaves similarly except does not escape HTML markup.  This
command ("puts") is safe to use on [tainted strings](#taint), but the "html"
command is not.

<a id="query"></a>TH1 query Command
-------------------------------------

  *  query ?-nocomplain? SQL CODE

Runs the SQL query given by the SQL argument.  For each row in the result
set, run CODE.

In SQL, parameters such as $var are filled in using the value of variable
"var".  Result values are stored in variables with the column name prior
to each invocation of CODE.  The names of the variables in which results
are stored can be controlled using "AS name" clauses in the SQL.  As
the database will often contain content that originates from untrusted
users, all result values are marked as [tainted](#taint).

**Beware of SQL injections in the `query` command!**
The SQL argument to the query command should always be literal SQL
text enclosed in {...}.  The SQL argument should never be a double-quoted
string or the value of a \$variable, as those constructs can lead to
an SQL Injection attack.  If you need to include the values of one or
more TH1 variables as part of the SQL, then put \$variable inside the
647
648
649
650
651
652
653




654
655
656
657
658
659
660
~~~
   query "SELECT res FROM tab1 WHERE key='$mykey'" {...}  ;# <-- UNSAFE!
~~~

In this second example, TH1 does the expansion of `$mykey` prior to passing
the text down into SQLite.  So if `$mykey` contains a single-quote character,
followed by additional hostile text, that will result in an SQL injection.





<a id="randhex"></a>TH1 randhex Command
-----------------------------------------

  *  randhex N

Returns a string of N*2 random hexadecimal digits with N<50.  If N is







>
>
>
>







679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
~~~
   query "SELECT res FROM tab1 WHERE key='$mykey'" {...}  ;# <-- UNSAFE!
~~~

In this second example, TH1 does the expansion of `$mykey` prior to passing
the text down into SQLite.  So if `$mykey` contains a single-quote character,
followed by additional hostile text, that will result in an SQL injection.

To help guard against SQL-injections, recent versions of Fossil require
that the SQL argument be [untainted](#taint) or else the "query" command
will fail.

<a id="randhex"></a>TH1 randhex Command
-----------------------------------------

  *  randhex N

Returns a string of N*2 random hexadecimal digits with N<50.  If N is
781
782
783
784
785
786
787














788
789
790
791
792
793
794
<a id="submenu"></a>TH1 submenu Command
-----------------------------------------

  *  submenu link LABEL URL

Add hyperlink to the submenu of the current page.















<a id="tclEval"></a>TH1 tclEval Command
-----------------------------------------

**This command requires the Tcl integration feature.**

  *  tclEval arg ?arg ...?








>
>
>
>
>
>
>
>
>
>
>
>
>
>







817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
<a id="submenu"></a>TH1 submenu Command
-----------------------------------------

  *  submenu link LABEL URL

Add hyperlink to the submenu of the current page.

<a id="taintCmd"></a>TH1 taint Command
-----------------------------------------

  *  taint STRING

This command returns a copy of STRING that has been marked as
[tainted](#taint).  Tainted strings are strings which might be
controlled by an attacker and might contain hostile inputs and
are thus unsafe to use in certain contexts.  For example, tainted
strings should not be output as part of a webpage as they might
contain rogue HTML or Javascript that could lead to an XSS
vulnerability.  Similarly, tainted strings should not be run as
SQL since they might contain an SQL-injection vulerability.

<a id="tclEval"></a>TH1 tclEval Command
-----------------------------------------

**This command requires the Tcl integration feature.**

  *  tclEval arg ?arg ...?

852
853
854
855
856
857
858












859
860
861
862
863
864
865
<a id="trace"></a>TH1 trace Command
-------------------------------------

  *  trace STRING

Generates a TH1 trace message if TH1 tracing is enabled.













<a id="unversioned_content"></a>TH1 unversioned content Command
-----------------------------------------------------------------

  *  unversioned content FILENAME

Attempts to locate the specified unversioned file and return its contents.
An error is generated if the repository is not open or the unversioned file







>
>
>
>
>
>
>
>
>
>
>
>







902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
<a id="trace"></a>TH1 trace Command
-------------------------------------

  *  trace STRING

Generates a TH1 trace message if TH1 tracing is enabled.

<a id="untaintCmd"></a>TH1 taint Command
-----------------------------------------

  *  untaint STRING

This command returns a copy of STRING that has been marked as
[untainted](#taint).  Untainted strings are strings which are
believed to be free of potentially hostile content.  Use this
command with caution, as it overwrites the tainted-string protection
mechanisms that are built into TH1.  If you do not understand all
the implications of executing this command, then do not use it.

<a id="unversioned_content"></a>TH1 unversioned content Command
-----------------------------------------------------------------

  *  unversioned content FILENAME

Attempts to locate the specified unversioned file and return its contents.
An error is generated if the repository is not open or the unversioned file