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