/src/httpd/server/util_script.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* Licensed to the Apache Software Foundation (ASF) under one or more |
2 | | * contributor license agreements. See the NOTICE file distributed with |
3 | | * this work for additional information regarding copyright ownership. |
4 | | * The ASF licenses this file to You under the Apache License, Version 2.0 |
5 | | * (the "License"); you may not use this file except in compliance with |
6 | | * the License. You may obtain a copy of the License at |
7 | | * |
8 | | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | | * |
10 | | * Unless required by applicable law or agreed to in writing, software |
11 | | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | | * See the License for the specific language governing permissions and |
14 | | * limitations under the License. |
15 | | */ |
16 | | |
17 | | #include "apr.h" |
18 | | #include "apr_lib.h" |
19 | | #include "apr_strings.h" |
20 | | |
21 | | #define APR_WANT_STRFUNC |
22 | | #include "apr_want.h" |
23 | | |
24 | | #if APR_HAVE_STDLIB_H |
25 | | #include <stdlib.h> |
26 | | #endif |
27 | | |
28 | | #include "ap_config.h" |
29 | | #include "httpd.h" |
30 | | #include "http_config.h" |
31 | | #include "http_main.h" |
32 | | #include "http_log.h" |
33 | | #include "http_core.h" |
34 | | #include "http_protocol.h" |
35 | | #include "http_request.h" /* for sub_req_lookup_uri() */ |
36 | | #include "util_script.h" |
37 | | #include "apr_date.h" /* For apr_date_parse_http() */ |
38 | | #include "util_ebcdic.h" |
39 | | |
40 | | #ifdef OS2 |
41 | | #define INCL_DOS |
42 | | #include <os2.h> |
43 | | #endif |
44 | | |
45 | | /* |
46 | | * Various utility functions which are common to a whole lot of |
47 | | * script-type extensions mechanisms, and might as well be gathered |
48 | | * in one place (if only to avoid creating inter-module dependencies |
49 | | * where there don't have to be). |
50 | | */ |
51 | | |
52 | | /* we know core's module_index is 0 */ |
53 | | #undef APLOG_MODULE_INDEX |
54 | 0 | #define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX |
55 | | |
56 | | static char *http2env(request_rec *r, const char *w) |
57 | 0 | { |
58 | 0 | char *res = (char *)apr_palloc(r->pool, sizeof("HTTP_") + strlen(w)); |
59 | 0 | char *cp = res; |
60 | 0 | char c; |
61 | |
|
62 | 0 | *cp++ = 'H'; |
63 | 0 | *cp++ = 'T'; |
64 | 0 | *cp++ = 'T'; |
65 | 0 | *cp++ = 'P'; |
66 | 0 | *cp++ = '_'; |
67 | |
|
68 | 0 | while ((c = *w++) != 0) { |
69 | 0 | if (apr_isalnum(c)) { |
70 | 0 | *cp++ = apr_toupper(c); |
71 | 0 | } |
72 | 0 | else if (c == '-') { |
73 | 0 | *cp++ = '_'; |
74 | 0 | } |
75 | 0 | else { |
76 | 0 | if (APLOGrtrace1(r)) |
77 | 0 | ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, |
78 | 0 | "Not exporting header with invalid name as envvar: %s", |
79 | 0 | ap_escape_logitem(r->pool, w)); |
80 | 0 | return NULL; |
81 | 0 | } |
82 | 0 | } |
83 | 0 | *cp = 0; |
84 | |
|
85 | 0 | return res; |
86 | 0 | } |
87 | | |
88 | | static void add_unless_null(apr_table_t *table, const char *name, const char *val) |
89 | 0 | { |
90 | 0 | if (name && val) { |
91 | 0 | apr_table_addn(table, name, val); |
92 | 0 | } |
93 | 0 | } |
94 | | |
95 | | /* Sets variable @name in table @dest from r->subprocess_env if |
96 | | * available, else from the environment, else from @fallback if |
97 | | * non-NULL. */ |
98 | | static void env2env(apr_table_t *dest, request_rec *r, |
99 | | const char *name, const char *fallback) |
100 | 0 | { |
101 | 0 | const char *val; |
102 | |
|
103 | 0 | val = apr_table_get(r->subprocess_env, name); |
104 | 0 | if (!val) |
105 | 0 | val = apr_pstrdup(r->pool, getenv(name)); |
106 | 0 | if (!val) |
107 | 0 | val = apr_pstrdup(r->pool, fallback); |
108 | 0 | if (val) |
109 | 0 | apr_table_addn(dest, name, val); |
110 | 0 | } |
111 | | |
112 | | AP_DECLARE(char **) ap_create_environment(apr_pool_t *p, apr_table_t *t) |
113 | 0 | { |
114 | 0 | const apr_array_header_t *env_arr = apr_table_elts(t); |
115 | 0 | const apr_table_entry_t *elts = (const apr_table_entry_t *) env_arr->elts; |
116 | 0 | char **env = (char **) apr_palloc(p, (env_arr->nelts + 2) * sizeof(char *)); |
117 | 0 | int i, j; |
118 | 0 | char *tz; |
119 | 0 | char *whack; |
120 | |
|
121 | 0 | j = 0; |
122 | 0 | if (!apr_table_get(t, "TZ")) { |
123 | 0 | tz = getenv("TZ"); |
124 | 0 | if (tz != NULL) { |
125 | 0 | env[j++] = apr_pstrcat(p, "TZ=", tz, NULL); |
126 | 0 | } |
127 | 0 | } |
128 | 0 | for (i = 0; i < env_arr->nelts; ++i) { |
129 | 0 | if (!elts[i].key) { |
130 | 0 | continue; |
131 | 0 | } |
132 | 0 | env[j] = apr_pstrcat(p, elts[i].key, "=", elts[i].val, NULL); |
133 | 0 | whack = env[j]; |
134 | 0 | if (apr_isdigit(*whack)) { |
135 | 0 | *whack++ = '_'; |
136 | 0 | } |
137 | 0 | while (*whack != '=') { |
138 | | #ifdef WIN32 |
139 | | if (!apr_isalnum(*whack) && *whack != '(' && *whack != ')') { |
140 | | #else |
141 | 0 | if (!apr_isalnum(*whack)) { |
142 | 0 | #endif |
143 | 0 | *whack = '_'; |
144 | 0 | } |
145 | 0 | ++whack; |
146 | 0 | } |
147 | 0 | ++j; |
148 | 0 | } |
149 | |
|
150 | 0 | env[j] = NULL; |
151 | 0 | return env; |
152 | 0 | } |
153 | | |
154 | | AP_DECLARE(void) ap_add_common_vars(request_rec *r) |
155 | 0 | { |
156 | 0 | apr_table_t *e; |
157 | 0 | server_rec *s = r->server; |
158 | 0 | conn_rec *c = r->connection; |
159 | 0 | core_dir_config *conf = |
160 | 0 | (core_dir_config *)ap_get_core_module_config(r->per_dir_config); |
161 | 0 | const char *env_temp; |
162 | 0 | const apr_array_header_t *hdrs_arr = apr_table_elts(r->headers_in); |
163 | 0 | const apr_table_entry_t *hdrs = (const apr_table_entry_t *) hdrs_arr->elts; |
164 | 0 | int i; |
165 | 0 | apr_port_t rport; |
166 | 0 | char *q; |
167 | | |
168 | | /* use a temporary apr_table_t which we'll overlap onto |
169 | | * r->subprocess_env later |
170 | | * (exception: if r->subprocess_env is empty at the start, |
171 | | * write directly into it) |
172 | | */ |
173 | 0 | if (apr_is_empty_table(r->subprocess_env)) { |
174 | 0 | e = r->subprocess_env; |
175 | 0 | } |
176 | 0 | else { |
177 | 0 | e = apr_table_make(r->pool, 25 + hdrs_arr->nelts); |
178 | 0 | } |
179 | | |
180 | | /* First, add environment vars from headers... this is as per |
181 | | * CGI specs, though other sorts of scripting interfaces see |
182 | | * the same vars... |
183 | | */ |
184 | |
|
185 | 0 | for (i = 0; i < hdrs_arr->nelts; ++i) { |
186 | 0 | if (!hdrs[i].key) { |
187 | 0 | continue; |
188 | 0 | } |
189 | | |
190 | | /* A few headers are special cased --- Authorization to prevent |
191 | | * rogue scripts from capturing passwords; content-type and -length |
192 | | * for no particular reason. |
193 | | */ |
194 | | |
195 | 0 | if (!ap_cstr_casecmp(hdrs[i].key, "Content-type")) { |
196 | 0 | apr_table_addn(e, "CONTENT_TYPE", hdrs[i].val); |
197 | 0 | } |
198 | 0 | else if (!ap_cstr_casecmp(hdrs[i].key, "Content-length")) { |
199 | 0 | apr_table_addn(e, "CONTENT_LENGTH", hdrs[i].val); |
200 | 0 | } |
201 | | /* HTTP_PROXY collides with a popular envvar used to configure |
202 | | * proxies, don't let clients set/override it. But, if you must... |
203 | | */ |
204 | 0 | #ifndef SECURITY_HOLE_PASS_PROXY |
205 | 0 | else if (!ap_cstr_casecmp(hdrs[i].key, "Proxy")) { |
206 | 0 | ; |
207 | 0 | } |
208 | 0 | #endif |
209 | | /* |
210 | | * You really don't want to disable this check, since it leaves you |
211 | | * wide open to CGIs stealing passwords and people viewing them |
212 | | * in the environment with "ps -e". But, if you must... |
213 | | */ |
214 | 0 | #ifndef SECURITY_HOLE_PASS_AUTHORIZATION |
215 | 0 | else if (!ap_cstr_casecmp(hdrs[i].key, "Authorization") |
216 | 0 | || !ap_cstr_casecmp(hdrs[i].key, "Proxy-Authorization")) { |
217 | 0 | if (conf->cgi_pass_auth == AP_CGI_PASS_AUTH_ON) { |
218 | 0 | add_unless_null(e, http2env(r, hdrs[i].key), hdrs[i].val); |
219 | 0 | } |
220 | 0 | } |
221 | 0 | #endif |
222 | 0 | else |
223 | 0 | add_unless_null(e, http2env(r, hdrs[i].key), hdrs[i].val); |
224 | 0 | } |
225 | |
|
226 | 0 | env2env(e, r, "PATH", DEFAULT_PATH); |
227 | | #if defined(WIN32) |
228 | | env2env(e, r, "SystemRoot", NULL); |
229 | | env2env(e, r, "COMSPEC", NULL); |
230 | | env2env(e, r, "PATHEXT", NULL); |
231 | | env2env(e, r, "WINDIR", NULL); |
232 | | #elif defined(OS2) |
233 | | env2env(e, r, "COMSPEC", NULL); |
234 | | env2env(e, r, "ETC", NULL); |
235 | | env2env(e, r, "DPATH", NULL); |
236 | | env2env(e, r, "PERLLIB_PREFIX", NULL); |
237 | | #elif defined(BEOS) |
238 | | env2env(e, r, "LIBRARY_PATH", NULL); |
239 | | #elif defined(DARWIN) |
240 | | env2env(e, r, "DYLD_LIBRARY_PATH", NULL); |
241 | | #elif defined(_AIX) |
242 | | env2env(e, r, "LIBPATH", NULL); |
243 | | #elif defined(__HPUX__) |
244 | | /* HPUX PARISC 2.0W knows both, otherwise redundancy is harmless */ |
245 | | env2env(e, r, "SHLIB_PATH", NULL); |
246 | | env2env(e, r, "LD_LIBRARY_PATH", NULL); |
247 | | #else /* Some Unix */ |
248 | 0 | env2env(e, r, "LD_LIBRARY_PATH", NULL); |
249 | 0 | #endif |
250 | |
|
251 | 0 | apr_table_addn(e, "SERVER_SIGNATURE", ap_psignature("", r)); |
252 | 0 | apr_table_addn(e, "SERVER_SOFTWARE", ap_get_server_banner()); |
253 | 0 | apr_table_addn(e, "SERVER_NAME", |
254 | 0 | ap_escape_html(r->pool, ap_get_server_name_for_url(r))); |
255 | 0 | apr_table_addn(e, "SERVER_ADDR", r->connection->local_ip); /* Apache */ |
256 | 0 | apr_table_addn(e, "SERVER_PORT", |
257 | 0 | apr_psprintf(r->pool, "%u", ap_get_server_port(r))); |
258 | 0 | add_unless_null(e, "REMOTE_HOST", |
259 | 0 | ap_get_useragent_host(r, REMOTE_HOST, NULL)); |
260 | 0 | apr_table_addn(e, "REMOTE_ADDR", r->useragent_ip); |
261 | 0 | apr_table_addn(e, "DOCUMENT_ROOT", ap_document_root(r)); /* Apache */ |
262 | 0 | apr_table_setn(e, "REQUEST_SCHEME", ap_http_scheme(r)); |
263 | 0 | apr_table_addn(e, "CONTEXT_PREFIX", ap_context_prefix(r)); |
264 | 0 | apr_table_addn(e, "CONTEXT_DOCUMENT_ROOT", ap_context_document_root(r)); |
265 | 0 | apr_table_addn(e, "SERVER_ADMIN", s->server_admin); /* Apache */ |
266 | 0 | if (apr_table_get(r->notes, "proxy-noquery") && (q = ap_strchr(r->filename, '?'))) { |
267 | 0 | char *script_filename = apr_pstrmemdup(r->pool, r->filename, q - r->filename); |
268 | 0 | apr_table_addn(e, "SCRIPT_FILENAME", script_filename); |
269 | 0 | } |
270 | 0 | else { |
271 | 0 | apr_table_addn(e, "SCRIPT_FILENAME", r->filename); /* Apache */ |
272 | 0 | } |
273 | |
|
274 | 0 | rport = c->client_addr->port; |
275 | 0 | apr_table_addn(e, "REMOTE_PORT", apr_itoa(r->pool, rport)); |
276 | |
|
277 | 0 | if (r->user) { |
278 | 0 | apr_table_addn(e, "REMOTE_USER", r->user); |
279 | 0 | } |
280 | 0 | else if (r->prev) { |
281 | 0 | request_rec *back = r->prev; |
282 | |
|
283 | 0 | while (back) { |
284 | 0 | if (back->user) { |
285 | 0 | apr_table_addn(e, "REDIRECT_REMOTE_USER", back->user); |
286 | 0 | break; |
287 | 0 | } |
288 | 0 | back = back->prev; |
289 | 0 | } |
290 | 0 | } |
291 | 0 | add_unless_null(e, "AUTH_TYPE", r->ap_auth_type); |
292 | 0 | env_temp = ap_get_remote_logname(r); |
293 | 0 | if (env_temp) { |
294 | 0 | apr_table_addn(e, "REMOTE_IDENT", apr_pstrdup(r->pool, env_temp)); |
295 | 0 | } |
296 | | |
297 | | /* Apache custom error responses. If we have redirected set two new vars */ |
298 | |
|
299 | 0 | if (r->prev) { |
300 | 0 | if (conf->qualify_redirect_url != AP_CORE_CONFIG_ON) { |
301 | 0 | add_unless_null(e, "REDIRECT_URL", r->prev->uri); |
302 | 0 | } |
303 | 0 | else { |
304 | | /* PR#57785: reconstruct full URL here */ |
305 | 0 | apr_uri_t *uri = &r->prev->parsed_uri; |
306 | 0 | if (!uri->scheme) { |
307 | 0 | uri->scheme = (char*)ap_http_scheme(r->prev); |
308 | 0 | } |
309 | 0 | if (!uri->port) { |
310 | 0 | uri->port = ap_get_server_port(r->prev); |
311 | 0 | uri->port_str = apr_psprintf(r->pool, "%u", uri->port); |
312 | 0 | } |
313 | 0 | if (!uri->hostname) { |
314 | 0 | uri->hostname = (char*)ap_get_server_name_for_url(r->prev); |
315 | 0 | } |
316 | 0 | add_unless_null(e, "REDIRECT_URL", |
317 | 0 | apr_uri_unparse(r->pool, uri, 0)); |
318 | 0 | } |
319 | 0 | add_unless_null(e, "REDIRECT_QUERY_STRING", r->prev->args); |
320 | 0 | } |
321 | |
|
322 | 0 | if (e != r->subprocess_env) { |
323 | 0 | apr_table_overlap(r->subprocess_env, e, APR_OVERLAP_TABLES_SET); |
324 | 0 | } |
325 | 0 | } |
326 | | |
327 | | /* This "cute" little function comes about because the path info on |
328 | | * filenames and URLs aren't always the same. So we take the two, |
329 | | * and find as much of the two that match as possible. |
330 | | */ |
331 | | |
332 | | AP_DECLARE(int) ap_find_path_info(const char *uri, const char *path_info) |
333 | 0 | { |
334 | 0 | int lu = strlen(uri); |
335 | 0 | int lp = strlen(path_info); |
336 | |
|
337 | 0 | while (lu-- && lp-- && uri[lu] == path_info[lp]) { |
338 | 0 | if (path_info[lp] == '/') { |
339 | 0 | while (lu && uri[lu-1] == '/') lu--; |
340 | 0 | } |
341 | 0 | } |
342 | |
|
343 | 0 | if (lu == -1) { |
344 | 0 | lu = 0; |
345 | 0 | } |
346 | |
|
347 | 0 | while (uri[lu] != '\0' && uri[lu] != '/') { |
348 | 0 | lu++; |
349 | 0 | } |
350 | 0 | return lu; |
351 | 0 | } |
352 | | |
353 | | /* Obtain the Request-URI from the original request-line, returning |
354 | | * a new string from the request pool containing the URI or "". |
355 | | */ |
356 | | static char *original_uri(request_rec *r) |
357 | 0 | { |
358 | 0 | char *first, *last; |
359 | |
|
360 | 0 | if (r->the_request == NULL) { |
361 | 0 | return (char *) apr_pcalloc(r->pool, 1); |
362 | 0 | } |
363 | | |
364 | 0 | first = r->the_request; /* use the request-line */ |
365 | |
|
366 | 0 | while (*first && !apr_isspace(*first)) { |
367 | 0 | ++first; /* skip over the method */ |
368 | 0 | } |
369 | 0 | while (apr_isspace(*first)) { |
370 | 0 | ++first; /* and the space(s) */ |
371 | 0 | } |
372 | |
|
373 | 0 | last = first; |
374 | 0 | while (*last && !apr_isspace(*last)) { |
375 | 0 | ++last; /* end at next whitespace */ |
376 | 0 | } |
377 | |
|
378 | 0 | return apr_pstrmemdup(r->pool, first, last - first); |
379 | 0 | } |
380 | | |
381 | | AP_DECLARE(void) ap_add_cgi_vars(request_rec *r) |
382 | 0 | { |
383 | 0 | apr_table_t *e = r->subprocess_env; |
384 | 0 | core_dir_config *conf = |
385 | 0 | (core_dir_config *)ap_get_core_module_config(r->per_dir_config); |
386 | 0 | int request_uri_from_original = 1; |
387 | 0 | const char *request_uri_rule; |
388 | |
|
389 | 0 | apr_table_setn(e, "GATEWAY_INTERFACE", "CGI/1.1"); |
390 | 0 | apr_table_setn(e, "SERVER_PROTOCOL", r->protocol); |
391 | 0 | apr_table_setn(e, "REQUEST_METHOD", r->method); |
392 | 0 | apr_table_setn(e, "QUERY_STRING", r->args ? r->args : ""); |
393 | |
|
394 | 0 | if (conf->cgi_var_rules) { |
395 | 0 | request_uri_rule = apr_hash_get(conf->cgi_var_rules, "REQUEST_URI", |
396 | 0 | APR_HASH_KEY_STRING); |
397 | 0 | if (request_uri_rule && !strcmp(request_uri_rule, "current-uri")) { |
398 | 0 | request_uri_from_original = 0; |
399 | 0 | } |
400 | 0 | } |
401 | 0 | apr_table_setn(e, "REQUEST_URI", |
402 | 0 | request_uri_from_original ? original_uri(r) : r->uri); |
403 | | |
404 | | /* Note that the code below special-cases scripts run from includes, |
405 | | * because it "knows" that the sub_request has been hacked to have the |
406 | | * args and path_info of the original request, and not any that may have |
407 | | * come with the script URI in the include command. Ugh. |
408 | | */ |
409 | |
|
410 | 0 | if (!strcmp(r->protocol, "INCLUDED")) { |
411 | 0 | apr_table_setn(e, "SCRIPT_NAME", r->uri); |
412 | 0 | if (r->path_info && *r->path_info) { |
413 | 0 | apr_table_setn(e, "PATH_INFO", r->path_info); |
414 | 0 | } |
415 | 0 | } |
416 | 0 | else if (!r->path_info || !*r->path_info) { |
417 | 0 | apr_table_setn(e, "SCRIPT_NAME", r->uri); |
418 | 0 | } |
419 | 0 | else { |
420 | 0 | int path_info_start = ap_find_path_info(r->uri, r->path_info); |
421 | |
|
422 | 0 | apr_table_setn(e, "SCRIPT_NAME", |
423 | 0 | apr_pstrndup(r->pool, r->uri, path_info_start)); |
424 | |
|
425 | 0 | apr_table_setn(e, "PATH_INFO", r->path_info); |
426 | 0 | } |
427 | |
|
428 | 0 | if (r->path_info && r->path_info[0]) { |
429 | | /* |
430 | | * To get PATH_TRANSLATED, treat PATH_INFO as a URI path. |
431 | | * Need to re-escape it for this, since the entire URI was |
432 | | * un-escaped before we determined where the PATH_INFO began. |
433 | | */ |
434 | 0 | request_rec *pa_req; |
435 | |
|
436 | 0 | pa_req = ap_sub_req_lookup_uri(ap_escape_uri(r->pool, r->path_info), r, |
437 | 0 | NULL); |
438 | |
|
439 | 0 | if (pa_req->filename) { |
440 | 0 | char *pt = apr_pstrcat(r->pool, pa_req->filename, pa_req->path_info, |
441 | 0 | NULL); |
442 | | #ifdef WIN32 |
443 | | /* We need to make this a real Windows path name */ |
444 | | apr_filepath_merge(&pt, "", pt, APR_FILEPATH_NATIVE, r->pool); |
445 | | #endif |
446 | 0 | apr_table_setn(e, "PATH_TRANSLATED", pt); |
447 | 0 | } |
448 | 0 | ap_destroy_sub_req(pa_req); |
449 | 0 | } |
450 | 0 | } |
451 | | |
452 | | |
453 | | static int set_cookie_doo_doo(void *v, const char *key, const char *val) |
454 | 0 | { |
455 | 0 | apr_table_addn(v, key, val); |
456 | 0 | return 1; |
457 | 0 | } |
458 | | |
459 | 0 | #define HTTP_UNSET (-HTTP_OK) |
460 | | #define SCRIPT_LOG_MARK __FILE__,__LINE__,module_index |
461 | | |
462 | | AP_DECLARE(int) ap_scan_script_header_err_core_ex(request_rec *r, char *buffer, |
463 | | int (*getsfunc) (char *, int, void *), |
464 | | void *getsfunc_data, |
465 | | int module_index) |
466 | 0 | { |
467 | 0 | char x[MAX_STRING_LEN]; |
468 | 0 | char *w, *l; |
469 | 0 | apr_size_t p; |
470 | 0 | int cgi_status = HTTP_UNSET; |
471 | 0 | apr_table_t *merge; |
472 | 0 | apr_table_t *cookie_table; |
473 | 0 | int trace_log = APLOG_R_MODULE_IS_LEVEL(r, module_index, APLOG_TRACE1); |
474 | 0 | int first_header = 1; |
475 | |
|
476 | 0 | if (buffer) { |
477 | 0 | *buffer = '\0'; |
478 | 0 | } |
479 | 0 | w = buffer ? buffer : x; |
480 | | |
481 | | /* temporary place to hold headers to merge in later */ |
482 | 0 | merge = apr_table_make(r->pool, 10); |
483 | | |
484 | | /* The HTTP specification says that it is legal to merge duplicate |
485 | | * headers into one. Some browsers that support Cookies don't like |
486 | | * merged headers and prefer that each Set-Cookie header is sent |
487 | | * separately. Lets humour those browsers by not merging. |
488 | | * Oh what a pain it is. |
489 | | */ |
490 | 0 | cookie_table = apr_table_make(r->pool, 2); |
491 | 0 | apr_table_do(set_cookie_doo_doo, cookie_table, r->err_headers_out, "Set-Cookie", NULL); |
492 | |
|
493 | 0 | while (1) { |
494 | |
|
495 | 0 | int rv = (*getsfunc) (w, MAX_STRING_LEN - 1, getsfunc_data); |
496 | 0 | if (rv == 0) { |
497 | 0 | const char *msg = "Premature end of script headers"; |
498 | 0 | if (first_header) |
499 | 0 | msg = "End of script output before headers"; |
500 | | /* Intentional no APLOGNO */ |
501 | 0 | ap_log_rerror(SCRIPT_LOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r, |
502 | 0 | "%s: %s", msg, |
503 | 0 | apr_filepath_name_get(r->filename)); |
504 | 0 | return HTTP_INTERNAL_SERVER_ERROR; |
505 | 0 | } |
506 | 0 | else if (rv == -1) { |
507 | | /* Intentional no APLOGNO */ |
508 | 0 | ap_log_rerror(SCRIPT_LOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r, |
509 | 0 | "Script timed out before returning headers: %s", |
510 | 0 | apr_filepath_name_get(r->filename)); |
511 | 0 | return HTTP_GATEWAY_TIME_OUT; |
512 | 0 | } |
513 | | |
514 | | /* Delete terminal (CR?)LF */ |
515 | | |
516 | 0 | p = strlen(w); |
517 | | /* Indeed, the host's '\n': |
518 | | '\012' for UNIX; '\015' for MacOS; '\025' for OS/390 |
519 | | -- whatever the script generates. |
520 | | */ |
521 | 0 | if (p > 0 && w[p - 1] == '\n') { |
522 | 0 | if (p > 1 && w[p - 2] == CR) { |
523 | 0 | w[p - 2] = '\0'; |
524 | 0 | } |
525 | 0 | else { |
526 | 0 | w[p - 1] = '\0'; |
527 | 0 | } |
528 | 0 | } |
529 | | |
530 | | /* |
531 | | * If we've finished reading the headers, check to make sure any |
532 | | * HTTP/1.1 conditions are met. If so, we're done; normal processing |
533 | | * will handle the script's output. If not, just return the error. |
534 | | * The appropriate thing to do would be to send the script process a |
535 | | * SIGPIPE to let it know we're ignoring it, close the channel to the |
536 | | * script process, and *then* return the failed-to-meet-condition |
537 | | * error. Otherwise we'd be waiting for the script to finish |
538 | | * blithering before telling the client the output was no good. |
539 | | * However, we don't have the information to do that, so we have to |
540 | | * leave it to an upper layer. |
541 | | */ |
542 | 0 | if (w[0] == '\0') { |
543 | 0 | int cond_status = OK; |
544 | | |
545 | | /* PR#38070: This fails because it gets confused when a |
546 | | * CGI Status header overrides ap_meets_conditions. |
547 | | * |
548 | | * We can fix that by dropping ap_meets_conditions when |
549 | | * Status has been set. Since this is the only place |
550 | | * cgi_status gets used, let's test it explicitly. |
551 | | * |
552 | | * The alternative would be to ignore CGI Status when |
553 | | * ap_meets_conditions returns anything interesting. |
554 | | * That would be safer wrt HTTP, but would break CGI. |
555 | | */ |
556 | 0 | if ((cgi_status == HTTP_UNSET) && (r->method_number == M_GET)) { |
557 | 0 | cond_status = ap_meets_conditions(r); |
558 | 0 | } |
559 | 0 | apr_table_overlap(r->err_headers_out, merge, |
560 | 0 | APR_OVERLAP_TABLES_MERGE); |
561 | 0 | if (!apr_is_empty_table(cookie_table)) { |
562 | | /* the cookies have already been copied to the cookie_table */ |
563 | 0 | apr_table_unset(r->err_headers_out, "Set-Cookie"); |
564 | 0 | r->err_headers_out = apr_table_overlay(r->pool, |
565 | 0 | r->err_headers_out, cookie_table); |
566 | 0 | } |
567 | 0 | return cond_status; |
568 | 0 | } |
569 | | |
570 | 0 | if (trace_log) { |
571 | 0 | if (first_header) |
572 | 0 | ap_log_rerror(SCRIPT_LOG_MARK, APLOG_TRACE4, 0, r, |
573 | 0 | "Headers from script '%s':", |
574 | 0 | apr_filepath_name_get(r->filename)); |
575 | 0 | ap_log_rerror(SCRIPT_LOG_MARK, APLOG_TRACE4, 0, r, " %s", w); |
576 | 0 | } |
577 | | |
578 | | /* if we see a bogus header don't ignore it. Shout and scream */ |
579 | |
|
580 | | #if APR_CHARSET_EBCDIC |
581 | | /* Chances are that we received an ASCII header text instead of |
582 | | * the expected EBCDIC header lines. Try to auto-detect: |
583 | | */ |
584 | | if (!(l = strchr(w, ':'))) { |
585 | | int maybeASCII = 0, maybeEBCDIC = 0; |
586 | | unsigned char *cp, native; |
587 | | apr_size_t inbytes_left, outbytes_left; |
588 | | |
589 | | for (cp = w; *cp != '\0'; ++cp) { |
590 | | native = apr_xlate_conv_byte(ap_hdrs_from_ascii, *cp); |
591 | | if (apr_isprint(*cp) && !apr_isprint(native)) |
592 | | ++maybeEBCDIC; |
593 | | if (!apr_isprint(*cp) && apr_isprint(native)) |
594 | | ++maybeASCII; |
595 | | } |
596 | | if (maybeASCII > maybeEBCDIC) { |
597 | | ap_log_error(SCRIPT_LOG_MARK, APLOG_ERR, 0, r->server, |
598 | | APLOGNO(02660) "CGI Interface Error: " |
599 | | "Script headers apparently ASCII: (CGI = %s)", |
600 | | r->filename); |
601 | | inbytes_left = outbytes_left = cp - w; |
602 | | apr_xlate_conv_buffer(ap_hdrs_from_ascii, |
603 | | w, &inbytes_left, w, &outbytes_left); |
604 | | } |
605 | | } |
606 | | #endif /*APR_CHARSET_EBCDIC*/ |
607 | 0 | if (!(l = strchr(w, ':'))) { |
608 | 0 | if (!buffer) { |
609 | | /* Soak up all the script output - may save an outright kill */ |
610 | 0 | while ((*getsfunc)(w, MAX_STRING_LEN - 1, getsfunc_data) > 0) { |
611 | 0 | continue; |
612 | 0 | } |
613 | 0 | } |
614 | | |
615 | | /* Intentional no APLOGNO */ |
616 | 0 | ap_log_rerror(SCRIPT_LOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r, |
617 | 0 | "malformed header from script '%s': Bad header: %.30s", |
618 | 0 | apr_filepath_name_get(r->filename), w); |
619 | 0 | return HTTP_INTERNAL_SERVER_ERROR; |
620 | 0 | } |
621 | | |
622 | 0 | *l++ = '\0'; |
623 | 0 | while (apr_isspace(*l)) { |
624 | 0 | ++l; |
625 | 0 | } |
626 | |
|
627 | 0 | if (!ap_cstr_casecmp(w, "Content-type")) { |
628 | 0 | char *tmp; |
629 | | |
630 | | /* Nuke trailing whitespace */ |
631 | |
|
632 | 0 | char *endp = l + strlen(l) - 1; |
633 | 0 | while (endp > l && apr_isspace(*endp)) { |
634 | 0 | *endp-- = '\0'; |
635 | 0 | } |
636 | |
|
637 | 0 | tmp = apr_pstrdup(r->pool, l); |
638 | 0 | ap_content_type_tolower(tmp); |
639 | 0 | ap_set_content_type(r, tmp); |
640 | 0 | } |
641 | | /* |
642 | | * If the script returned a specific status, that's what |
643 | | * we'll use - otherwise we assume 200 OK. |
644 | | */ |
645 | 0 | else if (!ap_cstr_casecmp(w, "Status")) { |
646 | 0 | r->status = cgi_status = atoi(l); |
647 | 0 | if (!ap_is_HTTP_VALID_RESPONSE(cgi_status)) |
648 | | /* Intentional no APLOGNO */ |
649 | 0 | ap_log_rerror(SCRIPT_LOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r, |
650 | 0 | "Invalid status line from script '%s': %.30s", |
651 | 0 | apr_filepath_name_get(r->filename), l); |
652 | 0 | else |
653 | 0 | if (APLOGrtrace1(r)) |
654 | 0 | ap_log_rerror(SCRIPT_LOG_MARK, APLOG_TRACE1, 0, r, |
655 | 0 | "Status line from script '%s': %.30s", |
656 | 0 | apr_filepath_name_get(r->filename), l); |
657 | 0 | r->status_line = apr_pstrdup(r->pool, l); |
658 | 0 | } |
659 | 0 | else if (!ap_cstr_casecmp(w, "Location")) { |
660 | 0 | apr_table_set(r->headers_out, w, l); |
661 | 0 | } |
662 | 0 | else if (!ap_cstr_casecmp(w, "Content-Length")) { |
663 | 0 | apr_table_set(r->headers_out, w, l); |
664 | 0 | } |
665 | 0 | else if (!ap_cstr_casecmp(w, "Content-Range")) { |
666 | 0 | apr_table_set(r->headers_out, w, l); |
667 | 0 | } |
668 | 0 | else if (!ap_cstr_casecmp(w, "Transfer-Encoding")) { |
669 | 0 | apr_table_set(r->headers_out, w, l); |
670 | 0 | } |
671 | 0 | else if (!ap_cstr_casecmp(w, "ETag")) { |
672 | 0 | apr_table_set(r->headers_out, w, l); |
673 | 0 | } |
674 | | /* |
675 | | * If the script gave us a Last-Modified header, we can't just |
676 | | * pass it on blindly because of restrictions on future or invalid values. |
677 | | */ |
678 | 0 | else if (!ap_cstr_casecmp(w, "Last-Modified")) { |
679 | 0 | apr_time_t parsed_date = apr_date_parse_http(l); |
680 | 0 | if (parsed_date != APR_DATE_BAD) { |
681 | 0 | ap_update_mtime(r, parsed_date); |
682 | 0 | ap_set_last_modified(r); |
683 | 0 | if (APLOGrtrace1(r)) { |
684 | 0 | apr_time_t last_modified_date = apr_date_parse_http(apr_table_get(r->headers_out, |
685 | 0 | "Last-Modified")); |
686 | | /* |
687 | | * A Last-Modified header value coming from a (F)CGI source |
688 | | * is considered HTTP input so we assume the GMT timezone. |
689 | | * The following logs should inform the admin about violations |
690 | | * and related actions taken by httpd. |
691 | | * The apr_date_parse_rfc function is 'timezone aware' |
692 | | * and it will be used to generate a more informative set of logs |
693 | | * (we don't use it as a replacement of apr_date_parse_http |
694 | | * for the aforementioned reason). |
695 | | */ |
696 | 0 | apr_time_t parsed_date_tz_aware = apr_date_parse_rfc(l); |
697 | | |
698 | | /* |
699 | | * The parsed Last-Modified header datestring has been replaced by httpd. |
700 | | */ |
701 | 0 | if (parsed_date > last_modified_date) { |
702 | 0 | ap_log_rerror(SCRIPT_LOG_MARK, APLOG_TRACE1, 0, r, |
703 | 0 | "The Last-Modified header value %s (%s) " |
704 | 0 | "has been replaced with '%s'", l, |
705 | 0 | parsed_date != parsed_date_tz_aware ? "not in GMT" |
706 | 0 | : "in the future", |
707 | 0 | apr_table_get(r->headers_out, "Last-Modified")); |
708 | | /* |
709 | | * Last-Modified header datestring not in GMT and not considered in the future |
710 | | * by httpd (like now() + 1 hour in the PST timezone). No action is taken but |
711 | | * the admin is warned about the violation. |
712 | | */ |
713 | 0 | } else if (parsed_date != parsed_date_tz_aware) { |
714 | 0 | ap_log_rerror(SCRIPT_LOG_MARK, APLOG_TRACE1, 0, r, |
715 | 0 | "The Last-Modified header value is not set " |
716 | 0 | "within the GMT timezone (as required)"); |
717 | 0 | } |
718 | 0 | } |
719 | 0 | } |
720 | 0 | else { |
721 | 0 | ap_log_rerror(SCRIPT_LOG_MARK, APLOG_INFO, 0, r, APLOGNO(10247) |
722 | 0 | "Ignored invalid header value: Last-Modified: '%s'", l); |
723 | 0 | } |
724 | 0 | } |
725 | 0 | else if (!ap_cstr_casecmp(w, "Set-Cookie")) { |
726 | 0 | apr_table_add(cookie_table, w, l); |
727 | 0 | } |
728 | 0 | else { |
729 | 0 | apr_table_add(merge, w, l); |
730 | 0 | } |
731 | 0 | first_header = 0; |
732 | 0 | } |
733 | | /* never reached - we leave this function within the while loop above */ |
734 | 0 | return OK; |
735 | 0 | } |
736 | | |
737 | | AP_DECLARE(int) ap_scan_script_header_err_core(request_rec *r, char *buffer, |
738 | | int (*getsfunc) (char *, int, void *), |
739 | | void *getsfunc_data) |
740 | 0 | { |
741 | 0 | return ap_scan_script_header_err_core_ex(r, buffer, getsfunc, |
742 | 0 | getsfunc_data, |
743 | 0 | APLOG_MODULE_INDEX); |
744 | 0 | } |
745 | | |
746 | | static int getsfunc_FILE(char *buf, int len, void *f) |
747 | 0 | { |
748 | 0 | return apr_file_gets(buf, len, (apr_file_t *) f) == APR_SUCCESS; |
749 | 0 | } |
750 | | |
751 | | AP_DECLARE(int) ap_scan_script_header_err(request_rec *r, apr_file_t *f, |
752 | | char *buffer) |
753 | 0 | { |
754 | 0 | return ap_scan_script_header_err_core_ex(r, buffer, getsfunc_FILE, f, |
755 | 0 | APLOG_MODULE_INDEX); |
756 | 0 | } |
757 | | |
758 | | AP_DECLARE(int) ap_scan_script_header_err_ex(request_rec *r, apr_file_t *f, |
759 | | char *buffer, int module_index) |
760 | 0 | { |
761 | 0 | return ap_scan_script_header_err_core_ex(r, buffer, getsfunc_FILE, f, |
762 | 0 | module_index); |
763 | 0 | } |
764 | | |
765 | | |
766 | | static int getsfunc_BRIGADE(char *buf, int len, void *arg) |
767 | 0 | { |
768 | 0 | apr_bucket_brigade *bb = (apr_bucket_brigade *)arg; |
769 | 0 | const char *dst_end = buf + len - 1; /* leave room for terminating null */ |
770 | 0 | char *dst = buf; |
771 | 0 | apr_bucket *e = APR_BRIGADE_FIRST(bb); |
772 | 0 | apr_status_t rv; |
773 | 0 | int done = 0; |
774 | |
|
775 | 0 | while ((dst < dst_end) && !done && e != APR_BRIGADE_SENTINEL(bb) |
776 | 0 | && !APR_BUCKET_IS_EOS(e)) { |
777 | 0 | const char *bucket_data; |
778 | 0 | apr_size_t bucket_data_len; |
779 | 0 | const char *src; |
780 | 0 | const char *src_end; |
781 | 0 | apr_bucket * next; |
782 | |
|
783 | 0 | rv = apr_bucket_read(e, &bucket_data, &bucket_data_len, |
784 | 0 | APR_BLOCK_READ); |
785 | 0 | if (rv != APR_SUCCESS || (bucket_data_len == 0)) { |
786 | 0 | *dst = '\0'; |
787 | 0 | return APR_STATUS_IS_TIMEUP(rv) ? -1 : 0; |
788 | 0 | } |
789 | 0 | src = bucket_data; |
790 | 0 | src_end = bucket_data + bucket_data_len; |
791 | 0 | while ((src < src_end) && (dst < dst_end) && !done) { |
792 | 0 | if (*src == '\n') { |
793 | 0 | done = 1; |
794 | 0 | } |
795 | 0 | else if (*src != '\r') { |
796 | 0 | *dst++ = *src; |
797 | 0 | } |
798 | 0 | src++; |
799 | 0 | } |
800 | |
|
801 | 0 | if (src < src_end) { |
802 | 0 | apr_bucket_split(e, src - bucket_data); |
803 | 0 | } |
804 | 0 | next = APR_BUCKET_NEXT(e); |
805 | 0 | apr_bucket_delete(e); |
806 | 0 | e = next; |
807 | 0 | } |
808 | 0 | *dst = 0; |
809 | 0 | return done; |
810 | 0 | } |
811 | | |
812 | | AP_DECLARE(int) ap_scan_script_header_err_brigade(request_rec *r, |
813 | | apr_bucket_brigade *bb, |
814 | | char *buffer) |
815 | 0 | { |
816 | 0 | return ap_scan_script_header_err_core_ex(r, buffer, getsfunc_BRIGADE, bb, |
817 | 0 | APLOG_MODULE_INDEX); |
818 | 0 | } |
819 | | |
820 | | AP_DECLARE(int) ap_scan_script_header_err_brigade_ex(request_rec *r, |
821 | | apr_bucket_brigade *bb, |
822 | | char *buffer, |
823 | | int module_index) |
824 | 0 | { |
825 | 0 | return ap_scan_script_header_err_core_ex(r, buffer, getsfunc_BRIGADE, bb, |
826 | 0 | module_index); |
827 | 0 | } |
828 | | |
829 | | |
830 | | struct vastrs { |
831 | | va_list args; |
832 | | int arg; |
833 | | const char *curpos; |
834 | | }; |
835 | | |
836 | | static int getsfunc_STRING(char *w, int len, void *pvastrs) |
837 | 0 | { |
838 | 0 | struct vastrs *strs = (struct vastrs*) pvastrs; |
839 | 0 | const char *p; |
840 | 0 | apr_size_t t; |
841 | |
|
842 | 0 | if (!strs->curpos || !*strs->curpos) { |
843 | 0 | w[0] = '\0'; |
844 | 0 | return 0; |
845 | 0 | } |
846 | 0 | p = ap_strchr_c(strs->curpos, '\n'); |
847 | 0 | if (p) |
848 | 0 | ++p; |
849 | 0 | else |
850 | 0 | p = ap_strchr_c(strs->curpos, '\0'); |
851 | 0 | t = p - strs->curpos; |
852 | 0 | if (t > len) |
853 | 0 | t = len; |
854 | 0 | strncpy (w, strs->curpos, t); |
855 | 0 | w[t] = '\0'; |
856 | 0 | if (!strs->curpos[t]) { |
857 | 0 | ++strs->arg; |
858 | 0 | strs->curpos = va_arg(strs->args, const char *); |
859 | 0 | } |
860 | 0 | else |
861 | 0 | strs->curpos += t; |
862 | 0 | return t; |
863 | 0 | } |
864 | | |
865 | | /* ap_scan_script_header_err_strs() accepts additional const char* args... |
866 | | * each is treated as one or more header lines, and the first non-header |
867 | | * character is returned to **arg, **data. (The first optional arg is |
868 | | * counted as 0.) |
869 | | */ |
870 | | AP_DECLARE_NONSTD(int) ap_scan_script_header_err_strs_ex(request_rec *r, |
871 | | char *buffer, |
872 | | int module_index, |
873 | | const char **termch, |
874 | | int *termarg, ...) |
875 | 0 | { |
876 | 0 | struct vastrs strs; |
877 | 0 | int res; |
878 | |
|
879 | 0 | va_start(strs.args, termarg); |
880 | 0 | strs.arg = 0; |
881 | 0 | strs.curpos = va_arg(strs.args, char*); |
882 | 0 | res = ap_scan_script_header_err_core_ex(r, buffer, getsfunc_STRING, |
883 | 0 | (void *) &strs, module_index); |
884 | 0 | if (termch) |
885 | 0 | *termch = strs.curpos; |
886 | 0 | if (termarg) |
887 | 0 | *termarg = strs.arg; |
888 | 0 | va_end(strs.args); |
889 | 0 | return res; |
890 | 0 | } |
891 | | |
892 | | AP_DECLARE_NONSTD(int) ap_scan_script_header_err_strs(request_rec *r, |
893 | | char *buffer, |
894 | | const char **termch, |
895 | | int *termarg, ...) |
896 | 0 | { |
897 | 0 | struct vastrs strs; |
898 | 0 | int res; |
899 | |
|
900 | 0 | va_start(strs.args, termarg); |
901 | 0 | strs.arg = 0; |
902 | 0 | strs.curpos = va_arg(strs.args, char*); |
903 | 0 | res = ap_scan_script_header_err_core_ex(r, buffer, getsfunc_STRING, |
904 | 0 | (void *) &strs, APLOG_MODULE_INDEX); |
905 | 0 | if (termch) |
906 | 0 | *termch = strs.curpos; |
907 | 0 | if (termarg) |
908 | 0 | *termarg = strs.arg; |
909 | 0 | va_end(strs.args); |
910 | 0 | return res; |
911 | 0 | } |
912 | | |
913 | | static void |
914 | | argstr_to_table(char *str, apr_table_t *parms) |
915 | 0 | { |
916 | 0 | char *key; |
917 | 0 | char *value; |
918 | 0 | char *strtok_state; |
919 | |
|
920 | 0 | if (str == NULL) { |
921 | 0 | return; |
922 | 0 | } |
923 | | |
924 | 0 | key = apr_strtok(str, "&", &strtok_state); |
925 | 0 | while (key) { |
926 | 0 | value = strchr(key, '='); |
927 | 0 | if (value) { |
928 | 0 | *value = '\0'; /* Split the string in two */ |
929 | 0 | value++; /* Skip passed the = */ |
930 | 0 | } |
931 | 0 | else { |
932 | 0 | value = "1"; |
933 | 0 | } |
934 | 0 | ap_unescape_url(key); |
935 | 0 | ap_unescape_url(value); |
936 | 0 | apr_table_set(parms, key, value); |
937 | 0 | key = apr_strtok(NULL, "&", &strtok_state); |
938 | 0 | } |
939 | 0 | } |
940 | | |
941 | | AP_DECLARE(void) ap_args_to_table(request_rec *r, apr_table_t **table) |
942 | 0 | { |
943 | 0 | apr_table_t *t = apr_table_make(r->pool, 10); |
944 | 0 | argstr_to_table(apr_pstrdup(r->pool, r->args), t); |
945 | 0 | *table = t; |
946 | 0 | } |