|
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 |
} |