Fossil SCM

fossil-scm / src / extcgi.c
Source Blame History 438 lines
ec56c69… drh 1 /*
ec56c69… drh 2 ** Copyright (c) 2019 D. Richard Hipp
ec56c69… drh 3 **
ec56c69… drh 4 ** This program is free software; you can redistribute it and/or
ec56c69… drh 5 ** modify it under the terms of the Simplified BSD License (also
ec56c69… drh 6 ** known as the "2-Clause License" or "FreeBSD License".)
ec56c69… drh 7 **
ec56c69… drh 8 ** This program is distributed in the hope that it will be useful,
ec56c69… drh 9 ** but without any warranty; without even the implied warranty of
ec56c69… drh 10 ** merchantability or fitness for a particular purpose.
ec56c69… drh 11 **
ec56c69… drh 12 ** Author contact information:
ec56c69… drh 13 ** [email protected]
ec56c69… drh 14 ** http://www.hwaci.com/drh/
ec56c69… drh 15 **
ec56c69… drh 16 *******************************************************************************
ec56c69… drh 17 **
ec56c69… drh 18 ** This file contains code to invoke CGI-based extensions to the
ec56c69… drh 19 ** Fossil server via the /ext webpage.
ec56c69… drh 20 **
ec56c69… drh 21 ** The /ext webpage acts like a recursive webserver, relaying the
ec56c69… drh 22 ** HTTP request to some other component - usually another CGI.
ec56c69… drh 23 **
ec56c69… drh 24 ** Before doing the relay, /ext examines the login cookie to see
ec56c69… drh 25 ** if the HTTP request is coming from a validated user, and if so
ec56c69… drh 26 ** /ext sets some additional environment variables that the extension
ec56c69… drh 27 ** CGI script can use. In this way, the extension CGI scripts use the
ec56c69… drh 28 ** same login system as the main repository, and appear to be
ec56c69… drh 29 ** an integrated part of the repository.
ec56c69… drh 30 */
ec56c69… drh 31 #include "config.h"
ec56c69… drh 32 #include "extcgi.h"
ec56c69… drh 33 #include <assert.h>
ec56c69… drh 34
ec56c69… drh 35 /*
ec56c69… drh 36 ** These are the environment variables that should be set for CGI
ec56c69… drh 37 ** extension programs:
ec56c69… drh 38 */
ec56c69… drh 39 static const char *azCgiEnv[] = {
ec56c69… drh 40 "AUTH_TYPE",
ec56c69… drh 41 "AUTH_CONTENT",
ec56c69… drh 42 "CONTENT_LENGTH",
ec56c69… drh 43 "CONTENT_TYPE",
ec56c69… drh 44 "DOCUMENT_ROOT",
ec56c69… drh 45 "FOSSIL_CAPABILITIES",
3f0ade5… drh 46 "FOSSIL_NONCE",
ec56c69… drh 47 "FOSSIL_REPOSITORY",
7b2b9d6… drh 48 "FOSSIL_URI",
ec56c69… drh 49 "FOSSIL_USER",
ec56c69… drh 50 "GATEWAY_INTERFACE",
f5fe221… drh 51 "HTTPS",
ec56c69… drh 52 "HTTP_ACCEPT",
ec56c69… drh 53 /* "HTTP_ACCEPT_ENCODING", // omitted from sub-cgi */
7e77e29… stephan 54 "HTTP_ACCEPT_LANGUAGE",
ec56c69… drh 55 "HTTP_COOKIE",
ec56c69… drh 56 "HTTP_HOST",
ec56c69… drh 57 "HTTP_IF_MODIFIED_SINCE",
ec56c69… drh 58 "HTTP_IF_NONE_MATCH",
ec56c69… drh 59 "HTTP_REFERER",
ec56c69… drh 60 "HTTP_USER_AGENT",
ec56c69… drh 61 "PATH_INFO",
ec56c69… drh 62 "QUERY_STRING",
ec56c69… drh 63 "REMOTE_ADDR",
ec56c69… drh 64 "REMOTE_USER",
ec56c69… drh 65 "REQUEST_METHOD",
282bdf0… drh 66 "REQUEST_SCHEME",
ec56c69… drh 67 "REQUEST_URI",
ec56c69… drh 68 "SCRIPT_DIRECTORY",
ec56c69… drh 69 "SCRIPT_FILENAME",
ec56c69… drh 70 "SCRIPT_NAME",
ec56c69… drh 71 "SERVER_NAME",
ec56c69… drh 72 "SERVER_PORT",
ec56c69… drh 73 "SERVER_PROTOCOL",
a9a1bd8… drh 74 "SERVER_SOFTWARE",
ec56c69… drh 75 };
a3bc655… drh 76
a3bc655… drh 77 /*
a3bc655… drh 78 ** Check a pathname to determine if it is acceptable for use as
a3bc655… drh 79 ** extension CGI. Some pathnames are excluded for security reasons.
a3bc655… drh 80 ** Return NULL on success or a static error string if there is
a3bc655… drh 81 ** a failure.
a3bc655… drh 82 */
a3bc655… drh 83 const char *ext_pathname_ok(const char *zName){
a3bc655… drh 84 int i;
a3bc655… drh 85 const char *zFailReason = 0;
a3bc655… drh 86 for(i=0; zName[i]; i++){
a3bc655… drh 87 char c = zName[i];
a3bc655… drh 88 if( (c=='.' || c=='-') && (i==0 || zName[i-1]=='/') ){
a3bc655… drh 89 zFailReason = "path element begins with '.' or '-'";
a3bc655… drh 90 break;
a3bc655… drh 91 }
a3bc655… drh 92 if( !fossil_isalnum(c) && c!='_' && c!='-' && c!='.' && c!='/' ){
a3bc655… drh 93 zFailReason = "illegal character in path";
a3bc655… drh 94 break;
a3bc655… drh 95 }
a3bc655… drh 96 }
a3bc655… drh 97 return zFailReason;
a3bc655… drh 98 }
a3bc655… drh 99
a3bc655… drh 100 /*
3ed3fa3… drh 101 ** The *pzPath input is a pathname obtained from mprintf().
3ed3fa3… drh 102 **
3ed3fa3… drh 103 ** If
3ed3fa3… drh 104 **
3ed3fa3… drh 105 ** (1) zPathname is the name of a directory, and
3ed3fa3… drh 106 ** (2) the name ends with "/", and
3ed3fa3… drh 107 ** (3) the directory contains a file named index.html, index.wiki,
3ed3fa3… drh 108 ** or index.md (in that order)
3ed3fa3… drh 109 **
3ed3fa3… drh 110 ** then replace the input with a revised name that includes the index.*
3ed3fa3… drh 111 ** file and return non-zero (true). If any condition is not met, return
3ed3fa3… drh 112 ** zero and leave the input pathname unchanged.
3ed3fa3… drh 113 */
3ed3fa3… drh 114 static int isDirWithIndexFile(char **pzPath){
3ed3fa3… drh 115 static const char *azIndexNames[] = {
3ed3fa3… drh 116 "index.html", "index.wiki", "index.md"
3ed3fa3… drh 117 };
3ed3fa3… drh 118 int i;
3ed3fa3… drh 119 if( file_isdir(*pzPath, ExtFILE)!=1 ) return 0;
3ed3fa3… drh 120 if( sqlite3_strglob("*/", *pzPath)!=0 ) return 0;
53db40e… drh 121 for(i=0; i<(int)(sizeof(azIndexNames)/sizeof(azIndexNames[0])); i++){
3ed3fa3… drh 122 char *zNew = mprintf("%s%s", *pzPath, azIndexNames[i]);
3ed3fa3… drh 123 if( file_isfile(zNew, ExtFILE) ){
3ed3fa3… drh 124 fossil_free(*pzPath);
3ed3fa3… drh 125 *pzPath = zNew;
3ed3fa3… drh 126 return 1;
3ed3fa3… drh 127 }
3ed3fa3… drh 128 fossil_free(zNew);
3ed3fa3… drh 129 }
3ed3fa3… drh 130 return 0;
3ed3fa3… drh 131 }
3ed3fa3… drh 132
3ed3fa3… drh 133 /*
ec56c69… drh 134 ** WEBPAGE: ext raw-content
ec56c69… drh 135 **
ec56c69… drh 136 ** Relay an HTTP request to secondary CGI after first checking the
ec56c69… drh 137 ** login credentials and setting auxiliary environment variables
ec56c69… drh 138 ** so that the secondary CGI can be aware of the credentials and
ec56c69… drh 139 ** capabilities of the Fossil user.
ec56c69… drh 140 **
ec56c69… drh 141 ** The /ext page is only functional if the "extroot: DIR" setting is
ec56c69… drh 142 ** found in the CGI script that launched Fossil, or if the "--extroot DIR"
2f78b2c… danield 143 ** flag is present when Fossil is launched using the "server", "ui", or
ec56c69… drh 144 ** "http" commands. DIR must be an absolute pathname (relative to the
ec56c69… drh 145 ** chroot jail) of the root of the file hierarchy that implements the CGI
ec56c69… drh 146 ** functionality. Executable files are CGI. Non-executable files are
ec56c69… drh 147 ** static content.
ec56c69… drh 148 **
ec56c69… drh 149 ** The path after the /ext is the path to the CGI script or static file
ec56c69… drh 150 ** relative to DIR. For security, this path may not contain characters
ec56c69… drh 151 ** other than ASCII letters or digits, ".", "-", "/", and "_". If the
ec56c69… drh 152 ** "." or "-" characters are present in the path then they may not follow
ec56c69… drh 153 ** a "/".
3ed3fa3… drh 154 **
3ed3fa3… drh 155 ** If the path after /ext ends with "/" and is the name of a directory then
3ed3fa3… drh 156 ** that directory is searched for files named "index.html", "index.wiki",
3ed3fa3… drh 157 ** and "index.md" (in that order) and if found, those filenames are
3ed3fa3… drh 158 ** appended to the path.
ec56c69… drh 159 */
ec56c69… drh 160 void ext_page(void){
ec56c69… drh 161 const char *zName = P("name"); /* Path information after /ext */
ec56c69… drh 162 char *zPath = 0; /* Complete path from extroot */
ec56c69… drh 163 int nRoot; /* Number of bytes in the extroot name */
ec56c69… drh 164 char *zScript = 0; /* Name of the CGI script */
ec56c69… drh 165 int nScript = 0; /* Bytes in the CGI script name */
ec56c69… drh 166 const char *zFailReason = "???";/* Reason for failure */
ec56c69… drh 167 int i; /* Loop counter */
ec56c69… drh 168 const char *zMime = 0; /* MIME type of the reply */
ec56c69… drh 169 int fdFromChild = -1; /* File descriptor for reading from child */
ec56c69… drh 170 FILE *toChild = 0; /* FILE for sending to child */
ec56c69… drh 171 FILE *fromChild = 0; /* FILE for reading from child */
ec56c69… drh 172 int pidChild = 0; /* Process id of the child */
ec56c69… drh 173 int rc; /* Reply code from subroutine call */
ec56c69… drh 174 int nContent = -1; /* Content length */
12c8cc7… drh 175 const char *zPathInfo; /* Original PATH_INFO value */
4231598… drh 176 char *zRestrictTag; /* Tag to restrict specific documents */
ec56c69… drh 177 Blob reply; /* The reply */
ec56c69… drh 178 char zLine[1000]; /* One line of the CGI reply */
a9a1bd8… drh 179 const char *zSrvSw; /* SERVER_SOFTWARE */
ec56c69… drh 180
12c8cc7… drh 181 zPathInfo = P("PATH_INFO");
ec56c69… drh 182 login_check_credentials();
ec56c69… drh 183 blob_init(&reply, 0, 0);
ec56c69… drh 184 if( g.zExtRoot==0 ){
ec56c69… drh 185 zFailReason = "extroot is not set";
ec56c69… drh 186 goto ext_not_found;
ec56c69… drh 187 }
ec56c69… drh 188 if( file_is_absolute_path(g.zExtRoot)==0 ){
ec56c69… drh 189 zFailReason = "extroot is a relative pathname";
ec56c69… drh 190 goto ext_not_found;
ec56c69… drh 191 }
ec56c69… drh 192 if( zName==0 ){
ec56c69… drh 193 zFailReason = "no path beyond /ext";
ec56c69… drh 194 goto ext_not_found;
ec56c69… drh 195 }
a3bc655… drh 196 zFailReason = ext_pathname_ok(zName);
a3bc655… drh 197 if( zFailReason ) goto ext_not_found;
a3bc655… drh 198 zFailReason = "???";
ec56c69… drh 199 if( file_isdir(g.zExtRoot,ExtFILE)!=1 ){
ec56c69… drh 200 zFailReason = "extroot is not a directory";
ec56c69… drh 201 goto ext_not_found;
ec56c69… drh 202 }
ec56c69… drh 203 zPath = mprintf("%s/%s", g.zExtRoot, zName);
ec56c69… drh 204 nRoot = (int)strlen(g.zExtRoot);
3ed3fa3… drh 205 if( file_isfile(zPath, ExtFILE) || isDirWithIndexFile(&zPath) ){
ec56c69… drh 206 nScript = (int)strlen(zPath);
ec56c69… drh 207 zScript = zPath;
ec56c69… drh 208 }else{
ec56c69… drh 209 for(i=nRoot+1; zPath[i]; i++){
ec56c69… drh 210 char c = zPath[i];
ec56c69… drh 211 if( c=='/' ){
ec56c69… drh 212 int isDir, isFile;
ec56c69… drh 213 zPath[i] = 0;
ec56c69… drh 214 isDir = file_isdir(zPath, ExtFILE);
ec56c69… drh 215 isFile = isDir==2 ? file_isfile(zPath, ExtFILE) : 0;
ec56c69… drh 216 zPath[i] = c;
ec56c69… drh 217 if( isDir==0 ){
ec56c69… drh 218 zFailReason = "path does not match any file or script";
ec56c69… drh 219 goto ext_not_found;
ec56c69… drh 220 }
ec56c69… drh 221 if( isFile!=0 ){
ec56c69… drh 222 zScript = mprintf("%.*s", i, zPath);
ec56c69… drh 223 nScript = i;
ec56c69… drh 224 break;
ec56c69… drh 225 }
ec56c69… drh 226 }
ec56c69… drh 227 }
ec56c69… drh 228 }
ec56c69… drh 229 if( nScript==0 ){
ec56c69… drh 230 zFailReason = "path does not match any file or script";
ec56c69… drh 231 goto ext_not_found;
ec56c69… drh 232 }
ec56c69… drh 233 assert( nScript>=nRoot+1 );
ec56c69… drh 234 style_set_current_page("ext/%s", &zScript[nRoot+1]);
4231598… drh 235 zRestrictTag = mprintf("ext/%s", &zScript[nRoot+1]);
4231598… drh 236 if( robot_restrict(zRestrictTag) ) return;
4231598… drh 237 fossil_free(zRestrictTag);
639b96b… drh 238 zMime = P("mimetype");
639b96b… drh 239 if( zMime==0 ) zMime = mimetype_from_name(zScript);
ec56c69… drh 240 if( zMime==0 ) zMime = "application/octet-stream";
ec56c69… drh 241 if( !file_isexe(zScript, ExtFILE) ){
ec56c69… drh 242 /* File is not executable. Must be a regular file. In that case,
ec56c69… drh 243 ** disallow extra path elements */
ec56c69… drh 244 if( zPath[nScript]!=0 ){
ec56c69… drh 245 zFailReason = "extra path elements after filename";
ec56c69… drh 246 goto ext_not_found;
ec56c69… drh 247 }
ec56c69… drh 248 blob_read_from_file(&reply, zScript, ExtFILE);
ec56c69… drh 249 document_render(&reply, zMime, zName, zName);
ec56c69… drh 250 return;
ec56c69… drh 251 }
ec56c69… drh 252
ec56c69… drh 253 /* If we reach this point, that means we are dealing with an executable
ec56c69… drh 254 ** file name zScript. Run that file as CGI.
ec56c69… drh 255 */
ec56c69… drh 256 cgi_replace_parameter("DOCUMENT_ROOT", g.zExtRoot);
ec56c69… drh 257 cgi_replace_parameter("SCRIPT_FILENAME", zScript);
ec56c69… drh 258 cgi_replace_parameter("SCRIPT_NAME",
ec56c69… drh 259 mprintf("%T/ext/%T",g.zTop,zScript+nRoot+1));
ec56c69… drh 260 cgi_replace_parameter("SCRIPT_DIRECTORY", file_dirname(zScript));
ec56c69… drh 261 cgi_replace_parameter("PATH_INFO", zName + strlen(zScript+nRoot+1));
ec56c69… drh 262 if( g.zLogin ){
ec56c69… drh 263 cgi_replace_parameter("REMOTE_USER", g.zLogin);
ec56c69… drh 264 cgi_set_parameter_nocopy("FOSSIL_USER", g.zLogin, 0);
ec56c69… drh 265 }
3f0ade5… drh 266 cgi_set_parameter_nocopy("FOSSIL_NONCE", style_nonce(), 0);
ec56c69… drh 267 cgi_set_parameter_nocopy("FOSSIL_REPOSITORY", g.zRepositoryName, 0);
7b2b9d6… drh 268 cgi_set_parameter_nocopy("FOSSIL_URI", g.zTop, 0);
ec56c69… drh 269 cgi_set_parameter_nocopy("FOSSIL_CAPABILITIES",
ec56c69… drh 270 db_text("","SELECT fullcap(cap) FROM user WHERE login=%Q",
ec56c69… drh 271 g.zLogin ? g.zLogin : "nobody"), 0);
a9a1bd8… drh 272 zSrvSw = P("SERVER_SOFTWARE");
a9a1bd8… drh 273 if( zSrvSw==0 ){
a9a1bd8… drh 274 zSrvSw = get_version();
a9a1bd8… drh 275 }else{
a9a1bd8… drh 276 char *z = mprintf("fossil version %s", get_version());
a9a1bd8… drh 277 if( strncmp(zSrvSw,z,strlen(z)-4)!=0 ){
a9a1bd8… drh 278 zSrvSw = mprintf("%z, %s", z, zSrvSw);
a9a1bd8… drh 279 }
a9a1bd8… drh 280 }
a9a1bd8… drh 281 cgi_replace_parameter("SERVER_SOFTWARE", zSrvSw);
ec56c69… drh 282 cgi_replace_parameter("GATEWAY_INTERFACE","CGI/1.0");
53db40e… drh 283 for(i=0; i<(int)(sizeof(azCgiEnv)/sizeof(azCgiEnv[0])); i++){
c6b4d80… drh 284 (void)P(azCgiEnv[i]);
c6b4d80… drh 285 }
ed63bdd… drh 286 fossil_clearenv();
53db40e… drh 287 for(i=0; i<(int)(sizeof(azCgiEnv)/sizeof(azCgiEnv[0])); i++){
ec56c69… drh 288 const char *zVal = P(azCgiEnv[i]);
ec56c69… drh 289 if( zVal ) fossil_setenv(azCgiEnv[i], zVal);
ec56c69… drh 290 }
ec56c69… drh 291 fossil_setenv("HTTP_ACCEPT_ENCODING","");
ec56c69… drh 292 rc = popen2(zScript, &fdFromChild, &toChild, &pidChild, 1);
ec56c69… drh 293 if( rc ){
ec56c69… drh 294 zFailReason = "cannot exec CGI child process";
ec56c69… drh 295 goto ext_not_found;
ec56c69… drh 296 }
ec56c69… drh 297 fromChild = fdopen(fdFromChild, "rb");
ec56c69… drh 298 if( fromChild==0 ){
ec56c69… drh 299 zFailReason = "cannot open FILE to read from CGI child process";
ec56c69… drh 300 goto ext_not_found;
ec56c69… drh 301 }
ec56c69… drh 302 if( blob_size(&g.cgiIn)>0 ){
ec56c69… drh 303 size_t nSent, toSend;
ec56c69… drh 304 unsigned char *data = (unsigned char*)blob_buffer(&g.cgiIn);
ec56c69… drh 305 toSend = (size_t)blob_size(&g.cgiIn);
ec56c69… drh 306 do{
ec56c69… drh 307 nSent = fwrite(data, 1, toSend, toChild);
ec56c69… drh 308 if( nSent<=0 ){
ec56c69… drh 309 zFailReason = "unable to send all content to the CGI child process";
ec56c69… drh 310 goto ext_not_found;
ec56c69… drh 311 }
ec56c69… drh 312 toSend -= nSent;
ec56c69… drh 313 data += nSent;
ec56c69… drh 314 }while( toSend>0 );
ec56c69… drh 315 fflush(toChild);
ec56c69… drh 316 }
ec56c69… drh 317 if( g.perm.Debug && P("fossil-ext-debug")!=0 ){
ec56c69… drh 318 /* For users with Debug privilege, if the "fossil-ext-debug" query
ec56c69… drh 319 ** parameter exists, then show raw output from the CGI */
ec56c69… drh 320 zMime = "text/plain";
ec56c69… drh 321 }else{
ec56c69… drh 322 while( fgets(zLine,sizeof(zLine),fromChild) ){
ec56c69… drh 323 for(i=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){}
ec56c69… drh 324 zLine[i] = 0;
ec56c69… drh 325 if( i==0 ) break;
ec56c69… drh 326 if( fossil_strnicmp(zLine,"Location:",9)==0 ){
ec56c69… drh 327 fclose(fromChild);
ec56c69… drh 328 fclose(toChild);
ec56c69… drh 329 cgi_redirect(&zLine[10]); /* no return */
ec56c69… drh 330 }else if( fossil_strnicmp(zLine,"Status:",7)==0 ){
ec56c69… drh 331 int j;
ec56c69… drh 332 for(i=7; fossil_isspace(zLine[i]); i++){}
ec56c69… drh 333 for(j=i; fossil_isdigit(zLine[j]); j++){}
ec56c69… drh 334 while( fossil_isspace(zLine[j]) ){ j++; }
ec56c69… drh 335 cgi_set_status(atoi(&zLine[i]), &zLine[j]);
ec56c69… drh 336 }else if( fossil_strnicmp(zLine,"Content-Length:",15)==0 ){
ec56c69… drh 337 nContent = atoi(&zLine[15]);
ec56c69… drh 338 }else if( fossil_strnicmp(zLine,"Content-Type:",13)==0 ){
ec56c69… drh 339 int j;
ec56c69… drh 340 for(i=13; fossil_isspace(zLine[i]); i++){}
ec56c69… drh 341 for(j=i; zLine[j] && zLine[j]!=';'; j++){}
ec56c69… drh 342 zMime = mprintf("%.*s", j-i, &zLine[i]);
1cff8d3… drh 343 }else{
1cff8d3… drh 344 cgi_append_header(zLine);
1cff8d3… drh 345 cgi_append_header("\r\n");
ec56c69… drh 346 }
ec56c69… drh 347 }
ec56c69… drh 348 }
ec56c69… drh 349 blob_read_from_channel(&reply, fromChild, nContent);
ec56c69… drh 350 zFailReason = 0; /* Indicate success */
ec56c69… drh 351
ec56c69… drh 352 ext_not_found:
ec56c69… drh 353 fossil_free(zPath);
ec56c69… drh 354 if( fromChild ){
ec56c69… drh 355 fclose(fromChild);
ec56c69… drh 356 }else if( fdFromChild>2 ){
ec56c69… drh 357 close(fdFromChild);
ec56c69… drh 358 }
ec56c69… drh 359 if( toChild ) fclose(toChild);
ec56c69… drh 360 if( zFailReason==0 ){
ec56c69… drh 361 document_render(&reply, zMime, zName, zName);
ec56c69… drh 362 }else{
ec56c69… drh 363 cgi_set_status(404, "Not Found");
ec56c69… drh 364 @ <h1>Not Found</h1>
12c8cc7… drh 365 @ <p>Page not found: %h(zPathInfo)</p>
ec56c69… drh 366 if( g.perm.Debug ){
ec56c69… drh 367 @ <p>Reason for failure: %h(zFailReason)</p>
ec56c69… drh 368 }
ec56c69… drh 369 }
ec56c69… drh 370 return;
a3bc655… drh 371 }
a3bc655… drh 372
a3bc655… drh 373 /*
a3bc655… drh 374 ** Create a temporary SFILE table and fill it with one entry for each file
a3bc655… drh 375 ** in the extension document root directory (g.zExtRoot). The SFILE table
a3bc655… drh 376 ** looks like this:
a3bc655… drh 377 **
a3bc655… drh 378 ** CREATE TEMP TABLE sfile(
a3bc655… drh 379 ** pathname TEXT PRIMARY KEY,
a3bc655… drh 380 ** isexe BOOLEAN
a3bc655… drh 381 ** ) WITHOUT ROWID;
a3bc655… drh 382 */
a3bc655… drh 383 void ext_files(void){
a3bc655… drh 384 Blob base;
a3bc655… drh 385 db_multi_exec(
a3bc655… drh 386 "CREATE TEMP TABLE sfile(\n"
a3bc655… drh 387 " pathname TEXT PRIMARY KEY,\n"
a3bc655… drh 388 " isexe BOOLEAN\n"
a3bc655… drh 389 ") WITHOUT ROWID;"
a3bc655… drh 390 );
a3bc655… drh 391 blob_init(&base, g.zExtRoot, -1);
a3bc655… drh 392 vfile_scan(&base, blob_size(&base),
a3bc655… drh 393 SCAN_ALL|SCAN_ISEXE,
a3bc655… drh 394 0, 0, ExtFILE);
a3bc655… drh 395 blob_zero(&base);
a3bc655… drh 396 }
a3bc655… drh 397
a3bc655… drh 398 /*
a3bc655… drh 399 ** WEBPAGE: extfilelist
a3bc655… drh 400 **
a3bc655… drh 401 ** List all files in the extension CGI document root and its subfolders.
a3bc655… drh 402 */
a3bc655… drh 403 void ext_filelist_page(void){
a3bc655… drh 404 Stmt q;
a3bc655… drh 405 login_check_credentials();
a3bc655… drh 406 if( !g.perm.Admin ){
a3bc655… drh 407 login_needed(0);
a3bc655… drh 408 return;
a3bc655… drh 409 }
a3bc655… drh 410 ext_files();
112c713… drh 411 style_set_current_feature("extcgi");
a3bc655… drh 412 style_header("CGI Extension Filelist");
a3bc655… drh 413 @ <table border="0" cellspacing="0" cellpadding="3">
a3bc655… drh 414 @ <tbody>
a3bc655… drh 415 db_prepare(&q, "SELECT pathname, isexe FROM sfile"
a3bc655… drh 416 " ORDER BY pathname");
a3bc655… drh 417 while( db_step(&q)==SQLITE_ROW ){
a3bc655… drh 418 const char *zName = db_column_text(&q,0);
a3bc655… drh 419 int isExe = db_column_int(&q,1);
a3bc655… drh 420 @ <tr>
a3bc655… drh 421 if( ext_pathname_ok(zName)!=0 ){
a3bc655… drh 422 @ <td><span style="opacity:0.5;">%h(zName)</span></td>
a3bc655… drh 423 @ <td>data file</td>
a3bc655… drh 424 }else{
a3bc655… drh 425 @ <td><a href="%R/ext/%h(zName)">%h(zName)</a></td>
a3bc655… drh 426 if( isExe ){
a3bc655… drh 427 @ <td>CGI</td>
a3bc655… drh 428 }else{
a3bc655… drh 429 @ <td>static content</td>
a3bc655… drh 430 }
a3bc655… drh 431 }
a3bc655… drh 432 @ </tr>
a3bc655… drh 433 }
a3bc655… drh 434 db_finalize(&q);
a3bc655… drh 435 @ </tbody>
a3bc655… drh 436 @ </table>
112c713… drh 437 style_finish_page();
ec56c69… drh 438 }

Keyboard Shortcuts

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