2 * simple and trivial SCGI server with hard-coded results for use in unit tests
3 * - listens on STDIN_FILENO (socket on STDIN_FILENO must be set up by caller)
4 * - processes a single SCGI request at a time
5 * - arbitrary limitation: reads request headers netstring up to 64k in size
6 * - expect recv data for request headers netstring every 10ms or less
7 * - no read timeouts for request body; might block reading request body
8 * - no write timeouts; might block writing response
11 #include <sys/types.h>
12 #include <sys/socket.h>
24 #define MSG_DONTWAIT 0
28 static char buf
[65536];
32 scgi_getenv(char *r
, const unsigned long rlen
, const char * const name
)
35 * if many lookups are done, then should use more efficient data structure*/
36 char * const end
= r
+rlen
;
38 const size_t len
= strlen(name
);
40 if (0 == strcmp(r
, name
)) return r
+len
+1;
42 z
= memchr(r
, '\0', (size_t)(end
-r
));
43 if (NULL
== z
) return NULL
;
44 z
= memchr(z
+1, '\0', (size_t)(end
-r
));
45 if (NULL
== z
) return NULL
;
53 scgi_process (const int fd
)
55 ssize_t rd
= 0, offset
= 0;
61 assert(fd
== STDOUT_FILENO
); /*(required for response sent with printf())*/
62 fcntl(fd
, F_SETFL
, fcntl(fd
, F_GETFL
) | O_NONBLOCK
);
65 struct pollfd pfd
= { fd
, POLLIN
, 0 };
66 switch (poll(&pfd
, 1, 10)) { /* 10ms timeout */
67 default: /* 1; the only pfd has revents */
71 pfd
.revents
|= POLLERR
;
74 if (!(pfd
.revents
& POLLIN
))
77 rd
= recv(fd
, buf
+offset
, sizeof(buf
)-offset
, MSG_DONTWAIT
);
78 } while (rd
< 0 && errno
== EINTR
);
82 p
= memchr(buf
, ':', offset
);
85 else if (errno
== EAGAIN
|| errno
== EWOULDBLOCK
)
89 } while (NULL
== (p
= memchr(buf
,':',offset
)) && offset
< 21);
91 return; /* timeout or error receiving start of netstring */
92 rlen
= strtoul(buf
, &p
, 10);
93 if (*buf
== '-' || *p
!= ':' || p
== buf
|| rlen
== ULONG_MAX
)
94 return; /* invalid netstring (and rlen == ULONG_MAX is too long)*/
95 if (rlen
> sizeof(buf
) - (p
- buf
) - 2)
96 return; /* netstring longer than arbitrary limit we accept here */
97 rlen
+= (p
- buf
) + 2;
99 while ((ssize_t
)rlen
< offset
) {
100 struct pollfd pfd
= { fd
, POLLIN
, 0 };
101 switch (poll(&pfd
, 1, 10)) { /* 10ms timeout */
102 default: /* 1; the only pfd has revents */
105 case 0: /* timeout */
106 pfd
.revents
|= POLLERR
;
109 if (!(pfd
.revents
& POLLIN
))
112 rd
= recv(fd
, buf
+offset
, sizeof(buf
)-offset
, MSG_DONTWAIT
);
113 } while (rd
< 0 && errno
== EINTR
);
118 else if (errno
== EAGAIN
|| errno
== EWOULDBLOCK
)
123 if (offset
< (ssize_t
)rlen
)
124 return; /* timeout or error receiving netstring */
125 if (buf
[rlen
-1] != ',')
126 return; /* invalid netstring */
127 rlen
-= (p
- buf
) + 2;
130 /* not checking for empty headers in SCGI request (empty values allowed) */
132 /* SCGI request must contain "SCGI" header with value "1" */
133 p
= scgi_getenv(r
, rlen
, "SCGI");
134 if (NULL
== p
|| p
[0] != '1' || p
[1] != '\0')
135 return; /* missing or invalid SCGI header */
137 /* CONTENT_LENGTH must be first header in SCGI request; always required */
138 if (0 != strcmp(r
, "CONTENT_LENGTH"))
139 return; /* missing CONTENT_LENGTH */
142 cl
= strtoll(r
+sizeof("CONTENT_LENGTH"), &p
, 10);
143 if (*p
!= '\0' || p
== r
+sizeof("CONTENT_LENGTH") || cl
< 0 || 0 != errno
)
144 return; /* invalid CONTENT_LENGTH */
146 fcntl(fd
, F_SETFL
, fcntl(fd
, F_GETFL
) & ~O_NONBLOCK
);
148 /* read,discard request body (currently ignored in these SCGI unit tests)
149 * (make basic effort to read body; ignore any timeouts or errors here) */
150 cl
-= (offset
- (r
+rlen
+1 - buf
));
154 rd
= recv(fd
, x
, (cl
>(long long)sizeof(x
)?sizeof(x
):(size_t)cl
), 0);
155 } while (rd
< 0 && errno
== EINTR
);
161 /*(from fcgi-responder.c, substituting scgi_getenv() for getenv())*/
163 if (NULL
!= (p
= scgi_getenv(r
, rlen
, "QUERY_STRING"))) {
164 if (0 == strcmp(p
, "lf")) {
165 printf("Status: 200 OK\n\n");
166 } else if (0 == strcmp(p
, "crlf")) {
167 printf("Status: 200 OK\r\n\r\n");
168 } else if (0 == strcmp(p
, "slow-lf")) {
169 printf("Status: 200 OK\n");
172 } else if (0 == strcmp(p
,"slow-crlf")) {
173 printf("Status: 200 OK\r\n");
176 } else if (0 == strcmp(p
, "die-at-end")) {
177 printf("Status: 200 OK\r\n\r\n");
180 printf("Status: 200 OK\r\n\r\n");
183 printf("Status: 500 Internal Foo\r\n\r\n");
186 if (0 == strcmp(p
, "path_info")) {
187 printf("%s", scgi_getenv(r
, rlen
, "PATH_INFO"));
188 } else if (0 == strcmp(p
, "script_name")) {
189 printf("%s", scgi_getenv(r
, rlen
, "SCRIPT_NAME"));
190 } else if (0 == strcmp(p
, "var")) {
191 p
= scgi_getenv(r
, rlen
, "X_LIGHTTPD_FCGI_AUTH");
192 printf("%s", p
? p
: "(no value)");
199 if (0 == num_requests
) finished
= 1;
207 fcntl(STDIN_FILENO
, F_SETFL
, fcntl(STDIN_FILENO
, F_GETFL
) & ~O_NONBLOCK
);
208 close(STDOUT_FILENO
); /*(so that accept() returns fd to STDOUT_FILENO)*/
211 fd
= accept(STDIN_FILENO
, NULL
, NULL
);
214 assert(fd
== STDOUT_FILENO
);
216 } while (fd
> 0 ? 0 == close(fd
) && !finished
: errno
== EINTR
);