Fossil SCM

Merge recent trunk enhancements into the robotck-instant branch.

drh 2025-08-17 18:33 robotck-instant merge
Commit dd11b563f4258d14a0dc83198d2e3cdd714dbb0b84ad267e395a1d96dd860a9d
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -1160,11 +1160,11 @@
11601160
if(body && !body.style.fontSize){
11611161
/** _Attempt_ to force the iframe to inherit the message's text size
11621162
if the body has no explicit size set. On desktop systems
11631163
the size is apparently being inherited in that case, but on mobile
11641164
not. */
1165
- body.style.fontSize = window.getComputedStyle(msgObj.e.content);
1165
+ body.style.fontSize = window.getComputedStyle(msgObj.e.content).fontSize;
11661166
}
11671167
if('' === iframe.style.maxHeight){
11681168
/* Resize iframe height to fit the content. Workaround: if we
11691169
adjust the iframe height while it's hidden then its height
11701170
is 0, so we must briefly unhide it. */
@@ -1735,14 +1735,10 @@
17351735
(k)=>Chat.e.inputX.addEventListener(k, noDragDropEvents, false)
17361736
);
17371737
return bxs;
17381738
})()/*drag/drop/paste*/;
17391739
1740
- const tzOffsetToString = function(off){
1741
- const hours = Math.round(off/60), min = Math.round(off % 30);
1742
- return ''+(hours + (min ? '.5' : ''));
1743
- };
17441740
const localTime8601 = function(d){
17451741
return [
17461742
d.getYear()+1900, '-', pad2(d.getMonth()+1), '-', pad2(d.getDate()),
17471743
'T', pad2(d.getHours()),':', pad2(d.getMinutes()),':',pad2(d.getSeconds())
17481744
].join('');
17491745
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -1160,11 +1160,11 @@
1160 if(body && !body.style.fontSize){
1161 /** _Attempt_ to force the iframe to inherit the message's text size
1162 if the body has no explicit size set. On desktop systems
1163 the size is apparently being inherited in that case, but on mobile
1164 not. */
1165 body.style.fontSize = window.getComputedStyle(msgObj.e.content);
1166 }
1167 if('' === iframe.style.maxHeight){
1168 /* Resize iframe height to fit the content. Workaround: if we
1169 adjust the iframe height while it's hidden then its height
1170 is 0, so we must briefly unhide it. */
@@ -1735,14 +1735,10 @@
1735 (k)=>Chat.e.inputX.addEventListener(k, noDragDropEvents, false)
1736 );
1737 return bxs;
1738 })()/*drag/drop/paste*/;
1739
1740 const tzOffsetToString = function(off){
1741 const hours = Math.round(off/60), min = Math.round(off % 30);
1742 return ''+(hours + (min ? '.5' : ''));
1743 };
1744 const localTime8601 = function(d){
1745 return [
1746 d.getYear()+1900, '-', pad2(d.getMonth()+1), '-', pad2(d.getDate()),
1747 'T', pad2(d.getHours()),':', pad2(d.getMinutes()),':',pad2(d.getSeconds())
1748 ].join('');
1749
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -1160,11 +1160,11 @@
1160 if(body && !body.style.fontSize){
1161 /** _Attempt_ to force the iframe to inherit the message's text size
1162 if the body has no explicit size set. On desktop systems
1163 the size is apparently being inherited in that case, but on mobile
1164 not. */
1165 body.style.fontSize = window.getComputedStyle(msgObj.e.content).fontSize;
1166 }
1167 if('' === iframe.style.maxHeight){
1168 /* Resize iframe height to fit the content. Workaround: if we
1169 adjust the iframe height while it's hidden then its height
1170 is 0, so we must briefly unhide it. */
@@ -1735,14 +1735,10 @@
1735 (k)=>Chat.e.inputX.addEventListener(k, noDragDropEvents, false)
1736 );
1737 return bxs;
1738 })()/*drag/drop/paste*/;
1739
 
 
 
 
1740 const localTime8601 = function(d){
1741 return [
1742 d.getYear()+1900, '-', pad2(d.getMonth()+1), '-', pad2(d.getDate()),
1743 'T', pad2(d.getHours()),':', pad2(d.getMinutes()),':',pad2(d.getSeconds())
1744 ].join('');
1745
+25 -18
--- src/login.c
+++ src/login.c
@@ -339,37 +339,44 @@
339339
}else{
340340
free(zCookie);
341341
}
342342
}
343343
344
+/*
345
+** Lifetime of an anoymous cookie, in seconds.
346
+*/
347
+#define ANONYMOUS_COOKIE_LIFESPAN 28800 /* 28800 seconds == 8 hours */
348
+
344349
/* Sets a cookie for an anonymous user login, which looks like this:
345350
**
346
-** HASH/TIME:IPADDR/anonymous
351
+** HASH/TIME/anonymous
347352
**
348
-** Where HASH is the sha1sum of TIME:IPADDR/SECRET, in which SECRET
349
-** is captcha-secret.
353
+** Where HASH is the sha1sum of TIME/USERAGENT/SECRET, in which SECRET
354
+** is captcha-secret and USERAGENT is the HTTP_USER_AGENT value.
350355
**
351356
** If zCookieDest is not NULL then the generated cookie is assigned to
352357
** *zCookieDest and the caller must eventually free() it.
353358
**
354359
** If bSessionCookie is true, the cookie will be a session cookie.
360
+**
361
+** Search for tag-20250817a to find the code that recognizes this cookie.
355362
*/
356363
void login_set_anon_cookie(char **zCookieDest, int bSessionCookie){
357364
char *zNow; /* Current time (julian day number) */
358365
char *zCookie; /* The login cookie */
359
- const char *zIpAddr; /* IP Address */
366
+ const char *zUserAgent; /* The user agent */
360367
const char *zCookieName; /* Name of the login cookie */
361368
Blob b; /* Blob used during cookie construction */
362
- int expires = bSessionCookie ? 0 : 3600; /* Valid for 60 minutes */
369
+ int expires = bSessionCookie ? 0 : ANONYMOUS_COOKIE_LIFESPAN;
363370
zCookieName = login_cookie_name();
364371
zNow = db_text("0", "SELECT julianday('now')");
365372
assert( zCookieName && zNow );
366373
blob_init(&b, zNow, -1);
367
- zIpAddr = PD("REMOTE_ADDR","nil");
368
- blob_appendf(&b, ":%s/%z", zIpAddr, captcha_secret(0));
374
+ zUserAgent = PD("HTTP_USER_AGENT","nil");
375
+ blob_appendf(&b, "/%s/%z", zUserAgent, captcha_secret(0));
369376
sha1sum_blob(&b, &b);
370
- zCookie = mprintf("%s/%s:%s/anonymous", blob_buffer(&b), zNow, zIpAddr);
377
+ zCookie = mprintf("%s/%s/anonymous", blob_buffer(&b), zNow);
371378
blob_reset(&b);
372379
cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires);
373380
if( zCookieDest ){
374381
*zCookieDest = zCookie;
375382
}else{
@@ -1412,36 +1419,36 @@
14121419
}
14131420
}
14141421
if( zUser==0 ){
14151422
/* Invalid cookie */
14161423
}else if( fossil_strcmp(zUser, "anonymous")==0 ){
1417
- /* Cookies of the form "HASH/TIME:IPADDR/anonymous". The TIME must
1418
- ** not be too old and the sha1 hash of TIME:IPADDR/SECRET must match
1419
- ** HASH. SECRET is the "captcha-secret" value in the repository.
1424
+ /* Cookies of the form "HASH/TIME/anonymous". The TIME must
1425
+ ** not be more than ANONYMOUS_COOKIE_LIFESPAN seconds ago and
1426
+ ** the sha1 hash of TIME/USERAGENT/SECRET must match HASH. USERAGENT
1427
+ ** is the HTTP_USER_AGENT of the client and SECRET is the
1428
+ ** "captcha-secret" value in the repository. See tag-20250817a
1429
+ ** for the code the creates this cookie.
14201430
*/
14211431
double rTime = atof(zArg);
1422
- const char *zCookieIP;
1432
+ const char *zUserAgent = PD("HTTP_USER_AGENT","nil");
14231433
Blob b;
14241434
char *zSecret;
14251435
int n = 0;
14261436
1427
- zCookieIP = strchr(zArg,':');
1428
- if( zCookieIP && strcmp(zCookieIP+1,zIpAddr)!=0 ) zCookieIP = 0;
14291437
do{
1430
- if( zCookieIP==0 ) break;
14311438
blob_zero(&b);
14321439
zSecret = captcha_secret(n++);
14331440
if( zSecret==0 ) break;
1434
- blob_appendf(&b, "%s/%s", zArg, zSecret);
1441
+ blob_appendf(&b, "%s/%s/%s", zArg, zUserAgent, zSecret);
14351442
sha1sum_blob(&b, &b);
14361443
if( fossil_strcmp(zHash, blob_str(&b))==0 ){
14371444
uid = db_int(0,
14381445
"SELECT uid FROM user WHERE login='anonymous'"
14391446
" AND octet_length(cap)>0"
14401447
" AND octet_length(pw)>0"
1441
- " AND %.17g+0.0416667>julianday('now')",
1442
- rTime
1448
+ " AND %.17g>julianday('now')",
1449
+ rTime+ANONYMOUS_COOKIE_LIFESPAN/86400.0
14431450
);
14441451
}
14451452
}while( uid==0 );
14461453
blob_reset(&b);
14471454
}else{
14481455
--- src/login.c
+++ src/login.c
@@ -339,37 +339,44 @@
339 }else{
340 free(zCookie);
341 }
342 }
343
 
 
 
 
 
344 /* Sets a cookie for an anonymous user login, which looks like this:
345 **
346 ** HASH/TIME:IPADDR/anonymous
347 **
348 ** Where HASH is the sha1sum of TIME:IPADDR/SECRET, in which SECRET
349 ** is captcha-secret.
350 **
351 ** If zCookieDest is not NULL then the generated cookie is assigned to
352 ** *zCookieDest and the caller must eventually free() it.
353 **
354 ** If bSessionCookie is true, the cookie will be a session cookie.
 
 
355 */
356 void login_set_anon_cookie(char **zCookieDest, int bSessionCookie){
357 char *zNow; /* Current time (julian day number) */
358 char *zCookie; /* The login cookie */
359 const char *zIpAddr; /* IP Address */
360 const char *zCookieName; /* Name of the login cookie */
361 Blob b; /* Blob used during cookie construction */
362 int expires = bSessionCookie ? 0 : 3600; /* Valid for 60 minutes */
363 zCookieName = login_cookie_name();
364 zNow = db_text("0", "SELECT julianday('now')");
365 assert( zCookieName && zNow );
366 blob_init(&b, zNow, -1);
367 zIpAddr = PD("REMOTE_ADDR","nil");
368 blob_appendf(&b, ":%s/%z", zIpAddr, captcha_secret(0));
369 sha1sum_blob(&b, &b);
370 zCookie = mprintf("%s/%s:%s/anonymous", blob_buffer(&b), zNow, zIpAddr);
371 blob_reset(&b);
372 cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires);
373 if( zCookieDest ){
374 *zCookieDest = zCookie;
375 }else{
@@ -1412,36 +1419,36 @@
1412 }
1413 }
1414 if( zUser==0 ){
1415 /* Invalid cookie */
1416 }else if( fossil_strcmp(zUser, "anonymous")==0 ){
1417 /* Cookies of the form "HASH/TIME:IPADDR/anonymous". The TIME must
1418 ** not be too old and the sha1 hash of TIME:IPADDR/SECRET must match
1419 ** HASH. SECRET is the "captcha-secret" value in the repository.
 
 
 
1420 */
1421 double rTime = atof(zArg);
1422 const char *zCookieIP;
1423 Blob b;
1424 char *zSecret;
1425 int n = 0;
1426
1427 zCookieIP = strchr(zArg,':');
1428 if( zCookieIP && strcmp(zCookieIP+1,zIpAddr)!=0 ) zCookieIP = 0;
1429 do{
1430 if( zCookieIP==0 ) break;
1431 blob_zero(&b);
1432 zSecret = captcha_secret(n++);
1433 if( zSecret==0 ) break;
1434 blob_appendf(&b, "%s/%s", zArg, zSecret);
1435 sha1sum_blob(&b, &b);
1436 if( fossil_strcmp(zHash, blob_str(&b))==0 ){
1437 uid = db_int(0,
1438 "SELECT uid FROM user WHERE login='anonymous'"
1439 " AND octet_length(cap)>0"
1440 " AND octet_length(pw)>0"
1441 " AND %.17g+0.0416667>julianday('now')",
1442 rTime
1443 );
1444 }
1445 }while( uid==0 );
1446 blob_reset(&b);
1447 }else{
1448
--- src/login.c
+++ src/login.c
@@ -339,37 +339,44 @@
339 }else{
340 free(zCookie);
341 }
342 }
343
344 /*
345 ** Lifetime of an anoymous cookie, in seconds.
346 */
347 #define ANONYMOUS_COOKIE_LIFESPAN 28800 /* 28800 seconds == 8 hours */
348
349 /* Sets a cookie for an anonymous user login, which looks like this:
350 **
351 ** HASH/TIME/anonymous
352 **
353 ** Where HASH is the sha1sum of TIME/USERAGENT/SECRET, in which SECRET
354 ** is captcha-secret and USERAGENT is the HTTP_USER_AGENT value.
355 **
356 ** If zCookieDest is not NULL then the generated cookie is assigned to
357 ** *zCookieDest and the caller must eventually free() it.
358 **
359 ** If bSessionCookie is true, the cookie will be a session cookie.
360 **
361 ** Search for tag-20250817a to find the code that recognizes this cookie.
362 */
363 void login_set_anon_cookie(char **zCookieDest, int bSessionCookie){
364 char *zNow; /* Current time (julian day number) */
365 char *zCookie; /* The login cookie */
366 const char *zUserAgent; /* The user agent */
367 const char *zCookieName; /* Name of the login cookie */
368 Blob b; /* Blob used during cookie construction */
369 int expires = bSessionCookie ? 0 : ANONYMOUS_COOKIE_LIFESPAN;
370 zCookieName = login_cookie_name();
371 zNow = db_text("0", "SELECT julianday('now')");
372 assert( zCookieName && zNow );
373 blob_init(&b, zNow, -1);
374 zUserAgent = PD("HTTP_USER_AGENT","nil");
375 blob_appendf(&b, "/%s/%z", zUserAgent, captcha_secret(0));
376 sha1sum_blob(&b, &b);
377 zCookie = mprintf("%s/%s/anonymous", blob_buffer(&b), zNow);
378 blob_reset(&b);
379 cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires);
380 if( zCookieDest ){
381 *zCookieDest = zCookie;
382 }else{
@@ -1412,36 +1419,36 @@
1419 }
1420 }
1421 if( zUser==0 ){
1422 /* Invalid cookie */
1423 }else if( fossil_strcmp(zUser, "anonymous")==0 ){
1424 /* Cookies of the form "HASH/TIME/anonymous". The TIME must
1425 ** not be more than ANONYMOUS_COOKIE_LIFESPAN seconds ago and
1426 ** the sha1 hash of TIME/USERAGENT/SECRET must match HASH. USERAGENT
1427 ** is the HTTP_USER_AGENT of the client and SECRET is the
1428 ** "captcha-secret" value in the repository. See tag-20250817a
1429 ** for the code the creates this cookie.
1430 */
1431 double rTime = atof(zArg);
1432 const char *zUserAgent = PD("HTTP_USER_AGENT","nil");
1433 Blob b;
1434 char *zSecret;
1435 int n = 0;
1436
 
 
1437 do{
 
1438 blob_zero(&b);
1439 zSecret = captcha_secret(n++);
1440 if( zSecret==0 ) break;
1441 blob_appendf(&b, "%s/%s/%s", zArg, zUserAgent, zSecret);
1442 sha1sum_blob(&b, &b);
1443 if( fossil_strcmp(zHash, blob_str(&b))==0 ){
1444 uid = db_int(0,
1445 "SELECT uid FROM user WHERE login='anonymous'"
1446 " AND octet_length(cap)>0"
1447 " AND octet_length(pw)>0"
1448 " AND %.17g>julianday('now')",
1449 rTime+ANONYMOUS_COOKIE_LIFESPAN/86400.0
1450 );
1451 }
1452 }while( uid==0 );
1453 blob_reset(&b);
1454 }else{
1455
+11 -1
--- src/stash.c
+++ src/stash.c
@@ -562,10 +562,14 @@
562562
** > fossil stash gdiff ?STASHID? ?DIFF-OPTIONS?
563563
**
564564
** Show diffs of the current working directory and what that
565565
** directory would be if STASHID were applied. With gdiff,
566566
** gdiff-command is used instead of internal diff logic.
567
+**
568
+** > fossil stash rename STASHID NEW-NAME
569
+**
570
+** Change the description of the given STASHID entry to NEW-NAME.
567571
*/
568572
void stash_cmd(void){
569573
const char *zCmd;
570574
int nCmd;
571575
int stashid = 0;
@@ -771,11 +775,17 @@
771775
}
772776
diff_options(&DCfg, zCmd[0]=='g', 0);
773777
stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
774778
stash_diff(stashid, fBaseline, &DCfg);
775779
}else
776
- if( strncmp(zCmd, "help", nCmd)==0 ){
780
+ if( strncmp(zCmd, "rename", nCmd)==0 ){
781
+ if( g.argc!=5 ) usage("rename STASHID NAME");
782
+ stashid = stash_get_id(g.argv[3]);
783
+ db_multi_exec("UPDATE STASH SET COMMENT=%Q WHERE stashid=%d",
784
+ g.argv[4], stashid);
785
+ }
786
+ else if( strncmp(zCmd, "help", nCmd)==0 ){
777787
g.argv[1] = "help";
778788
g.argv[2] = "stash";
779789
g.argc = 3;
780790
help_cmd();
781791
}else
782792
--- src/stash.c
+++ src/stash.c
@@ -562,10 +562,14 @@
562 ** > fossil stash gdiff ?STASHID? ?DIFF-OPTIONS?
563 **
564 ** Show diffs of the current working directory and what that
565 ** directory would be if STASHID were applied. With gdiff,
566 ** gdiff-command is used instead of internal diff logic.
 
 
 
 
567 */
568 void stash_cmd(void){
569 const char *zCmd;
570 int nCmd;
571 int stashid = 0;
@@ -771,11 +775,17 @@
771 }
772 diff_options(&DCfg, zCmd[0]=='g', 0);
773 stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
774 stash_diff(stashid, fBaseline, &DCfg);
775 }else
776 if( strncmp(zCmd, "help", nCmd)==0 ){
 
 
 
 
 
 
777 g.argv[1] = "help";
778 g.argv[2] = "stash";
779 g.argc = 3;
780 help_cmd();
781 }else
782
--- src/stash.c
+++ src/stash.c
@@ -562,10 +562,14 @@
562 ** > fossil stash gdiff ?STASHID? ?DIFF-OPTIONS?
563 **
564 ** Show diffs of the current working directory and what that
565 ** directory would be if STASHID were applied. With gdiff,
566 ** gdiff-command is used instead of internal diff logic.
567 **
568 ** > fossil stash rename STASHID NEW-NAME
569 **
570 ** Change the description of the given STASHID entry to NEW-NAME.
571 */
572 void stash_cmd(void){
573 const char *zCmd;
574 int nCmd;
575 int stashid = 0;
@@ -771,11 +775,17 @@
775 }
776 diff_options(&DCfg, zCmd[0]=='g', 0);
777 stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
778 stash_diff(stashid, fBaseline, &DCfg);
779 }else
780 if( strncmp(zCmd, "rename", nCmd)==0 ){
781 if( g.argc!=5 ) usage("rename STASHID NAME");
782 stashid = stash_get_id(g.argv[3]);
783 db_multi_exec("UPDATE STASH SET COMMENT=%Q WHERE stashid=%d",
784 g.argv[4], stashid);
785 }
786 else if( strncmp(zCmd, "help", nCmd)==0 ){
787 g.argv[1] = "help";
788 g.argv[2] = "stash";
789 g.argc = 3;
790 help_cmd();
791 }else
792
--- www/changes.wiki
+++ www/changes.wiki
@@ -13,12 +13,13 @@
1313
<li> Enable the --editor option on the [/help?cmd=amend|fossil amend] command.
1414
<li> Require at least an anonymous login to access the /blame page and similar,
1515
to help prevent robots from soaking up excess CPU time on such pages.
1616
<li> When walking the filesystem looking for Fossil repositories, avoid descending
1717
into directories named "/proc".
18
- <ll> Reduce memory requirements for sending authenticated sync protocol
18
+ <li> Reduce memory requirements for sending authenticated sync protocol
1919
messages.
20
+ <li> Add the [/help?cmd=stash | stash rename] subcommand.
2021
</ol>
2122
2223
<h2 id='v2_26'>Changes for version 2.26 (2025-04-30)</h2><ol>
2324
<li>Enhancements to [/help?cmd=diff|fossil diff] and similar:
2425
<ol type="a">
2526
--- www/changes.wiki
+++ www/changes.wiki
@@ -13,12 +13,13 @@
13 <li> Enable the --editor option on the [/help?cmd=amend|fossil amend] command.
14 <li> Require at least an anonymous login to access the /blame page and similar,
15 to help prevent robots from soaking up excess CPU time on such pages.
16 <li> When walking the filesystem looking for Fossil repositories, avoid descending
17 into directories named "/proc".
18 <ll> Reduce memory requirements for sending authenticated sync protocol
19 messages.
 
20 </ol>
21
22 <h2 id='v2_26'>Changes for version 2.26 (2025-04-30)</h2><ol>
23 <li>Enhancements to [/help?cmd=diff|fossil diff] and similar:
24 <ol type="a">
25
--- www/changes.wiki
+++ www/changes.wiki
@@ -13,12 +13,13 @@
13 <li> Enable the --editor option on the [/help?cmd=amend|fossil amend] command.
14 <li> Require at least an anonymous login to access the /blame page and similar,
15 to help prevent robots from soaking up excess CPU time on such pages.
16 <li> When walking the filesystem looking for Fossil repositories, avoid descending
17 into directories named "/proc".
18 <li> Reduce memory requirements for sending authenticated sync protocol
19 messages.
20 <li> Add the [/help?cmd=stash | stash rename] subcommand.
21 </ol>
22
23 <h2 id='v2_26'>Changes for version 2.26 (2025-04-30)</h2><ol>
24 <li>Enhancements to [/help?cmd=diff|fossil diff] and similar:
25 <ol type="a">
26

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button