Beta WinNT support. David Allen [djallen@raleigh.ibm.com]
[mod_fastcgi.git] / fcgi_protocol.c
blob223035da712934f84b0b0eb236914b2d257ca824
1 /*
2 * $Id: fcgi_protocol.c,v 1.14 2000/04/27 02:27:35 robs Exp $
3 */
6 #include "fcgi.h"
7 #include "fcgi_protocol.h"
9 /*******************************************************************************
10 * Build and queue a FastCGI message header. It is the caller's
11 * responsibility to make sure that there's enough space in the buffer, and
12 * that the data bytes (specified by 'len') are queued immediately following
13 * this header.
15 static void queue_header(fcgi_request *fr, int type, int len)
17 FCGI_Header header;
19 ap_assert(type > 0 && type <= FCGI_MAXTYPE);
20 ap_assert(len >= 0 && len <= 0xffff);
21 ap_assert(BufferFree(fr->serverOutputBuffer) > sizeof(FCGI_Header));
23 /* Assemble and queue the packet header. */
24 header.version = FCGI_VERSION;
25 header.type = type;
26 header.requestIdB1 = (fr->requestId >> 8) & 0xff;
27 header.requestIdB0 = (fr->requestId) & 0xff;
28 header.contentLengthB1 = len/256; /* MSB */
29 header.contentLengthB0 = len%256; /* LSB */
30 header.paddingLength = 0;
31 header.reserved = 0;
32 fcgi_buf_add_block(fr->serverOutputBuffer, (char *) &header, sizeof(FCGI_Header));
35 /*******************************************************************************
36 * Build a FCGI_BeginRequest message body.
38 static void build_begin_request(int role, int keepConnection,
39 FCGI_BeginRequestBody *body)
41 ap_assert((role >> 16) == 0);
42 body->roleB1 = (role >> 8) & 0xff;
43 body->roleB0 = (role ) & 0xff;
44 body->flags = (keepConnection) ? FCGI_KEEP_CONN : 0;
45 memset(body->reserved, 0, sizeof(body->reserved));
48 /*******************************************************************************
49 * Build and queue a FastCGI "Begin Request" message.
51 void fcgi_protocol_queue_begin_request(fcgi_request *fr)
53 FCGI_BeginRequestBody body;
54 int bodySize = sizeof(FCGI_BeginRequestBody);
56 /* We should be the first ones to use this buffer */
57 ap_assert(BufferLength(fr->serverOutputBuffer) == 0);
59 build_begin_request(fr->role, FALSE, &body);
60 queue_header(fr, FCGI_BEGIN_REQUEST, bodySize);
61 fcgi_buf_add_block(fr->serverOutputBuffer, (char *) &body, bodySize);
64 /*******************************************************************************
65 * Build a FastCGI name-value pair (env) header.
67 static void build_env_header(int nameLen, int valueLen,
68 unsigned char *headerBuffPtr, int *headerLenPtr)
70 unsigned char *startHeaderBuffPtr = headerBuffPtr;
72 ap_assert(nameLen >= 0);
73 if(nameLen < 0x80) {
74 *headerBuffPtr++ = nameLen;
75 } else {
76 *headerBuffPtr++ = (nameLen >> 24) | 0x80;
77 *headerBuffPtr++ = (nameLen >> 16);
78 *headerBuffPtr++ = (nameLen >> 8);
79 *headerBuffPtr++ = nameLen;
81 ap_assert(valueLen >= 0);
82 if(valueLen < 0x80) {
83 *headerBuffPtr++ = valueLen;
84 } else {
85 *headerBuffPtr++ = (valueLen >> 24) | 0x80;
86 *headerBuffPtr++ = (valueLen >> 16);
87 *headerBuffPtr++ = (valueLen >> 8);
88 *headerBuffPtr++ = valueLen;
90 *headerLenPtr = headerBuffPtr - startHeaderBuffPtr;
93 /* A static fn stolen from Apache's util_script.c...
94 * Obtain the Request-URI from the original request-line, returning
95 * a new string from the request pool containing the URI or "".
97 static char *apache_original_uri(request_rec *r)
99 char *first, *last;
101 if (r->the_request == NULL)
102 return (char *) ap_pcalloc(r->pool, 1);
104 first = r->the_request; /* use the request-line */
106 while (*first && !ap_isspace(*first))
107 ++first; /* skip over the method */
109 while (ap_isspace(*first))
110 ++first; /* and the space(s) */
112 last = first;
113 while (*last && !ap_isspace(*last))
114 ++last; /* end at next whitespace */
116 return ap_pstrndup(r->pool, first, last - first);
119 /* Based on Apache's ap_add_cgi_vars() in util_script.c.
120 * Apache's spins in sub_req_lookup_uri() trying to setup PATH_TRANSLATED,
121 * so we just don't do that part.
123 static void add_auth_cgi_vars(request_rec *r, const int compat)
125 table *e = r->subprocess_env;
127 ap_table_setn(e, "GATEWAY_INTERFACE", "CGI/1.1");
128 ap_table_setn(e, "SERVER_PROTOCOL", r->protocol);
129 ap_table_setn(e, "REQUEST_METHOD", r->method);
130 ap_table_setn(e, "QUERY_STRING", r->args ? r->args : "");
131 ap_table_setn(e, "REQUEST_URI", apache_original_uri(r));
133 /* The FastCGI spec precludes sending of CONTENT_LENGTH, PATH_INFO,
134 * PATH_TRANSLATED, and SCRIPT_NAME (for some reason?). PATH_TRANSLATED we
135 * don't have, its the variable that causes Apache to break trying to set
136 * up (and thus the reason this fn exists vs. using ap_add_cgi_vars()). */
137 if (compat) {
138 ap_table_unset(e, "CONTENT_LENGTH");
139 return;
142 /* Note that the code below special-cases scripts run from includes,
143 * because it "knows" that the sub_request has been hacked to have the
144 * args and path_info of the original request, and not any that may have
145 * come with the script URI in the include command. Ugh. */
146 if (!strcmp(r->protocol, "INCLUDED")) {
147 ap_table_setn(e, "SCRIPT_NAME", r->uri);
148 if (r->path_info && *r->path_info)
149 ap_table_setn(e, "PATH_INFO", r->path_info);
151 else if (!r->path_info || !*r->path_info)
152 ap_table_setn(e, "SCRIPT_NAME", r->uri);
153 else {
154 int path_info_start = ap_find_path_info(r->uri, r->path_info);
156 ap_table_setn(e, "SCRIPT_NAME", ap_pstrndup(r->pool, r->uri, path_info_start));
157 ap_table_setn(e, "PATH_INFO", r->path_info);
161 static void add_pass_header_vars(fcgi_request *fr)
163 const array_header *ph = fr->dynamic ? dynamic_pass_headers : fr->fs->pass_headers;
165 if (ph) {
166 const char **elt = (const char **)ph->elts;
167 int i = ph->nelts;
169 for ( ; i; --i, ++elt) {
170 const char *val = ap_table_get(fr->r->headers_in, *elt);
171 if (val) {
172 ap_table_setn(fr->r->subprocess_env, *elt, val);
178 /*******************************************************************************
179 * Build and queue the environment name-value pairs. Returns TRUE if the
180 * complete ENV was buffered, FALSE otherwise. Note: envp is updated to
181 * reflect the current position in the ENV.
183 int fcgi_protocol_queue_env(request_rec *r, fcgi_request *fr, env_status *env)
185 int charCount;
187 if (env->envp == NULL) {
188 ap_add_common_vars(r);
189 add_pass_header_vars(fr);
191 if (fr->role == FCGI_RESPONDER)
192 ap_add_cgi_vars(r);
193 else
194 add_auth_cgi_vars(r, fr->auth_compat);
196 env->envp = ap_create_environment(r->pool, r->subprocess_env);
197 env->pass = prep;
200 while (**env->envp) {
201 switch (env->pass)
203 case prep:
204 env->equalPtr = strchr(*env->envp, '=');
205 ap_assert(env->equalPtr != NULL);
206 env->nameLen = env->equalPtr - *env->envp;
207 env->valueLen = strlen(++env->equalPtr);
208 build_env_header(env->nameLen, env->valueLen, env->headerBuff, &env->headerLen);
209 env->totalLen = env->headerLen + env->nameLen + env->valueLen;
210 env->pass = header;
211 /* drop through */
213 case header:
214 if (BufferFree(fr->serverOutputBuffer) < (int)(sizeof(FCGI_Header) + env->headerLen)) {
215 return (FALSE);
217 queue_header(fr, FCGI_PARAMS, env->totalLen);
218 fcgi_buf_add_block(fr->serverOutputBuffer, (char *)env->headerBuff, env->headerLen);
219 env->pass = name;
220 /* drop through */
222 case name:
223 charCount = fcgi_buf_add_block(fr->serverOutputBuffer, *env->envp, env->nameLen);
224 if (charCount != env->nameLen) {
225 **env->envp += charCount;
226 env->nameLen -= charCount;
227 return (FALSE);
229 env->pass = value;
230 /* drop through */
232 case value:
233 charCount = fcgi_buf_add_block(fr->serverOutputBuffer, env->equalPtr, env->valueLen);
234 if (charCount != env->valueLen) {
235 env->equalPtr += charCount;
236 env->valueLen -= charCount;
237 return (FALSE);
239 env->pass = prep;
241 (*env->envp)++;
244 if (BufferFree(fr->serverOutputBuffer) < sizeof(FCGI_Header)) {
245 return(FALSE);
247 queue_header(fr, FCGI_PARAMS, 0);
248 return(TRUE);
251 /*******************************************************************************
252 * Queue data from the client input buffer to the FastCGI server output
253 * buffer (encapsulating the data in FastCGI protocol messages).
255 void fcgi_protocol_queue_client_buffer(fcgi_request *fr)
257 int movelen;
258 int in_len, out_free;
260 if (fr->eofSent)
261 return;
264 * If there's some client data and room for at least one byte
265 * of data in the output buffer (after protocol overhead), then
266 * move some data to the output buffer.
268 in_len = BufferLength(fr->clientInputBuffer);
269 out_free = max(0, BufferFree(fr->serverOutputBuffer) - sizeof(FCGI_Header));
270 movelen = min(in_len, out_free);
271 if (movelen > 0) {
272 queue_header(fr, FCGI_STDIN, movelen);
273 fcgi_buf_get_to_buf(fr->serverOutputBuffer, fr->clientInputBuffer, movelen);
277 * If all the client data has been sent, and there's room
278 * in the output buffer, indicate EOF.
280 if (movelen == in_len && fr->expectingClientContent <= 0
281 && BufferFree(fr->serverOutputBuffer) >= sizeof(FCGI_Header))
283 queue_header(fr, FCGI_STDIN, 0);
284 fr->eofSent = TRUE;
288 /*******************************************************************************
289 * Read FastCGI protocol messages from the FastCGI server input buffer into
290 * fr->header when parsing headers, to fr->fs_stderr when reading stderr data,
291 * or to the client output buffer otherwises.
293 int fcgi_protocol_dequeue(pool *p, fcgi_request *fr)
295 FCGI_Header header;
296 int len;
298 while (BufferLength(fr->serverInputBuffer) > 0) {
300 * State #1: looking for the next complete packet header.
302 if (fr->gotHeader == FALSE) {
303 if (BufferLength(fr->serverInputBuffer) < sizeof(FCGI_Header)) {
304 return OK;
306 fcgi_buf_get_to_block(fr->serverInputBuffer, (char *) &header,
307 sizeof(FCGI_Header));
309 * XXX: Better handling of packets with other version numbers
310 * and other packet problems.
312 if (header.version != FCGI_VERSION) {
313 ap_log_rerror(FCGI_LOG_ERR_NOERRNO, fr->r,
314 "FastCGI: comm with server \"%s\" aborted: protocol error: invalid version: %d != FCGI_VERSION(%d)",
315 fr->fs_path, header.version, FCGI_VERSION);
316 return SERVER_ERROR;
318 if (header.type > FCGI_MAXTYPE) {
319 ap_log_rerror(FCGI_LOG_ERR_NOERRNO, fr->r,
320 "FastCGI: comm with server \"%s\" aborted: protocol error: invalid type: %d > FCGI_MAXTYPE(%d)",
321 fr->fs_path, header.type, FCGI_MAXTYPE);
322 return SERVER_ERROR;
325 fr->packetType = header.type;
326 fr->dataLen = (header.contentLengthB1 << 8)
327 + header.contentLengthB0;
328 fr->gotHeader = TRUE;
329 fr->paddingLen = header.paddingLength;
333 * State #2: got a header, and processing packet bytes.
335 len = min(fr->dataLen, BufferLength(fr->serverInputBuffer));
336 ap_assert(len >= 0);
337 switch (fr->packetType) {
338 case FCGI_STDOUT:
339 if (len > 0) {
340 switch(fr->parseHeader) {
341 case SCAN_CGI_READING_HEADERS:
342 fcgi_buf_get_to_array(fr->serverInputBuffer, fr->header, len);
343 break;
344 case SCAN_CGI_FINISHED:
345 len = min(BufferFree(fr->clientOutputBuffer), len);
346 if (len > 0) {
347 fcgi_buf_get_to_buf(fr->clientOutputBuffer, fr->serverInputBuffer, len);
348 } else {
349 return OK;
351 break;
352 default:
353 /* Toss data on the floor */
354 break;
356 fr->dataLen -= len;
358 break;
360 case FCGI_STDERR:
362 if (fr->fs_stderr == NULL)
364 fr->fs_stderr = ap_palloc(p, FCGI_SERVER_MAX_STDERR_LINE_LEN + 1);
367 /* We're gonna consume all thats here */
368 fr->dataLen -= len;
370 while (len > 0)
372 char *null, *end, *start = fr->fs_stderr;
374 /* Get as much as will fit in the buffer */
375 int get_len = min(len, FCGI_SERVER_MAX_STDERR_LINE_LEN - fr->fs_stderr_len);
376 fcgi_buf_get_to_block(fr->serverInputBuffer, start + fr->fs_stderr_len, get_len);
377 len -= get_len;
378 fr->fs_stderr_len += get_len;
379 *(start + fr->fs_stderr_len) = '\0';
381 /* Disallow nulls, we could be nicer but this is the motivator */
382 while ((null = memchr(start, '\0', fr->fs_stderr_len)))
384 int discard = ++null - start;
385 ap_log_rerror(FCGI_LOG_ERR_NOERRNO, fr->r,
386 "FastCGI: server \"%s\" sent a null character in the stderr stream!?, "
387 "discarding %d characters of stderr", fr->fs_path, discard);
388 start = null;
389 fr->fs_stderr_len -= discard;
392 /* Print as much as possible */
393 while ((end = strpbrk(start, "\r\n")))
395 if (start != end)
397 *end = '\0';
398 ap_log_rerror(FCGI_LOG_ERR_NOERRNO, fr->r, "FastCGI: server \"%s\" stderr: %s", fr->fs_path, start);
400 end += strspn(++end, "\r\n");
401 fr->fs_stderr_len -= (end - start);
402 start = end;
405 if (fr->fs_stderr_len)
407 if (start != fr->fs_stderr)
409 /* Move leftovers down */
410 memmove(fr->fs_stderr, start, fr->fs_stderr_len);
412 else if (fr->fs_stderr_len == FCGI_SERVER_MAX_STDERR_LINE_LEN)
414 /* Full buffer, dump it and complain */
415 ap_log_rerror(FCGI_LOG_ERR_NOERRNO, fr->r, "FastCGI: server \"%s\" stderr: %s", fr->fs_path, fr->fs_stderr);
416 ap_log_rerror(FCGI_LOG_WARN_NOERRNO, fr->r,
417 "FastCGI: too much stderr received from server \"%s\", "
418 "increase FCGI_SERVER_MAX_STDERR_LINE_LEN (%d) and rebuild "
419 "or use \"\\n\" to terminate lines",
420 fr->fs_path, FCGI_SERVER_MAX_STDERR_LINE_LEN);
421 fr->fs_stderr_len = 0;
425 break;
427 case FCGI_END_REQUEST:
428 if (!fr->readingEndRequestBody) {
429 if (fr->dataLen != sizeof(FCGI_EndRequestBody)) {
430 ap_log_rerror(FCGI_LOG_ERR_NOERRNO, fr->r,
431 "FastCGI: comm with server \"%s\" aborted: protocol error: invalid FCGI_END_REQUEST size: "
432 "%d != sizeof(FCGI_EndRequestBody)(%d)",
433 fr->fs_path, fr->dataLen, sizeof(FCGI_EndRequestBody));
434 return SERVER_ERROR;
436 fr->readingEndRequestBody = TRUE;
438 if (len>0) {
439 fcgi_buf_get_to_buf(fr->erBufPtr, fr->serverInputBuffer, len);
440 fr->dataLen -= len;
442 if (fr->dataLen == 0) {
443 FCGI_EndRequestBody *erBody = &fr->endRequestBody;
444 fcgi_buf_get_to_block(
445 fr->erBufPtr, (char *) &fr->endRequestBody,
446 sizeof(FCGI_EndRequestBody));
447 if (erBody->protocolStatus != FCGI_REQUEST_COMPLETE) {
449 * XXX: What to do with FCGI_OVERLOADED?
451 ap_log_rerror(FCGI_LOG_ERR_NOERRNO, fr->r,
452 "FastCGI: comm with server \"%s\" aborted: protocol error: invalid FCGI_END_REQUEST status: "
453 "%d != FCGI_REQUEST_COMPLETE(%d)", fr->fs_path,
454 erBody->protocolStatus, FCGI_REQUEST_COMPLETE);
455 return SERVER_ERROR;
457 fr->exitStatus = (erBody->appStatusB3 << 24)
458 + (erBody->appStatusB2 << 16)
459 + (erBody->appStatusB1 << 8)
460 + (erBody->appStatusB0 );
461 fr->exitStatusSet = TRUE;
462 fr->readingEndRequestBody = FALSE;
464 break;
465 case FCGI_GET_VALUES_RESULT:
466 /* XXX coming soon */
467 case FCGI_UNKNOWN_TYPE:
468 /* XXX coming soon */
471 * XXX Ignore unknown packet types from the FastCGI server.
473 default:
474 fcgi_buf_toss(fr->serverInputBuffer, len);
475 fr->dataLen -= len;
476 break;
477 } /* switch */
480 * Discard padding, then start looking for
481 * the next header.
483 if (fr->dataLen == 0) {
484 if (fr->paddingLen > 0) {
485 len = min(fr->paddingLen,
486 BufferLength(fr->serverInputBuffer));
487 fcgi_buf_toss(fr->serverInputBuffer, len);
488 fr->paddingLen -= len;
490 if (fr->paddingLen == 0) {
491 fr->gotHeader = FALSE;
494 } /* while */
495 return OK;