2 * $Id: fcgi_protocol.c,v 1.11 2000/04/06 05:29:25 robs Exp $
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
15 static void queue_header(fcgi_request
*fr
, int type
, int len
)
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
;
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;
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);
74 *headerBuffPtr
++ = nameLen
;
76 *headerBuffPtr
++ = (nameLen
>> 24) | 0x80;
77 *headerBuffPtr
++ = (nameLen
>> 16);
78 *headerBuffPtr
++ = (nameLen
>> 8);
79 *headerBuffPtr
++ = nameLen
;
81 ap_assert(valueLen
>= 0);
83 *headerBuffPtr
++ = valueLen
;
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
)
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) */
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()). */
138 ap_table_unset(e
, "CONTENT_LENGTH");
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
);
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
;
166 const char **elt
= (const char **)ph
->elts
;
169 for ( ; i
; --i
, ++elt
) {
170 const char *val
= ap_table_get(fr
->r
->headers_in
, *elt
);
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
, char ***envp
)
185 static int headerLen
, nameLen
, valueLen
, totalLen
;
186 static char *equalPtr
;
187 static unsigned char headerBuff
[8];
188 static enum { prep
, header
, name
, value
} pass
;
192 ap_add_common_vars(r
);
193 add_pass_header_vars(fr
);
195 if (fr
->role
== FCGI_RESPONDER
)
198 add_auth_cgi_vars(r
, fr
->auth_compat
);
200 *envp
= ap_create_environment(r
->pool
, r
->subprocess_env
);
207 equalPtr
= strchr(**envp
, '=');
208 ap_assert(equalPtr
!= NULL
);
209 nameLen
= equalPtr
- **envp
;
210 valueLen
= strlen(++equalPtr
);
211 build_env_header(nameLen
, valueLen
, headerBuff
, &headerLen
);
212 totalLen
= headerLen
+ nameLen
+ valueLen
;
216 if (BufferFree(fr
->serverOutputBuffer
) < (sizeof(FCGI_Header
)+headerLen
)) {
219 queue_header(fr
, FCGI_PARAMS
, totalLen
);
220 fcgi_buf_add_block(fr
->serverOutputBuffer
, (char *)headerBuff
, headerLen
);
224 charCount
= fcgi_buf_add_block(fr
->serverOutputBuffer
, **envp
, nameLen
);
225 if (charCount
!= nameLen
) {
227 nameLen
-= charCount
;
233 charCount
= fcgi_buf_add_block(fr
->serverOutputBuffer
, equalPtr
, valueLen
);
234 if (charCount
!= valueLen
) {
235 equalPtr
+= charCount
;
236 valueLen
-= charCount
;
243 if (BufferFree(fr
->serverOutputBuffer
) < sizeof(FCGI_Header
)) {
246 queue_header(fr
, FCGI_PARAMS
, 0);
250 /*******************************************************************************
251 * Queue data from the client input buffer to the FastCGI server output
252 * buffer (encapsulating the data in FastCGI protocol messages).
254 void fcgi_protocol_queue_client_buffer(fcgi_request
*fr
)
257 int in_len
, out_free
;
263 * If there's some client data and room for at least one byte
264 * of data in the output buffer (after protocol overhead), then
265 * move some data to the output buffer.
267 in_len
= BufferLength(fr
->clientInputBuffer
);
268 out_free
= max(0, BufferFree(fr
->serverOutputBuffer
) - sizeof(FCGI_Header
));
269 movelen
= min(in_len
, out_free
);
271 queue_header(fr
, FCGI_STDIN
, movelen
);
272 fcgi_buf_get_to_buf(fr
->serverOutputBuffer
, fr
->clientInputBuffer
, movelen
);
276 * If all the client data has been sent, and there's room
277 * in the output buffer, indicate EOF.
279 if (movelen
== in_len
&& fr
->expectingClientContent
<= 0
280 && BufferFree(fr
->serverOutputBuffer
) >= sizeof(FCGI_Header
))
282 queue_header(fr
, FCGI_STDIN
, 0);
287 /*******************************************************************************
288 * Read FastCGI protocol messages from the FastCGI server input buffer into
289 * fr->header when parsing headers, to fr->fs_stderr when reading stderr data,
290 * or to the client output buffer otherwises.
292 int fcgi_protocol_dequeue(pool
*p
, fcgi_request
*fr
)
297 while (BufferLength(fr
->serverInputBuffer
) > 0) {
299 * State #1: looking for the next complete packet header.
301 if (fr
->gotHeader
== FALSE
) {
302 if (BufferLength(fr
->serverInputBuffer
) < sizeof(FCGI_Header
)) {
305 fcgi_buf_get_to_block(fr
->serverInputBuffer
, (char *) &header
,
306 sizeof(FCGI_Header
));
308 * XXX: Better handling of packets with other version numbers
309 * and other packet problems.
311 if (header
.version
!= FCGI_VERSION
) {
312 ap_log_rerror(FCGI_LOG_ERR_NOERRNO
, fr
->r
,
313 "FastCGI: comm with server \"%s\" aborted: protocol error: invalid version: %d != FCGI_VERSION(%d)",
314 fr
->fs_path
, header
.version
, FCGI_VERSION
);
317 if (header
.type
> FCGI_MAXTYPE
) {
318 ap_log_rerror(FCGI_LOG_ERR_NOERRNO
, fr
->r
,
319 "FastCGI: comm with server \"%s\" aborted: protocol error: invalid type: %d > FCGI_MAXTYPE(%d)",
320 fr
->fs_path
, header
.type
, FCGI_MAXTYPE
);
324 fr
->packetType
= header
.type
;
325 fr
->dataLen
= (header
.contentLengthB1
<< 8)
326 + header
.contentLengthB0
;
327 fr
->gotHeader
= TRUE
;
328 fr
->paddingLen
= header
.paddingLength
;
332 * State #2: got a header, and processing packet bytes.
334 len
= min(fr
->dataLen
, BufferLength(fr
->serverInputBuffer
));
336 switch (fr
->packetType
) {
339 switch(fr
->parseHeader
) {
340 case SCAN_CGI_READING_HEADERS
:
341 fcgi_buf_get_to_array(fr
->serverInputBuffer
, fr
->header
, len
);
343 case SCAN_CGI_FINISHED
:
344 len
= min(BufferFree(fr
->clientOutputBuffer
), len
);
346 fcgi_buf_get_to_buf(fr
->clientOutputBuffer
, fr
->serverInputBuffer
, len
);
352 /* Toss data on the floor */
361 if (fr
->fs_stderr
== NULL
)
363 fr
->fs_stderr
= ap_palloc(p
, FCGI_SERVER_MAX_STDERR_LINE_LEN
+ 1);
364 fr
->fs_stderr_len
= 0;
367 /* We're gonna consume all thats here */
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
, fr
->fs_stderr
+ fr
->fs_stderr_len
, get_len
);
378 fr
->fs_stderr_len
+= get_len
;
379 *(fr
->fs_stderr
+ fr
->fs_stderr_len
) = '\0';
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
);
389 fr
->fs_stderr_len
-= discard
;
392 /* Print as much as possible */
393 while ((end
= strpbrk(start
, "\r\n")))
396 ap_log_rerror(FCGI_LOG_ERR_NOERRNO
, fr
->r
, "FastCGI: server \"%s\" stderr: %s", fr
->fs_path
, start
);
397 end
+= strspn(++end
, "\r\n");
398 fr
->fs_stderr_len
-= (end
- start
);
402 if (fr
->fs_stderr_len
)
404 if (start
!= fr
->fs_stderr
)
406 /* Move leftovers down */
407 memmove(fr
->fs_stderr
, start
, fr
->fs_stderr_len
);
409 else if (fr
->fs_stderr_len
== FCGI_SERVER_MAX_STDERR_LINE_LEN
)
411 /* Full buffer, dump it and complain */
412 ap_log_rerror(FCGI_LOG_ERR_NOERRNO
, fr
->r
, "FastCGI: server \"%s\" stderr: %s", fr
->fs_path
, fr
->fs_stderr
);
413 ap_log_rerror(FCGI_LOG_WARN_NOERRNO
, fr
->r
,
414 "FastCGI: too much stderr received from server \"%s\", "
415 "increase FCGI_SERVER_MAX_STDERR_LINE_LEN (%d) and rebuild "
416 "or use \"\\n\" to terminate lines",
417 fr
->fs_path
, len
, FCGI_SERVER_MAX_STDERR_LINE_LEN
);
418 fr
->fs_stderr_len
= 0;
424 case FCGI_END_REQUEST
:
425 if (!fr
->readingEndRequestBody
) {
426 if (fr
->dataLen
!= sizeof(FCGI_EndRequestBody
)) {
427 ap_log_rerror(FCGI_LOG_ERR_NOERRNO
, fr
->r
,
428 "FastCGI: comm with server \"%s\" aborted: protocol error: invalid FCGI_END_REQUEST size: "
429 "%d != sizeof(FCGI_EndRequestBody)(%d)",
430 fr
->fs_path
, fr
->dataLen
, sizeof(FCGI_EndRequestBody
));
433 fr
->readingEndRequestBody
= TRUE
;
436 fcgi_buf_get_to_buf(fr
->erBufPtr
, fr
->serverInputBuffer
, len
);
439 if (fr
->dataLen
== 0) {
440 FCGI_EndRequestBody
*erBody
= &fr
->endRequestBody
;
441 fcgi_buf_get_to_block(
442 fr
->erBufPtr
, (char *) &fr
->endRequestBody
,
443 sizeof(FCGI_EndRequestBody
));
444 if (erBody
->protocolStatus
!= FCGI_REQUEST_COMPLETE
) {
446 * XXX: What to do with FCGI_OVERLOADED?
448 ap_log_rerror(FCGI_LOG_ERR_NOERRNO
, fr
->r
,
449 "FastCGI: comm with server \"%s\" aborted: protocol error: invalid FCGI_END_REQUEST status: "
450 "%d != FCGI_REQUEST_COMPLETE(%d)", fr
->fs_path
,
451 erBody
->protocolStatus
, FCGI_REQUEST_COMPLETE
);
454 fr
->exitStatus
= (erBody
->appStatusB3
<< 24)
455 + (erBody
->appStatusB2
<< 16)
456 + (erBody
->appStatusB1
<< 8)
457 + (erBody
->appStatusB0
);
458 fr
->exitStatusSet
= TRUE
;
459 fr
->readingEndRequestBody
= FALSE
;
462 case FCGI_GET_VALUES_RESULT
:
463 /* XXX coming soon */
464 case FCGI_UNKNOWN_TYPE
:
465 /* XXX coming soon */
468 * XXX Ignore unknown packet types from the FastCGI server.
471 fcgi_buf_toss(fr
->serverInputBuffer
, len
);
477 * Discard padding, then start looking for
480 if (fr
->dataLen
== 0) {
481 if (fr
->paddingLen
> 0) {
482 len
= min(fr
->paddingLen
,
483 BufferLength(fr
->serverInputBuffer
));
484 fcgi_buf_toss(fr
->serverInputBuffer
, len
);
485 fr
->paddingLen
-= len
;
487 if (fr
->paddingLen
== 0) {
488 fr
->gotHeader
= FALSE
;