2 * $Id: fcgi_protocol.c,v 1.25 2003/02/03 22:59:01 robs Exp $
6 #include "fcgi_protocol.h"
13 #pragma warning( disable : 4706)
16 /*******************************************************************************
17 * Build and queue a FastCGI message header. It is the caller's
18 * responsibility to make sure that there's enough space in the buffer, and
19 * that the data bytes (specified by 'len') are queued immediately following
22 static void queue_header(fcgi_request
*fr
, unsigned char type
, unsigned int len
)
27 ASSERT(type
<= FCGI_MAXTYPE
);
28 ASSERT(len
<= 0xffff);
29 ASSERT(BufferFree(fr
->serverOutputBuffer
) >= sizeof(FCGI_Header
));
31 /* Assemble and queue the packet header. */
32 header
.version
= FCGI_VERSION
;
34 header
.requestIdB1
= (unsigned char) (fr
->requestId
>> 8);
35 header
.requestIdB0
= (unsigned char) fr
->requestId
;
36 header
.contentLengthB1
= (unsigned char) (len
/ 256); /* MSB */
37 header
.contentLengthB0
= (unsigned char) (len
% 256); /* LSB */
38 header
.paddingLength
= 0;
40 fcgi_buf_add_block(fr
->serverOutputBuffer
, (char *) &header
, sizeof(FCGI_Header
));
43 /*******************************************************************************
44 * Build a FCGI_BeginRequest message body.
46 static void build_begin_request(unsigned int role
, unsigned char keepConnection
,
47 FCGI_BeginRequestBody
*body
)
49 ASSERT((role
>> 16) == 0);
50 body
->roleB1
= (unsigned char) (role
>> 8);
51 body
->roleB0
= (unsigned char) role
;
52 body
->flags
= (unsigned char) ((keepConnection
) ? FCGI_KEEP_CONN
: 0);
53 memset(body
->reserved
, 0, sizeof(body
->reserved
));
56 /*******************************************************************************
57 * Build and queue a FastCGI "Begin Request" message.
59 void fcgi_protocol_queue_begin_request(fcgi_request
*fr
)
61 FCGI_BeginRequestBody body
;
62 int bodySize
= sizeof(FCGI_BeginRequestBody
);
64 /* We should be the first ones to use this buffer */
65 ASSERT(BufferLength(fr
->serverOutputBuffer
) == 0);
67 build_begin_request(fr
->role
, FALSE
, &body
);
68 queue_header(fr
, FCGI_BEGIN_REQUEST
, bodySize
);
69 fcgi_buf_add_block(fr
->serverOutputBuffer
, (char *) &body
, bodySize
);
72 /*******************************************************************************
73 * Build a FastCGI name-value pair (env) header.
75 static void build_env_header(int nameLen
, int valueLen
,
76 unsigned char *headerBuffPtr
, int *headerLenPtr
)
78 unsigned char *startHeaderBuffPtr
= headerBuffPtr
;
83 *headerBuffPtr
++ = (unsigned char) nameLen
;
85 *headerBuffPtr
++ = (unsigned char) ((nameLen
>> 24) | 0x80);
86 *headerBuffPtr
++ = (unsigned char) (nameLen
>> 16);
87 *headerBuffPtr
++ = (unsigned char) (nameLen
>> 8);
88 *headerBuffPtr
++ = (unsigned char) nameLen
;
91 ASSERT(valueLen
>= 0);
93 if (valueLen
< 0x80) {
94 *headerBuffPtr
++ = (unsigned char) valueLen
;
96 *headerBuffPtr
++ = (unsigned char) ((valueLen
>> 24) | 0x80);
97 *headerBuffPtr
++ = (unsigned char) (valueLen
>> 16);
98 *headerBuffPtr
++ = (unsigned char) (valueLen
>> 8);
99 *headerBuffPtr
++ = (unsigned char) valueLen
;
101 *headerLenPtr
= headerBuffPtr
- startHeaderBuffPtr
;
104 /* A static fn stolen from Apache's util_script.c...
105 * Obtain the Request-URI from the original request-line, returning
106 * a new string from the request pool containing the URI or "".
108 static char *apache_original_uri(request_rec
*r
)
112 if (r
->the_request
== NULL
)
113 return (char *) ap_pcalloc(r
->pool
, 1);
115 first
= r
->the_request
; /* use the request-line */
117 while (*first
&& !ap_isspace(*first
))
118 ++first
; /* skip over the method */
120 while (ap_isspace(*first
))
121 ++first
; /* and the space(s) */
124 while (*last
&& !ap_isspace(*last
))
125 ++last
; /* end at next whitespace */
127 return ap_pstrndup(r
->pool
, first
, last
- first
);
130 /* Based on Apache's ap_add_cgi_vars() in util_script.c.
131 * Apache's spins in sub_req_lookup_uri() trying to setup PATH_TRANSLATED,
132 * so we just don't do that part.
134 static void add_auth_cgi_vars(request_rec
*r
, const int compat
)
136 table
*e
= r
->subprocess_env
;
138 ap_table_setn(e
, "GATEWAY_INTERFACE", "CGI/1.1");
139 ap_table_setn(e
, "SERVER_PROTOCOL", r
->protocol
);
140 ap_table_setn(e
, "REQUEST_METHOD", r
->method
);
141 ap_table_setn(e
, "QUERY_STRING", r
->args
? r
->args
: "");
142 ap_table_setn(e
, "REQUEST_URI", apache_original_uri(r
));
144 /* The FastCGI spec precludes sending of CONTENT_LENGTH, PATH_INFO,
145 * PATH_TRANSLATED, and SCRIPT_NAME (for some reason?). PATH_TRANSLATED we
146 * don't have, its the variable that causes Apache to break trying to set
147 * up (and thus the reason this fn exists vs. using ap_add_cgi_vars()). */
149 ap_table_unset(e
, "CONTENT_LENGTH");
153 /* Note that the code below special-cases scripts run from includes,
154 * because it "knows" that the sub_request has been hacked to have the
155 * args and path_info of the original request, and not any that may have
156 * come with the script URI in the include command. Ugh. */
157 if (!strcmp(r
->protocol
, "INCLUDED")) {
158 ap_table_setn(e
, "SCRIPT_NAME", r
->uri
);
159 if (r
->path_info
&& *r
->path_info
)
160 ap_table_setn(e
, "PATH_INFO", r
->path_info
);
162 else if (!r
->path_info
|| !*r
->path_info
)
163 ap_table_setn(e
, "SCRIPT_NAME", r
->uri
);
165 int path_info_start
= ap_find_path_info(r
->uri
, r
->path_info
);
167 ap_table_setn(e
, "SCRIPT_NAME", ap_pstrndup(r
->pool
, r
->uri
, path_info_start
));
168 ap_table_setn(e
, "PATH_INFO", r
->path_info
);
172 static void add_pass_header_vars(fcgi_request
*fr
)
174 const array_header
*ph
= fr
->dynamic
? dynamic_pass_headers
: fr
->fs
->pass_headers
;
177 const char **elt
= (const char **)ph
->elts
;
180 for ( ; i
; --i
, ++elt
) {
181 const char *val
= ap_table_get(fr
->r
->headers_in
, *elt
);
183 ap_table_setn(fr
->r
->subprocess_env
, *elt
, val
);
189 /*******************************************************************************
190 * Build and queue the environment name-value pairs. Returns TRUE if the
191 * complete ENV was buffered, FALSE otherwise. Note: envp is updated to
192 * reflect the current position in the ENV.
194 int fcgi_protocol_queue_env(request_rec
*r
, fcgi_request
*fr
, env_status
*env
)
198 if (env
->envp
== NULL
) {
199 ap_add_common_vars(r
);
200 add_pass_header_vars(fr
);
202 if (fr
->role
== FCGI_RESPONDER
)
205 add_auth_cgi_vars(r
, fr
->auth_compat
);
207 env
->envp
= ap_create_environment(r
->pool
, r
->subprocess_env
);
215 env
->equalPtr
= strchr(*env
->envp
, '=');
216 ASSERT(env
->equalPtr
!= NULL
);
217 env
->nameLen
= env
->equalPtr
- *env
->envp
;
218 env
->valueLen
= strlen(++env
->equalPtr
);
219 build_env_header(env
->nameLen
, env
->valueLen
, env
->headerBuff
, &env
->headerLen
);
220 env
->totalLen
= env
->headerLen
+ env
->nameLen
+ env
->valueLen
;
225 if (BufferFree(fr
->serverOutputBuffer
) < (int)(sizeof(FCGI_Header
) + env
->headerLen
)) {
228 queue_header(fr
, FCGI_PARAMS
, env
->totalLen
);
229 fcgi_buf_add_block(fr
->serverOutputBuffer
, (char *)env
->headerBuff
, env
->headerLen
);
234 charCount
= fcgi_buf_add_block(fr
->serverOutputBuffer
, *env
->envp
, env
->nameLen
);
235 if (charCount
!= env
->nameLen
) {
236 *env
->envp
+= charCount
;
237 env
->nameLen
-= charCount
;
244 charCount
= fcgi_buf_add_block(fr
->serverOutputBuffer
, env
->equalPtr
, env
->valueLen
);
245 if (charCount
!= env
->valueLen
) {
246 env
->equalPtr
+= charCount
;
247 env
->valueLen
-= charCount
;
255 if (BufferFree(fr
->serverOutputBuffer
) < sizeof(FCGI_Header
)) {
258 queue_header(fr
, FCGI_PARAMS
, 0);
262 /*******************************************************************************
263 * Queue data from the client input buffer to the FastCGI server output
264 * buffer (encapsulating the data in FastCGI protocol messages).
266 void fcgi_protocol_queue_client_buffer(fcgi_request
*fr
)
269 int in_len
, out_free
;
275 * If there's some client data and room for at least one byte
276 * of data in the output buffer (after protocol overhead), then
277 * move some data to the output buffer.
279 in_len
= BufferLength(fr
->clientInputBuffer
);
280 out_free
= max(0, BufferFree(fr
->serverOutputBuffer
) - sizeof(FCGI_Header
));
281 movelen
= min(in_len
, out_free
);
283 queue_header(fr
, FCGI_STDIN
, movelen
);
284 fcgi_buf_get_to_buf(fr
->serverOutputBuffer
, fr
->clientInputBuffer
, movelen
);
288 * If all the client data has been sent, and there's room
289 * in the output buffer, indicate EOF.
291 if (movelen
== in_len
&& fr
->expectingClientContent
<= 0
292 && BufferFree(fr
->serverOutputBuffer
) >= sizeof(FCGI_Header
))
294 queue_header(fr
, FCGI_STDIN
, 0);
299 /*******************************************************************************
300 * Read FastCGI protocol messages from the FastCGI server input buffer into
301 * fr->header when parsing headers, to fr->fs_stderr when reading stderr data,
302 * or to the client output buffer otherwises.
304 int fcgi_protocol_dequeue(pool
*p
, fcgi_request
*fr
)
309 while (BufferLength(fr
->serverInputBuffer
) > 0) {
311 * State #1: looking for the next complete packet header.
313 if (fr
->gotHeader
== FALSE
) {
314 if (BufferLength(fr
->serverInputBuffer
) < sizeof(FCGI_Header
)) {
317 fcgi_buf_get_to_block(fr
->serverInputBuffer
, (char *) &header
,
318 sizeof(FCGI_Header
));
320 * XXX: Better handling of packets with other version numbers
321 * and other packet problems.
323 if (header
.version
!= FCGI_VERSION
) {
324 ap_log_rerror(FCGI_LOG_ERR_NOERRNO
, fr
->r
,
325 "FastCGI: comm with server \"%s\" aborted: protocol error: invalid version: %d != FCGI_VERSION(%d)",
326 fr
->fs_path
, header
.version
, FCGI_VERSION
);
327 return HTTP_INTERNAL_SERVER_ERROR
;
329 if (header
.type
> FCGI_MAXTYPE
) {
330 ap_log_rerror(FCGI_LOG_ERR_NOERRNO
, fr
->r
,
331 "FastCGI: comm with server \"%s\" aborted: protocol error: invalid type: %d > FCGI_MAXTYPE(%d)",
332 fr
->fs_path
, header
.type
, FCGI_MAXTYPE
);
333 return HTTP_INTERNAL_SERVER_ERROR
;
336 fr
->packetType
= header
.type
;
337 fr
->dataLen
= (header
.contentLengthB1
<< 8)
338 + header
.contentLengthB0
;
339 fr
->gotHeader
= TRUE
;
340 fr
->paddingLen
= header
.paddingLength
;
344 * State #2: got a header, and processing packet bytes.
346 len
= min(fr
->dataLen
, BufferLength(fr
->serverInputBuffer
));
348 switch (fr
->packetType
) {
351 switch(fr
->parseHeader
) {
352 case SCAN_CGI_READING_HEADERS
:
353 fcgi_buf_get_to_array(fr
->serverInputBuffer
, fr
->header
, len
);
355 case SCAN_CGI_FINISHED
:
356 len
= min(BufferFree(fr
->clientOutputBuffer
), len
);
358 fcgi_buf_get_to_buf(fr
->clientOutputBuffer
, fr
->serverInputBuffer
, len
);
364 /* Toss data on the floor */
365 fcgi_buf_removed(fr
->serverInputBuffer
, len
);
374 if (fr
->fs_stderr
== NULL
)
376 fr
->fs_stderr
= ap_palloc(p
, FCGI_SERVER_MAX_STDERR_LINE_LEN
+ 1);
379 /* We're gonna consume all thats here */
384 char *null
, *end
, *start
= fr
->fs_stderr
;
386 /* Get as much as will fit in the buffer */
387 int get_len
= min(len
, FCGI_SERVER_MAX_STDERR_LINE_LEN
- fr
->fs_stderr_len
);
388 fcgi_buf_get_to_block(fr
->serverInputBuffer
, start
+ fr
->fs_stderr_len
, get_len
);
390 fr
->fs_stderr_len
+= get_len
;
391 *(start
+ fr
->fs_stderr_len
) = '\0';
393 /* Disallow nulls, we could be nicer but this is the motivator */
394 while ((null
= memchr(start
, '\0', fr
->fs_stderr_len
)))
396 int discard
= ++null
- start
;
397 ap_log_rerror(FCGI_LOG_ERR_NOERRNO
, fr
->r
,
398 "FastCGI: server \"%s\" sent a null character in the stderr stream!?, "
399 "discarding %d characters of stderr", fr
->fs_path
, discard
);
401 fr
->fs_stderr_len
-= discard
;
404 /* Print as much as possible */
405 while ((end
= strpbrk(start
, "\r\n")))
410 ap_log_rerror(FCGI_LOG_ERR_NOERRNO
, fr
->r
,
411 "FastCGI: server \"%s\" stderr: %s", fr
->fs_path
, start
);
414 end
+= strspn(end
, "\r\n");
415 fr
->fs_stderr_len
-= (end
- start
);
419 if (fr
->fs_stderr_len
)
421 if (start
!= fr
->fs_stderr
)
423 /* Move leftovers down */
424 memmove(fr
->fs_stderr
, start
, fr
->fs_stderr_len
);
426 else if (fr
->fs_stderr_len
== FCGI_SERVER_MAX_STDERR_LINE_LEN
)
428 /* Full buffer, dump it and complain */
429 ap_log_rerror(FCGI_LOG_ERR_NOERRNO
, fr
->r
,
430 "FastCGI: server \"%s\" stderr: %s", fr
->fs_path
, fr
->fs_stderr
);
431 ap_log_rerror(FCGI_LOG_WARN_NOERRNO
, fr
->r
,
432 "FastCGI: too much stderr received from server \"%s\", "
433 "increase FCGI_SERVER_MAX_STDERR_LINE_LEN (%d) and rebuild "
434 "or use \"\\n\" to terminate lines",
435 fr
->fs_path
, FCGI_SERVER_MAX_STDERR_LINE_LEN
);
436 fr
->fs_stderr_len
= 0;
442 case FCGI_END_REQUEST
:
443 if (!fr
->readingEndRequestBody
) {
444 if (fr
->dataLen
!= sizeof(FCGI_EndRequestBody
)) {
445 ap_log_rerror(FCGI_LOG_ERR_NOERRNO
, fr
->r
,
446 "FastCGI: comm with server \"%s\" aborted: protocol error: "
447 "invalid FCGI_END_REQUEST size: "
448 "%d != sizeof(FCGI_EndRequestBody)(%d)",
449 fr
->fs_path
, fr
->dataLen
, sizeof(FCGI_EndRequestBody
));
450 return HTTP_INTERNAL_SERVER_ERROR
;
452 fr
->readingEndRequestBody
= TRUE
;
455 fcgi_buf_get_to_buf(fr
->erBufPtr
, fr
->serverInputBuffer
, len
);
458 if (fr
->dataLen
== 0) {
459 FCGI_EndRequestBody
*erBody
= &fr
->endRequestBody
;
460 fcgi_buf_get_to_block(
461 fr
->erBufPtr
, (char *) &fr
->endRequestBody
,
462 sizeof(FCGI_EndRequestBody
));
463 if (erBody
->protocolStatus
!= FCGI_REQUEST_COMPLETE
) {
465 * XXX: What to do with FCGI_OVERLOADED?
467 ap_log_rerror(FCGI_LOG_ERR_NOERRNO
, fr
->r
,
468 "FastCGI: comm with server \"%s\" aborted: protocol error: invalid FCGI_END_REQUEST status: "
469 "%d != FCGI_REQUEST_COMPLETE(%d)", fr
->fs_path
,
470 erBody
->protocolStatus
, FCGI_REQUEST_COMPLETE
);
471 return HTTP_INTERNAL_SERVER_ERROR
;
473 fr
->exitStatus
= (erBody
->appStatusB3
<< 24)
474 + (erBody
->appStatusB2
<< 16)
475 + (erBody
->appStatusB1
<< 8)
476 + (erBody
->appStatusB0
);
477 fr
->exitStatusSet
= TRUE
;
478 fr
->readingEndRequestBody
= FALSE
;
481 case FCGI_GET_VALUES_RESULT
:
482 /* XXX coming soon */
483 case FCGI_UNKNOWN_TYPE
:
484 /* XXX coming soon */
487 * XXX Ignore unknown packet types from the FastCGI server.
490 fcgi_buf_toss(fr
->serverInputBuffer
, len
);
496 * Discard padding, then start looking for
499 if (fr
->dataLen
== 0) {
500 if (fr
->paddingLen
> 0) {
501 len
= min(fr
->paddingLen
,
502 BufferLength(fr
->serverInputBuffer
));
503 fcgi_buf_toss(fr
->serverInputBuffer
, len
);
504 fr
->paddingLen
-= len
;
506 if (fr
->paddingLen
== 0) {
507 fr
->gotHeader
= FALSE
;