Fossil SCM

Merge updates from trunk.

mistachkin 2019-12-13 19:06 verifyLogin merge
Commit 9abccbaa50c81115e6e18019cfba7902e598b9c855924cbd2d8763eca50c06d0
+78 -9
--- src/cgi.c
+++ src/cgi.c
@@ -350,11 +350,11 @@
350350
fwrite(blob_buffer(&cgiContent[i]), 1, size, g.httpOut);
351351
}
352352
}
353353
}
354354
fflush(g.httpOut);
355
- CGIDEBUG(("DONE\n"));
355
+ CGIDEBUG(("-------- END cgi ---------\n"));
356356
357357
/* After the webpage has been sent, do any useful background
358358
** processing.
359359
*/
360360
g.cgiOutput = 2;
@@ -1167,10 +1167,14 @@
11671167
}
11681168
j++;
11691169
}
11701170
nUsedQP = j;
11711171
}
1172
+
1173
+ /* Invoking with a NULL zName is just a way to cause the parameters
1174
+ ** to be sorted. So go ahead and bail out in that case */
1175
+ if( zName==0 || zName[0]==0 ) return 0;
11721176
11731177
/* Do a binary search for a matching query parameter */
11741178
lo = 0;
11751179
hi = nUsedQP-1;
11761180
while( lo<=hi ){
@@ -1189,11 +1193,11 @@
11891193
/* If no match is found and the name begins with an upper-case
11901194
** letter, then check to see if there is an environment variable
11911195
** with the given name. Handle environment variables with empty values
11921196
** the same as non-existent environment variables.
11931197
*/
1194
- if( zName && fossil_isupper(zName[0]) ){
1198
+ if( fossil_isupper(zName[0]) ){
11951199
const char *zValue = fossil_getenv(zName);
11961200
if( zValue && zValue[0] ){
11971201
cgi_set_parameter_nocopy(zName, zValue, 0);
11981202
CGIDEBUG(("env-match [%s] = [%s]\n", zName, zValue));
11991203
return zValue;
@@ -1314,28 +1318,73 @@
13141318
va_end(ap);
13151319
return 1;
13161320
}
13171321
13181322
/*
1319
-** Print all query parameters on standard output. Format the
1320
-** parameters as HTML. This is used for testing and debugging.
1323
+** Load all relevant environment variables into the parameter buffer.
1324
+** Invoke this routine prior to calling cgi_print_all() in order to see
1325
+** the full CGI environment. This routine intended for debugging purposes
1326
+** only.
1327
+*/
1328
+void cgi_load_environment(void){
1329
+ /* The following is a list of environment variables that Fossil considers
1330
+ ** to be "relevant". */
1331
+ static const char *const azCgiVars[] = {
1332
+ "COMSPEC", "DOCUMENT_ROOT", "GATEWAY_INTERFACE", "SCGI",
1333
+ "HTTP_ACCEPT", "HTTP_ACCEPT_CHARSET", "HTTP_ACCEPT_ENCODING",
1334
+ "HTTP_ACCEPT_LANGUAGE", "HTTP_AUTHENICATION",
1335
+ "HTTP_CONNECTION", "HTTP_HOST",
1336
+ "HTTP_IF_NONE_MATCH", "HTTP_IF_MODIFIED_SINCE",
1337
+ "HTTP_USER_AGENT", "HTTP_REFERER", "PATH_INFO", "PATH_TRANSLATED",
1338
+ "QUERY_STRING", "REMOTE_ADDR", "REMOTE_PORT",
1339
+ "REMOTE_USER", "REQUEST_METHOD",
1340
+ "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL",
1341
+ "HOME", "FOSSIL_HOME", "USERNAME", "USER", "FOSSIL_USER",
1342
+ "SQLITE_TMPDIR", "TMPDIR",
1343
+ "TEMP", "TMP", "FOSSIL_VFS",
1344
+ "FOSSIL_FORCE_TICKET_MODERATION", "FOSSIL_FORCE_WIKI_MODERATION",
1345
+ "FOSSIL_TCL_PATH", "TH1_DELETE_INTERP", "TH1_ENABLE_DOCS",
1346
+ "TH1_ENABLE_HOOKS", "TH1_ENABLE_TCL", "REMOTE_HOST",
1347
+ };
1348
+ int i;
1349
+ for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]);
1350
+}
1351
+
1352
+/*
1353
+** Print all query parameters on standard output.
1354
+** This is used for testing and debugging.
13211355
**
13221356
** Omit the values of the cookies unless showAll is true.
1357
+**
1358
+** The eDest parameter determines where the output is shown:
1359
+**
1360
+** eDest==0: Rendering as HTML into the CGI reply
1361
+** eDest==1: Written to stderr
1362
+** eDest==2: Written to cgi_debug
13231363
*/
1324
-void cgi_print_all(int showAll, int onConsole){
1364
+void cgi_print_all(int showAll, unsigned int eDest){
13251365
int i;
13261366
cgi_parameter("",""); /* Force the parameters into sorted order */
13271367
for(i=0; i<nUsedQP; i++){
13281368
const char *zName = aParamQP[i].zName;
13291369
if( !showAll ){
13301370
if( fossil_stricmp("HTTP_COOKIE",zName)==0 ) continue;
13311371
if( fossil_strnicmp("fossil-",zName,7)==0 ) continue;
13321372
}
1333
- if( onConsole ){
1334
- fossil_trace("%s = %s\n", zName, aParamQP[i].zValue);
1335
- }else{
1336
- cgi_printf("%h = %h <br />\n", zName, aParamQP[i].zValue);
1373
+ switch( eDest ){
1374
+ case 0: {
1375
+ cgi_printf("%h = %h <br />\n", zName, aParamQP[i].zValue);
1376
+ break;
1377
+ }
1378
+ case 1: {
1379
+ fossil_trace("%s = %s\n", zName, aParamQP[i].zValue);
1380
+ break;
1381
+ }
1382
+ case 2: {
1383
+ cgi_debug("%s = %s\n", zName, aParamQP[i].zValue);
1384
+ break;
1385
+ }
13371386
}
13381387
}
13391388
}
13401389
13411390
/*
@@ -2076,10 +2125,30 @@
20762125
return mprintf("%s, %d %s %02d %02d:%02d:%02d +0000",
20772126
azDays[pTm->tm_wday], pTm->tm_mday, azMonths[pTm->tm_mon],
20782127
pTm->tm_year+1900, pTm->tm_hour, pTm->tm_min, pTm->tm_sec);
20792128
}
20802129
}
2130
+
2131
+/*
2132
+** Returns an ISO8601-formatted time string suitable for debugging
2133
+** purposes.
2134
+**
2135
+** The value returned is always a string obtained from mprintf() and must
2136
+** be freed using fossil_free() to avoid a memory leak.
2137
+*/
2138
+char *cgi_iso8601_datestamp(void){
2139
+ struct tm *pTm;
2140
+ time_t now = time(0);
2141
+ pTm = gmtime(&now);
2142
+ if( pTm==0 ){
2143
+ return mprintf("");
2144
+ }else{
2145
+ return mprintf("%04d-%02d-%02d %02d:%02d:%02d",
2146
+ pTm->tm_year+1900, pTm->tm_mon, pTm->tm_mday,
2147
+ pTm->tm_hour, pTm->tm_min, pTm->tm_sec);
2148
+ }
2149
+}
20812150
20822151
/*
20832152
** Parse an RFC822-formatted timestamp as we'd expect from HTTP and return
20842153
** a Unix epoch time. <= zero is returned on failure.
20852154
**
20862155
--- src/cgi.c
+++ src/cgi.c
@@ -350,11 +350,11 @@
350 fwrite(blob_buffer(&cgiContent[i]), 1, size, g.httpOut);
351 }
352 }
353 }
354 fflush(g.httpOut);
355 CGIDEBUG(("DONE\n"));
356
357 /* After the webpage has been sent, do any useful background
358 ** processing.
359 */
360 g.cgiOutput = 2;
@@ -1167,10 +1167,14 @@
1167 }
1168 j++;
1169 }
1170 nUsedQP = j;
1171 }
 
 
 
 
1172
1173 /* Do a binary search for a matching query parameter */
1174 lo = 0;
1175 hi = nUsedQP-1;
1176 while( lo<=hi ){
@@ -1189,11 +1193,11 @@
1189 /* If no match is found and the name begins with an upper-case
1190 ** letter, then check to see if there is an environment variable
1191 ** with the given name. Handle environment variables with empty values
1192 ** the same as non-existent environment variables.
1193 */
1194 if( zName && fossil_isupper(zName[0]) ){
1195 const char *zValue = fossil_getenv(zName);
1196 if( zValue && zValue[0] ){
1197 cgi_set_parameter_nocopy(zName, zValue, 0);
1198 CGIDEBUG(("env-match [%s] = [%s]\n", zName, zValue));
1199 return zValue;
@@ -1314,28 +1318,73 @@
1314 va_end(ap);
1315 return 1;
1316 }
1317
1318 /*
1319 ** Print all query parameters on standard output. Format the
1320 ** parameters as HTML. This is used for testing and debugging.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1321 **
1322 ** Omit the values of the cookies unless showAll is true.
 
 
 
 
 
 
1323 */
1324 void cgi_print_all(int showAll, int onConsole){
1325 int i;
1326 cgi_parameter("",""); /* Force the parameters into sorted order */
1327 for(i=0; i<nUsedQP; i++){
1328 const char *zName = aParamQP[i].zName;
1329 if( !showAll ){
1330 if( fossil_stricmp("HTTP_COOKIE",zName)==0 ) continue;
1331 if( fossil_strnicmp("fossil-",zName,7)==0 ) continue;
1332 }
1333 if( onConsole ){
1334 fossil_trace("%s = %s\n", zName, aParamQP[i].zValue);
1335 }else{
1336 cgi_printf("%h = %h <br />\n", zName, aParamQP[i].zValue);
 
 
 
 
 
 
 
 
 
1337 }
1338 }
1339 }
1340
1341 /*
@@ -2076,10 +2125,30 @@
2076 return mprintf("%s, %d %s %02d %02d:%02d:%02d +0000",
2077 azDays[pTm->tm_wday], pTm->tm_mday, azMonths[pTm->tm_mon],
2078 pTm->tm_year+1900, pTm->tm_hour, pTm->tm_min, pTm->tm_sec);
2079 }
2080 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2081
2082 /*
2083 ** Parse an RFC822-formatted timestamp as we'd expect from HTTP and return
2084 ** a Unix epoch time. <= zero is returned on failure.
2085 **
2086
--- src/cgi.c
+++ src/cgi.c
@@ -350,11 +350,11 @@
350 fwrite(blob_buffer(&cgiContent[i]), 1, size, g.httpOut);
351 }
352 }
353 }
354 fflush(g.httpOut);
355 CGIDEBUG(("-------- END cgi ---------\n"));
356
357 /* After the webpage has been sent, do any useful background
358 ** processing.
359 */
360 g.cgiOutput = 2;
@@ -1167,10 +1167,14 @@
1167 }
1168 j++;
1169 }
1170 nUsedQP = j;
1171 }
1172
1173 /* Invoking with a NULL zName is just a way to cause the parameters
1174 ** to be sorted. So go ahead and bail out in that case */
1175 if( zName==0 || zName[0]==0 ) return 0;
1176
1177 /* Do a binary search for a matching query parameter */
1178 lo = 0;
1179 hi = nUsedQP-1;
1180 while( lo<=hi ){
@@ -1189,11 +1193,11 @@
1193 /* If no match is found and the name begins with an upper-case
1194 ** letter, then check to see if there is an environment variable
1195 ** with the given name. Handle environment variables with empty values
1196 ** the same as non-existent environment variables.
1197 */
1198 if( fossil_isupper(zName[0]) ){
1199 const char *zValue = fossil_getenv(zName);
1200 if( zValue && zValue[0] ){
1201 cgi_set_parameter_nocopy(zName, zValue, 0);
1202 CGIDEBUG(("env-match [%s] = [%s]\n", zName, zValue));
1203 return zValue;
@@ -1314,28 +1318,73 @@
1318 va_end(ap);
1319 return 1;
1320 }
1321
1322 /*
1323 ** Load all relevant environment variables into the parameter buffer.
1324 ** Invoke this routine prior to calling cgi_print_all() in order to see
1325 ** the full CGI environment. This routine intended for debugging purposes
1326 ** only.
1327 */
1328 void cgi_load_environment(void){
1329 /* The following is a list of environment variables that Fossil considers
1330 ** to be "relevant". */
1331 static const char *const azCgiVars[] = {
1332 "COMSPEC", "DOCUMENT_ROOT", "GATEWAY_INTERFACE", "SCGI",
1333 "HTTP_ACCEPT", "HTTP_ACCEPT_CHARSET", "HTTP_ACCEPT_ENCODING",
1334 "HTTP_ACCEPT_LANGUAGE", "HTTP_AUTHENICATION",
1335 "HTTP_CONNECTION", "HTTP_HOST",
1336 "HTTP_IF_NONE_MATCH", "HTTP_IF_MODIFIED_SINCE",
1337 "HTTP_USER_AGENT", "HTTP_REFERER", "PATH_INFO", "PATH_TRANSLATED",
1338 "QUERY_STRING", "REMOTE_ADDR", "REMOTE_PORT",
1339 "REMOTE_USER", "REQUEST_METHOD",
1340 "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL",
1341 "HOME", "FOSSIL_HOME", "USERNAME", "USER", "FOSSIL_USER",
1342 "SQLITE_TMPDIR", "TMPDIR",
1343 "TEMP", "TMP", "FOSSIL_VFS",
1344 "FOSSIL_FORCE_TICKET_MODERATION", "FOSSIL_FORCE_WIKI_MODERATION",
1345 "FOSSIL_TCL_PATH", "TH1_DELETE_INTERP", "TH1_ENABLE_DOCS",
1346 "TH1_ENABLE_HOOKS", "TH1_ENABLE_TCL", "REMOTE_HOST",
1347 };
1348 int i;
1349 for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]);
1350 }
1351
1352 /*
1353 ** Print all query parameters on standard output.
1354 ** This is used for testing and debugging.
1355 **
1356 ** Omit the values of the cookies unless showAll is true.
1357 **
1358 ** The eDest parameter determines where the output is shown:
1359 **
1360 ** eDest==0: Rendering as HTML into the CGI reply
1361 ** eDest==1: Written to stderr
1362 ** eDest==2: Written to cgi_debug
1363 */
1364 void cgi_print_all(int showAll, unsigned int eDest){
1365 int i;
1366 cgi_parameter("",""); /* Force the parameters into sorted order */
1367 for(i=0; i<nUsedQP; i++){
1368 const char *zName = aParamQP[i].zName;
1369 if( !showAll ){
1370 if( fossil_stricmp("HTTP_COOKIE",zName)==0 ) continue;
1371 if( fossil_strnicmp("fossil-",zName,7)==0 ) continue;
1372 }
1373 switch( eDest ){
1374 case 0: {
1375 cgi_printf("%h = %h <br />\n", zName, aParamQP[i].zValue);
1376 break;
1377 }
1378 case 1: {
1379 fossil_trace("%s = %s\n", zName, aParamQP[i].zValue);
1380 break;
1381 }
1382 case 2: {
1383 cgi_debug("%s = %s\n", zName, aParamQP[i].zValue);
1384 break;
1385 }
1386 }
1387 }
1388 }
1389
1390 /*
@@ -2076,10 +2125,30 @@
2125 return mprintf("%s, %d %s %02d %02d:%02d:%02d +0000",
2126 azDays[pTm->tm_wday], pTm->tm_mday, azMonths[pTm->tm_mon],
2127 pTm->tm_year+1900, pTm->tm_hour, pTm->tm_min, pTm->tm_sec);
2128 }
2129 }
2130
2131 /*
2132 ** Returns an ISO8601-formatted time string suitable for debugging
2133 ** purposes.
2134 **
2135 ** The value returned is always a string obtained from mprintf() and must
2136 ** be freed using fossil_free() to avoid a memory leak.
2137 */
2138 char *cgi_iso8601_datestamp(void){
2139 struct tm *pTm;
2140 time_t now = time(0);
2141 pTm = gmtime(&now);
2142 if( pTm==0 ){
2143 return mprintf("");
2144 }else{
2145 return mprintf("%04d-%02d-%02d %02d:%02d:%02d",
2146 pTm->tm_year+1900, pTm->tm_mon, pTm->tm_mday,
2147 pTm->tm_hour, pTm->tm_min, pTm->tm_sec);
2148 }
2149 }
2150
2151 /*
2152 ** Parse an RFC822-formatted timestamp as we'd expect from HTTP and return
2153 ** a Unix epoch time. <= zero is returned on failure.
2154 **
2155
+2 -1
--- src/doc.c
+++ src/doc.c
@@ -39,11 +39,11 @@
3939
4040
/* A table of mimetypes based on file content prefixes
4141
*/
4242
static const struct {
4343
const char *zPrefix; /* The file prefix */
44
- int size; /* Length of the prefix */
44
+ const int size; /* Length of the prefix */
4545
const char *zMimetype; /* The corresponding mimetype */
4646
} aMime[] = {
4747
{ "GIF87a", 6, "image/gif" },
4848
{ "GIF89a", 6, "image/gif" },
4949
{ "\211PNG\r\n\032\n", 8, "image/png" },
@@ -269,10 +269,11 @@
269269
{ "viv", 3, "video/vnd.vivo" },
270270
{ "vivo", 4, "video/vnd.vivo" },
271271
{ "vrml", 4, "model/vrml" },
272272
{ "wav", 3, "audio/x-wav" },
273273
{ "wax", 3, "audio/x-ms-wax" },
274
+ { "webp", 4, "image/webp" },
274275
{ "wiki", 4, "text/x-fossil-wiki" },
275276
{ "wma", 3, "audio/x-ms-wma" },
276277
{ "wmv", 3, "video/x-ms-wmv" },
277278
{ "wmx", 3, "video/x-ms-wmx" },
278279
{ "wrl", 3, "model/vrml" },
279280
--- src/doc.c
+++ src/doc.c
@@ -39,11 +39,11 @@
39
40 /* A table of mimetypes based on file content prefixes
41 */
42 static const struct {
43 const char *zPrefix; /* The file prefix */
44 int size; /* Length of the prefix */
45 const char *zMimetype; /* The corresponding mimetype */
46 } aMime[] = {
47 { "GIF87a", 6, "image/gif" },
48 { "GIF89a", 6, "image/gif" },
49 { "\211PNG\r\n\032\n", 8, "image/png" },
@@ -269,10 +269,11 @@
269 { "viv", 3, "video/vnd.vivo" },
270 { "vivo", 4, "video/vnd.vivo" },
271 { "vrml", 4, "model/vrml" },
272 { "wav", 3, "audio/x-wav" },
273 { "wax", 3, "audio/x-ms-wax" },
 
274 { "wiki", 4, "text/x-fossil-wiki" },
275 { "wma", 3, "audio/x-ms-wma" },
276 { "wmv", 3, "video/x-ms-wmv" },
277 { "wmx", 3, "video/x-ms-wmx" },
278 { "wrl", 3, "model/vrml" },
279
--- src/doc.c
+++ src/doc.c
@@ -39,11 +39,11 @@
39
40 /* A table of mimetypes based on file content prefixes
41 */
42 static const struct {
43 const char *zPrefix; /* The file prefix */
44 const int size; /* Length of the prefix */
45 const char *zMimetype; /* The corresponding mimetype */
46 } aMime[] = {
47 { "GIF87a", 6, "image/gif" },
48 { "GIF89a", 6, "image/gif" },
49 { "\211PNG\r\n\032\n", 8, "image/png" },
@@ -269,10 +269,11 @@
269 { "viv", 3, "video/vnd.vivo" },
270 { "vivo", 4, "video/vnd.vivo" },
271 { "vrml", 4, "model/vrml" },
272 { "wav", 3, "audio/x-wav" },
273 { "wax", 3, "audio/x-ms-wax" },
274 { "webp", 4, "image/webp" },
275 { "wiki", 4, "text/x-fossil-wiki" },
276 { "wma", 3, "audio/x-ms-wma" },
277 { "wmv", 3, "video/x-ms-wmv" },
278 { "wmx", 3, "video/x-ms-wmx" },
279 { "wrl", 3, "model/vrml" },
280
+39 -1
--- src/extcgi.c
+++ src/extcgi.c
@@ -98,10 +98,43 @@
9898
break;
9999
}
100100
}
101101
return zFailReason;
102102
}
103
+
104
+/*
105
+** The *pzPath input is a pathname obtained from mprintf().
106
+**
107
+** If
108
+**
109
+** (1) zPathname is the name of a directory, and
110
+** (2) the name ends with "/", and
111
+** (3) the directory contains a file named index.html, index.wiki,
112
+** or index.md (in that order)
113
+**
114
+** then replace the input with a revised name that includes the index.*
115
+** file and return non-zero (true). If any condition is not met, return
116
+** zero and leave the input pathname unchanged.
117
+*/
118
+static int isDirWithIndexFile(char **pzPath){
119
+ static const char *azIndexNames[] = {
120
+ "index.html", "index.wiki", "index.md"
121
+ };
122
+ int i;
123
+ if( file_isdir(*pzPath, ExtFILE)!=1 ) return 0;
124
+ if( sqlite3_strglob("*/", *pzPath)!=0 ) return 0;
125
+ for(i=0; i<sizeof(azIndexNames)/sizeof(azIndexNames[0]); i++){
126
+ char *zNew = mprintf("%s%s", *pzPath, azIndexNames[i]);
127
+ if( file_isfile(zNew, ExtFILE) ){
128
+ fossil_free(*pzPath);
129
+ *pzPath = zNew;
130
+ return 1;
131
+ }
132
+ fossil_free(zNew);
133
+ }
134
+ return 0;
135
+}
103136
104137
/*
105138
** WEBPAGE: ext raw-content
106139
**
107140
** Relay an HTTP request to secondary CGI after first checking the
@@ -120,10 +153,15 @@
120153
** The path after the /ext is the path to the CGI script or static file
121154
** relative to DIR. For security, this path may not contain characters
122155
** other than ASCII letters or digits, ".", "-", "/", and "_". If the
123156
** "." or "-" characters are present in the path then they may not follow
124157
** a "/".
158
+**
159
+** If the path after /ext ends with "/" and is the name of a directory then
160
+** that directory is searched for files named "index.html", "index.wiki",
161
+** and "index.md" (in that order) and if found, those filenames are
162
+** appended to the path.
125163
*/
126164
void ext_page(void){
127165
const char *zName = P("name"); /* Path information after /ext */
128166
char *zPath = 0; /* Complete path from extroot */
129167
int nRoot; /* Number of bytes in the extroot name */
@@ -164,11 +202,11 @@
164202
zFailReason = "extroot is not a directory";
165203
goto ext_not_found;
166204
}
167205
zPath = mprintf("%s/%s", g.zExtRoot, zName);
168206
nRoot = (int)strlen(g.zExtRoot);
169
- if( file_isfile(zPath, ExtFILE) ){
207
+ if( file_isfile(zPath, ExtFILE) || isDirWithIndexFile(&zPath) ){
170208
nScript = (int)strlen(zPath);
171209
zScript = zPath;
172210
}else{
173211
for(i=nRoot+1; zPath[i]; i++){
174212
char c = zPath[i];
175213
--- src/extcgi.c
+++ src/extcgi.c
@@ -98,10 +98,43 @@
98 break;
99 }
100 }
101 return zFailReason;
102 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
104 /*
105 ** WEBPAGE: ext raw-content
106 **
107 ** Relay an HTTP request to secondary CGI after first checking the
@@ -120,10 +153,15 @@
120 ** The path after the /ext is the path to the CGI script or static file
121 ** relative to DIR. For security, this path may not contain characters
122 ** other than ASCII letters or digits, ".", "-", "/", and "_". If the
123 ** "." or "-" characters are present in the path then they may not follow
124 ** a "/".
 
 
 
 
 
125 */
126 void ext_page(void){
127 const char *zName = P("name"); /* Path information after /ext */
128 char *zPath = 0; /* Complete path from extroot */
129 int nRoot; /* Number of bytes in the extroot name */
@@ -164,11 +202,11 @@
164 zFailReason = "extroot is not a directory";
165 goto ext_not_found;
166 }
167 zPath = mprintf("%s/%s", g.zExtRoot, zName);
168 nRoot = (int)strlen(g.zExtRoot);
169 if( file_isfile(zPath, ExtFILE) ){
170 nScript = (int)strlen(zPath);
171 zScript = zPath;
172 }else{
173 for(i=nRoot+1; zPath[i]; i++){
174 char c = zPath[i];
175
--- src/extcgi.c
+++ src/extcgi.c
@@ -98,10 +98,43 @@
98 break;
99 }
100 }
101 return zFailReason;
102 }
103
104 /*
105 ** The *pzPath input is a pathname obtained from mprintf().
106 **
107 ** If
108 **
109 ** (1) zPathname is the name of a directory, and
110 ** (2) the name ends with "/", and
111 ** (3) the directory contains a file named index.html, index.wiki,
112 ** or index.md (in that order)
113 **
114 ** then replace the input with a revised name that includes the index.*
115 ** file and return non-zero (true). If any condition is not met, return
116 ** zero and leave the input pathname unchanged.
117 */
118 static int isDirWithIndexFile(char **pzPath){
119 static const char *azIndexNames[] = {
120 "index.html", "index.wiki", "index.md"
121 };
122 int i;
123 if( file_isdir(*pzPath, ExtFILE)!=1 ) return 0;
124 if( sqlite3_strglob("*/", *pzPath)!=0 ) return 0;
125 for(i=0; i<sizeof(azIndexNames)/sizeof(azIndexNames[0]); i++){
126 char *zNew = mprintf("%s%s", *pzPath, azIndexNames[i]);
127 if( file_isfile(zNew, ExtFILE) ){
128 fossil_free(*pzPath);
129 *pzPath = zNew;
130 return 1;
131 }
132 fossil_free(zNew);
133 }
134 return 0;
135 }
136
137 /*
138 ** WEBPAGE: ext raw-content
139 **
140 ** Relay an HTTP request to secondary CGI after first checking the
@@ -120,10 +153,15 @@
153 ** The path after the /ext is the path to the CGI script or static file
154 ** relative to DIR. For security, this path may not contain characters
155 ** other than ASCII letters or digits, ".", "-", "/", and "_". If the
156 ** "." or "-" characters are present in the path then they may not follow
157 ** a "/".
158 **
159 ** If the path after /ext ends with "/" and is the name of a directory then
160 ** that directory is searched for files named "index.html", "index.wiki",
161 ** and "index.md" (in that order) and if found, those filenames are
162 ** appended to the path.
163 */
164 void ext_page(void){
165 const char *zName = P("name"); /* Path information after /ext */
166 char *zPath = 0; /* Complete path from extroot */
167 int nRoot; /* Number of bytes in the extroot name */
@@ -164,11 +202,11 @@
202 zFailReason = "extroot is not a directory";
203 goto ext_not_found;
204 }
205 zPath = mprintf("%s/%s", g.zExtRoot, zName);
206 nRoot = (int)strlen(g.zExtRoot);
207 if( file_isfile(zPath, ExtFILE) || isDirWithIndexFile(&zPath) ){
208 nScript = (int)strlen(zPath);
209 zScript = zPath;
210 }else{
211 for(i=nRoot+1; zPath[i]; i++){
212 char c = zPath[i];
213
+16 -11
--- src/main.c
+++ src/main.c
@@ -2042,11 +2042,11 @@
20422042
g.httpIn = stdin;
20432043
fossil_binary_mode(g.httpOut);
20442044
fossil_binary_mode(g.httpIn);
20452045
g.cgiOutput = 1;
20462046
fossil_set_timeout(FOSSIL_DEFAULT_TIMEOUT);
2047
- /* Read and parse the CGI control file. */
2047
+ /* Find the name of the CGI control file */
20482048
if( g.argc==3 && fossil_strcmp(g.argv[1],"cgi")==0 ){
20492049
zFile = g.argv[2];
20502050
}else if( g.argc>=2 ){
20512051
zFile = g.argv[1];
20522052
}else{
@@ -2147,20 +2147,10 @@
21472147
fossil_setenv(blob_str(&value), blob_str(&value2));
21482148
blob_reset(&value);
21492149
blob_reset(&value2);
21502150
continue;
21512151
}
2152
- if( blob_eq(&key, "debug:") && blob_token(&line, &value) ){
2153
- /* debug: FILENAME
2154
- **
2155
- ** Causes output from cgi_debug() and CGIDEBUG(()) calls to go
2156
- ** into FILENAME.
2157
- */
2158
- g.fDebug = fossil_fopen(blob_str(&value), "ab");
2159
- blob_reset(&value);
2160
- continue;
2161
- }
21622152
if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){
21632153
/* errorlog: FILENAME
21642154
**
21652155
** Causes messages from warnings, errors, and panics to be appended
21662156
** to FILENAME.
@@ -2206,10 +2196,25 @@
22062196
** this directive is a silent no-op.
22072197
*/
22082198
skin_use_alternative(blob_str(&value));
22092199
blob_reset(&value);
22102200
continue;
2201
+ }
2202
+ if( blob_eq(&key, "cgi-debug:") && blob_token(&line, &value) ){
2203
+ /* cgi-debug: FILENAME
2204
+ **
2205
+ ** Causes output from cgi_debug() and CGIDEBUG(()) calls to go
2206
+ ** into FILENAME. Useful for debugging CGI configuration problems.
2207
+ */
2208
+ char *zNow = cgi_iso8601_datestamp();
2209
+ cgi_load_environment();
2210
+ g.fDebug = fossil_fopen(blob_str(&value), "ab");
2211
+ blob_reset(&value);
2212
+ cgi_debug("-------- BEGIN cgi at %s --------\n", zNow);
2213
+ fossil_free(zNow);
2214
+ cgi_print_all(1,2);
2215
+ continue;
22112216
}
22122217
}
22132218
blob_reset(&config);
22142219
if( g.db==0 && g.zRepositoryName==0 && nRedirect==0 ){
22152220
cgi_panic("Unable to find or open the project repository");
22162221
--- src/main.c
+++ src/main.c
@@ -2042,11 +2042,11 @@
2042 g.httpIn = stdin;
2043 fossil_binary_mode(g.httpOut);
2044 fossil_binary_mode(g.httpIn);
2045 g.cgiOutput = 1;
2046 fossil_set_timeout(FOSSIL_DEFAULT_TIMEOUT);
2047 /* Read and parse the CGI control file. */
2048 if( g.argc==3 && fossil_strcmp(g.argv[1],"cgi")==0 ){
2049 zFile = g.argv[2];
2050 }else if( g.argc>=2 ){
2051 zFile = g.argv[1];
2052 }else{
@@ -2147,20 +2147,10 @@
2147 fossil_setenv(blob_str(&value), blob_str(&value2));
2148 blob_reset(&value);
2149 blob_reset(&value2);
2150 continue;
2151 }
2152 if( blob_eq(&key, "debug:") && blob_token(&line, &value) ){
2153 /* debug: FILENAME
2154 **
2155 ** Causes output from cgi_debug() and CGIDEBUG(()) calls to go
2156 ** into FILENAME.
2157 */
2158 g.fDebug = fossil_fopen(blob_str(&value), "ab");
2159 blob_reset(&value);
2160 continue;
2161 }
2162 if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){
2163 /* errorlog: FILENAME
2164 **
2165 ** Causes messages from warnings, errors, and panics to be appended
2166 ** to FILENAME.
@@ -2206,10 +2196,25 @@
2206 ** this directive is a silent no-op.
2207 */
2208 skin_use_alternative(blob_str(&value));
2209 blob_reset(&value);
2210 continue;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2211 }
2212 }
2213 blob_reset(&config);
2214 if( g.db==0 && g.zRepositoryName==0 && nRedirect==0 ){
2215 cgi_panic("Unable to find or open the project repository");
2216
--- src/main.c
+++ src/main.c
@@ -2042,11 +2042,11 @@
2042 g.httpIn = stdin;
2043 fossil_binary_mode(g.httpOut);
2044 fossil_binary_mode(g.httpIn);
2045 g.cgiOutput = 1;
2046 fossil_set_timeout(FOSSIL_DEFAULT_TIMEOUT);
2047 /* Find the name of the CGI control file */
2048 if( g.argc==3 && fossil_strcmp(g.argv[1],"cgi")==0 ){
2049 zFile = g.argv[2];
2050 }else if( g.argc>=2 ){
2051 zFile = g.argv[1];
2052 }else{
@@ -2147,20 +2147,10 @@
2147 fossil_setenv(blob_str(&value), blob_str(&value2));
2148 blob_reset(&value);
2149 blob_reset(&value2);
2150 continue;
2151 }
 
 
 
 
 
 
 
 
 
 
2152 if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){
2153 /* errorlog: FILENAME
2154 **
2155 ** Causes messages from warnings, errors, and panics to be appended
2156 ** to FILENAME.
@@ -2206,10 +2196,25 @@
2196 ** this directive is a silent no-op.
2197 */
2198 skin_use_alternative(blob_str(&value));
2199 blob_reset(&value);
2200 continue;
2201 }
2202 if( blob_eq(&key, "cgi-debug:") && blob_token(&line, &value) ){
2203 /* cgi-debug: FILENAME
2204 **
2205 ** Causes output from cgi_debug() and CGIDEBUG(()) calls to go
2206 ** into FILENAME. Useful for debugging CGI configuration problems.
2207 */
2208 char *zNow = cgi_iso8601_datestamp();
2209 cgi_load_environment();
2210 g.fDebug = fossil_fopen(blob_str(&value), "ab");
2211 blob_reset(&value);
2212 cgi_debug("-------- BEGIN cgi at %s --------\n", zNow);
2213 fossil_free(zNow);
2214 cgi_print_all(1,2);
2215 continue;
2216 }
2217 }
2218 blob_reset(&config);
2219 if( g.db==0 && g.zRepositoryName==0 && nRedirect==0 ){
2220 cgi_panic("Unable to find or open the project repository");
2221
+9 -1
--- src/smtp.c
+++ src/smtp.c
@@ -598,36 +598,44 @@
598598
** to the list of users TO. FROM is the sender of the email.
599599
**
600600
** Options:
601601
**
602602
** --direct Go directly to the TO domain. Bypass MX lookup
603
+** --relayhost R Use R as relay host directly for delivery.
603604
** --port N Use TCP port N instead of 25
604605
** --trace Show the SMTP conversation on the console
605606
*/
606607
void test_smtp_send(void){
607608
SmtpSession *p;
608609
const char *zFrom;
609610
int nTo;
610611
const char *zToDomain;
611612
const char *zFromDomain;
613
+ const char *zRelay;
612614
const char **azTo;
613615
int smtpPort = 25;
614616
const char *zPort;
615617
Blob body;
616618
u32 smtpFlags = SMTP_PORT;
617619
if( find_option("trace",0,0)!=0 ) smtpFlags |= SMTP_TRACE_STDOUT;
618620
if( find_option("direct",0,0)!=0 ) smtpFlags |= SMTP_DIRECT;
619621
zPort = find_option("port",0,1);
620622
if( zPort ) smtpPort = atoi(zPort);
623
+ zRelay = find_option("relayhost",0,1);
621624
verify_all_options();
622625
if( g.argc<5 ) usage("EMAIL FROM TO ...");
623626
blob_read_from_file(&body, g.argv[2], ExtFILE);
624627
zFrom = g.argv[3];
625628
nTo = g.argc-4;
626629
azTo = (const char**)g.argv+4;
627630
zFromDomain = domainOfAddr(zFrom);
628
- zToDomain = domainOfAddr(azTo[0]);
631
+ if( zRelay!=0 && zRelay[0]!= 0) {
632
+ smtpFlags |= SMTP_DIRECT;
633
+ zToDomain = zRelay;
634
+ }else{
635
+ zToDomain = domainOfAddr(azTo[0]);
636
+ }
629637
p = smtp_session_new(zFromDomain, zToDomain, smtpFlags, smtpPort);
630638
if( p->zErr ){
631639
fossil_fatal("%s", p->zErr);
632640
}
633641
fossil_print("Connection to \"%s\"\n", p->zHostname);
634642
--- src/smtp.c
+++ src/smtp.c
@@ -598,36 +598,44 @@
598 ** to the list of users TO. FROM is the sender of the email.
599 **
600 ** Options:
601 **
602 ** --direct Go directly to the TO domain. Bypass MX lookup
 
603 ** --port N Use TCP port N instead of 25
604 ** --trace Show the SMTP conversation on the console
605 */
606 void test_smtp_send(void){
607 SmtpSession *p;
608 const char *zFrom;
609 int nTo;
610 const char *zToDomain;
611 const char *zFromDomain;
 
612 const char **azTo;
613 int smtpPort = 25;
614 const char *zPort;
615 Blob body;
616 u32 smtpFlags = SMTP_PORT;
617 if( find_option("trace",0,0)!=0 ) smtpFlags |= SMTP_TRACE_STDOUT;
618 if( find_option("direct",0,0)!=0 ) smtpFlags |= SMTP_DIRECT;
619 zPort = find_option("port",0,1);
620 if( zPort ) smtpPort = atoi(zPort);
 
621 verify_all_options();
622 if( g.argc<5 ) usage("EMAIL FROM TO ...");
623 blob_read_from_file(&body, g.argv[2], ExtFILE);
624 zFrom = g.argv[3];
625 nTo = g.argc-4;
626 azTo = (const char**)g.argv+4;
627 zFromDomain = domainOfAddr(zFrom);
628 zToDomain = domainOfAddr(azTo[0]);
 
 
 
 
 
629 p = smtp_session_new(zFromDomain, zToDomain, smtpFlags, smtpPort);
630 if( p->zErr ){
631 fossil_fatal("%s", p->zErr);
632 }
633 fossil_print("Connection to \"%s\"\n", p->zHostname);
634
--- src/smtp.c
+++ src/smtp.c
@@ -598,36 +598,44 @@
598 ** to the list of users TO. FROM is the sender of the email.
599 **
600 ** Options:
601 **
602 ** --direct Go directly to the TO domain. Bypass MX lookup
603 ** --relayhost R Use R as relay host directly for delivery.
604 ** --port N Use TCP port N instead of 25
605 ** --trace Show the SMTP conversation on the console
606 */
607 void test_smtp_send(void){
608 SmtpSession *p;
609 const char *zFrom;
610 int nTo;
611 const char *zToDomain;
612 const char *zFromDomain;
613 const char *zRelay;
614 const char **azTo;
615 int smtpPort = 25;
616 const char *zPort;
617 Blob body;
618 u32 smtpFlags = SMTP_PORT;
619 if( find_option("trace",0,0)!=0 ) smtpFlags |= SMTP_TRACE_STDOUT;
620 if( find_option("direct",0,0)!=0 ) smtpFlags |= SMTP_DIRECT;
621 zPort = find_option("port",0,1);
622 if( zPort ) smtpPort = atoi(zPort);
623 zRelay = find_option("relayhost",0,1);
624 verify_all_options();
625 if( g.argc<5 ) usage("EMAIL FROM TO ...");
626 blob_read_from_file(&body, g.argv[2], ExtFILE);
627 zFrom = g.argv[3];
628 nTo = g.argc-4;
629 azTo = (const char**)g.argv+4;
630 zFromDomain = domainOfAddr(zFrom);
631 if( zRelay!=0 && zRelay[0]!= 0) {
632 smtpFlags |= SMTP_DIRECT;
633 zToDomain = zRelay;
634 }else{
635 zToDomain = domainOfAddr(azTo[0]);
636 }
637 p = smtp_session_new(zFromDomain, zToDomain, smtpFlags, smtpPort);
638 if( p->zErr ){
639 fossil_fatal("%s", p->zErr);
640 }
641 fossil_print("Connection to \"%s\"\n", p->zHostname);
642
+1 -19
--- src/style.c
+++ src/style.c
@@ -1139,38 +1139,20 @@
11391139
** the error message is shown.
11401140
**
11411141
** If zFormat is an empty string, then this is the /test_env page.
11421142
*/
11431143
void webpage_error(const char *zFormat, ...){
1144
- int i;
11451144
int showAll;
11461145
char *zErr = 0;
11471146
int isAuth = 0;
11481147
char zCap[100];
1149
- static const char *const azCgiVars[] = {
1150
- "COMSPEC", "DOCUMENT_ROOT", "GATEWAY_INTERFACE", "SCGI",
1151
- "HTTP_ACCEPT", "HTTP_ACCEPT_CHARSET", "HTTP_ACCEPT_ENCODING",
1152
- "HTTP_ACCEPT_LANGUAGE", "HTTP_AUTHENICATION",
1153
- "HTTP_CONNECTION", "HTTP_HOST",
1154
- "HTTP_IF_NONE_MATCH", "HTTP_IF_MODIFIED_SINCE",
1155
- "HTTP_USER_AGENT", "HTTP_REFERER", "PATH_INFO", "PATH_TRANSLATED",
1156
- "QUERY_STRING", "REMOTE_ADDR", "REMOTE_PORT",
1157
- "REMOTE_USER", "REQUEST_METHOD",
1158
- "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL",
1159
- "HOME", "FOSSIL_HOME", "USERNAME", "USER", "FOSSIL_USER",
1160
- "SQLITE_TMPDIR", "TMPDIR",
1161
- "TEMP", "TMP", "FOSSIL_VFS",
1162
- "FOSSIL_FORCE_TICKET_MODERATION", "FOSSIL_FORCE_WIKI_MODERATION",
1163
- "FOSSIL_TCL_PATH", "TH1_DELETE_INTERP", "TH1_ENABLE_DOCS",
1164
- "TH1_ENABLE_HOOKS", "TH1_ENABLE_TCL", "REMOTE_HOST",
1165
- };
11661148
11671149
login_check_credentials();
11681150
if( g.perm.Admin || g.perm.Setup || db_get_boolean("test_env_enable",0) ){
11691151
isAuth = 1;
11701152
}
1171
- for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]);
1153
+ cgi_load_environment();
11721154
if( zFormat[0] ){
11731155
va_list ap;
11741156
va_start(ap, zFormat);
11751157
zErr = vmprintf(zFormat, ap);
11761158
va_end(ap);
11771159
--- src/style.c
+++ src/style.c
@@ -1139,38 +1139,20 @@
1139 ** the error message is shown.
1140 **
1141 ** If zFormat is an empty string, then this is the /test_env page.
1142 */
1143 void webpage_error(const char *zFormat, ...){
1144 int i;
1145 int showAll;
1146 char *zErr = 0;
1147 int isAuth = 0;
1148 char zCap[100];
1149 static const char *const azCgiVars[] = {
1150 "COMSPEC", "DOCUMENT_ROOT", "GATEWAY_INTERFACE", "SCGI",
1151 "HTTP_ACCEPT", "HTTP_ACCEPT_CHARSET", "HTTP_ACCEPT_ENCODING",
1152 "HTTP_ACCEPT_LANGUAGE", "HTTP_AUTHENICATION",
1153 "HTTP_CONNECTION", "HTTP_HOST",
1154 "HTTP_IF_NONE_MATCH", "HTTP_IF_MODIFIED_SINCE",
1155 "HTTP_USER_AGENT", "HTTP_REFERER", "PATH_INFO", "PATH_TRANSLATED",
1156 "QUERY_STRING", "REMOTE_ADDR", "REMOTE_PORT",
1157 "REMOTE_USER", "REQUEST_METHOD",
1158 "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL",
1159 "HOME", "FOSSIL_HOME", "USERNAME", "USER", "FOSSIL_USER",
1160 "SQLITE_TMPDIR", "TMPDIR",
1161 "TEMP", "TMP", "FOSSIL_VFS",
1162 "FOSSIL_FORCE_TICKET_MODERATION", "FOSSIL_FORCE_WIKI_MODERATION",
1163 "FOSSIL_TCL_PATH", "TH1_DELETE_INTERP", "TH1_ENABLE_DOCS",
1164 "TH1_ENABLE_HOOKS", "TH1_ENABLE_TCL", "REMOTE_HOST",
1165 };
1166
1167 login_check_credentials();
1168 if( g.perm.Admin || g.perm.Setup || db_get_boolean("test_env_enable",0) ){
1169 isAuth = 1;
1170 }
1171 for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]);
1172 if( zFormat[0] ){
1173 va_list ap;
1174 va_start(ap, zFormat);
1175 zErr = vmprintf(zFormat, ap);
1176 va_end(ap);
1177
--- src/style.c
+++ src/style.c
@@ -1139,38 +1139,20 @@
1139 ** the error message is shown.
1140 **
1141 ** If zFormat is an empty string, then this is the /test_env page.
1142 */
1143 void webpage_error(const char *zFormat, ...){
 
1144 int showAll;
1145 char *zErr = 0;
1146 int isAuth = 0;
1147 char zCap[100];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1148
1149 login_check_credentials();
1150 if( g.perm.Admin || g.perm.Setup || db_get_boolean("test_env_enable",0) ){
1151 isAuth = 1;
1152 }
1153 cgi_load_environment();
1154 if( zFormat[0] ){
1155 va_list ap;
1156 va_start(ap, zFormat);
1157 zErr = vmprintf(zFormat, ap);
1158 va_end(ap);
1159
--- www/cgi.wiki
+++ www/cgi.wiki
@@ -136,5 +136,10 @@
136136
This parameter can be repeated as many times as necessary.
137137
138138
<h2 id="HOME">HOME: <i>PATH</i></h2>
139139
140140
This parameter is a short-hand for "<b>setenv HOME <i>PATH</i></b>".
141
+
142
+<h2 id="cgi-debug">cgi-debug: <i>FILE</i></h2>
143
+
144
+Cause CGI-related debugging information to be appended in <i>FILE</i>. Use
145
+this to help debug CGI problems.
141146
--- www/cgi.wiki
+++ www/cgi.wiki
@@ -136,5 +136,10 @@
136 This parameter can be repeated as many times as necessary.
137
138 <h2 id="HOME">HOME: <i>PATH</i></h2>
139
140 This parameter is a short-hand for "<b>setenv HOME <i>PATH</i></b>".
 
 
 
 
 
141
--- www/cgi.wiki
+++ www/cgi.wiki
@@ -136,5 +136,10 @@
136 This parameter can be repeated as many times as necessary.
137
138 <h2 id="HOME">HOME: <i>PATH</i></h2>
139
140 This parameter is a short-hand for "<b>setenv HOME <i>PATH</i></b>".
141
142 <h2 id="cgi-debug">cgi-debug: <i>FILE</i></h2>
143
144 Cause CGI-related debugging information to be appended in <i>FILE</i>. Use
145 this to help debug CGI problems.
146
--- www/serverext.wiki
+++ www/serverext.wiki
@@ -201,11 +201,11 @@
201201
202202
<blockquote><verbatim>
203203
<script nonce='$FOSSIL_NONCE'>...</script>
204204
</verbatim></blockquote>
205205
206
-Except, of course, the $FOSSIL_NONCE is replace by the value of the
206
+Except, of course, the $FOSSIL_NONCE is replaced by the value of the
207207
FOSSIL_NONCE environment variable.
208208
209209
If the HTTP request includes content (for example if this is a POST request)
210210
then the CONTENT_LENGTH value will be positive and the data for the content
211211
will be readable on standard input.
212212
--- www/serverext.wiki
+++ www/serverext.wiki
@@ -201,11 +201,11 @@
201
202 <blockquote><verbatim>
203 <script nonce='$FOSSIL_NONCE'>...</script>
204 </verbatim></blockquote>
205
206 Except, of course, the $FOSSIL_NONCE is replace by the value of the
207 FOSSIL_NONCE environment variable.
208
209 If the HTTP request includes content (for example if this is a POST request)
210 then the CONTENT_LENGTH value will be positive and the data for the content
211 will be readable on standard input.
212
--- www/serverext.wiki
+++ www/serverext.wiki
@@ -201,11 +201,11 @@
201
202 <blockquote><verbatim>
203 <script nonce='$FOSSIL_NONCE'>...</script>
204 </verbatim></blockquote>
205
206 Except, of course, the $FOSSIL_NONCE is replaced by the value of the
207 FOSSIL_NONCE environment variable.
208
209 If the HTTP request includes content (for example if this is a POST request)
210 then the CONTENT_LENGTH value will be positive and the data for the content
211 will be readable on standard input.
212

Keyboard Shortcuts

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