scgi-responder.c 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. /*
  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
  9. *
  10. * Copyright(c) 2017 Glenn Strauss gstrauss()gluelogic.com All rights reserved
  11. * License: BSD 3-clause (same as lighttpd)
  12. */
  13. #include <sys/types.h>
  14. #include <sys/socket.h>
  15. #include <assert.h>
  16. #include <errno.h>
  17. #include <fcntl.h>
  18. #include <limits.h>
  19. #include <poll.h>
  20. #include <stdlib.h>
  21. #include <stdio.h>
  22. #include <string.h>
  23. #include <unistd.h>
  24. #ifndef MSG_DONTWAIT
  25. #define MSG_DONTWAIT 0
  26. #endif
  27. static int finished;
  28. static char buf[65536];
  29. static char *
  30. scgi_getenv(char *r, const unsigned long rlen, const char * const name)
  31. {
  32. /* simple search;
  33. * if many lookups are done, then should use more efficient data structure*/
  34. char * const end = r+rlen;
  35. char *z;
  36. const size_t len = strlen(name);
  37. do {
  38. if (0 == strcmp(r, name)) return r+len+1;
  39. z = memchr(r, '\0', (size_t)(end-r));
  40. if (NULL == z) return NULL;
  41. z = memchr(z+1, '\0', (size_t)(end-r));
  42. if (NULL == z) return NULL;
  43. r = z+1;
  44. } while (r < end);
  45. return NULL;
  46. }
  47. static void
  48. scgi_process (const int fd)
  49. {
  50. ssize_t rd = 0, offset = 0;
  51. char *p = NULL, *r;
  52. unsigned long rlen;
  53. long long cl;
  54. assert(fd == STDOUT_FILENO); /*(required for response sent with printf())*/
  55. fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
  56. do {
  57. struct pollfd pfd = { fd, POLLIN, 0 };
  58. switch (poll(&pfd, 1, 10)) { /* 10ms timeout */
  59. default: /* 1; the only pfd has revents */
  60. break;
  61. case -1: /* error */
  62. case 0: /* timeout */
  63. pfd.revents |= POLLERR;
  64. break;
  65. }
  66. if (!(pfd.revents & POLLIN))
  67. break;
  68. do {
  69. rd = recv(fd, buf+offset, sizeof(buf)-offset, MSG_DONTWAIT);
  70. } while (rd < 0 && errno == EINTR);
  71. if (rd > 0)
  72. offset += rd;
  73. else if (0 == rd) {
  74. p = memchr(buf, ':', offset);
  75. break;
  76. }
  77. else if (errno == EAGAIN || errno == EWOULDBLOCK)
  78. continue;
  79. else
  80. break;
  81. } while (NULL == (p = memchr(buf,':',offset)) && offset < 21);
  82. if (NULL == p)
  83. return; /* timeout or error receiving start of netstring */
  84. rlen = strtoul(buf, &p, 10);
  85. if (*buf == '-' || *p != ':' || p == buf || rlen == ULONG_MAX)
  86. return; /* invalid netstring (and rlen == ULONG_MAX is too long)*/
  87. if (rlen > sizeof(buf) - (p - buf) - 2)
  88. return; /* netstring longer than arbitrary limit we accept here */
  89. rlen += (p - buf) + 2;
  90. while ((ssize_t)rlen < offset) {
  91. struct pollfd pfd = { fd, POLLIN, 0 };
  92. switch (poll(&pfd, 1, 10)) { /* 10ms timeout */
  93. default: /* 1; the only pfd has revents */
  94. break;
  95. case -1: /* error */
  96. case 0: /* timeout */
  97. pfd.revents |= POLLERR;
  98. break;
  99. }
  100. if (!(pfd.revents & POLLIN))
  101. break;
  102. do {
  103. rd = recv(fd, buf+offset, sizeof(buf)-offset, MSG_DONTWAIT);
  104. } while (rd < 0 && errno == EINTR);
  105. if (rd > 0)
  106. offset += rd;
  107. else if (0 == rd)
  108. break;
  109. else if (errno == EAGAIN || errno == EWOULDBLOCK)
  110. continue;
  111. else
  112. break;
  113. }
  114. if (offset < (ssize_t)rlen)
  115. return; /* timeout or error receiving netstring */
  116. if (buf[rlen-1] != ',')
  117. return; /* invalid netstring */
  118. rlen -= (p - buf) + 2;
  119. r = p+1;
  120. /* not checking for empty headers in SCGI request (empty values allowed) */
  121. /* SCGI request must contain "SCGI" header with value "1" */
  122. p = scgi_getenv(r, rlen, "SCGI");
  123. if (NULL == p || p[0] != '1' || p[1] != '\0')
  124. return; /* missing or invalid SCGI header */
  125. /* CONTENT_LENGTH must be first header in SCGI request; always required */
  126. if (0 != strcmp(r, "CONTENT_LENGTH"))
  127. return; /* missing CONTENT_LENGTH */
  128. errno = 0;
  129. cl = strtoll(r+sizeof("CONTENT_LENGTH"), &p, 10);
  130. if (*p != '\0' || p == r+sizeof("CONTENT_LENGTH") || cl < 0 || 0 != errno)
  131. return; /* invalid CONTENT_LENGTH */
  132. fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK);
  133. /* read,discard request body (currently ignored in these SCGI unit tests)
  134. * (make basic effort to read body; ignore any timeouts or errors here) */
  135. cl -= (offset - (r+rlen+1 - buf));
  136. while (cl > 0) {
  137. char x[8192];
  138. do {
  139. rd = recv(fd, x, (cl>(long long)sizeof(x)?sizeof(x):(size_t)cl), 0);
  140. } while (rd < 0 && errno == EINTR);
  141. if (rd <= 0)
  142. break;
  143. cl -= rd;
  144. }
  145. /*(similar to fcgi-responder.c:fcgi_process_params())*/
  146. const char *cdata = NULL;
  147. if (NULL != (p = scgi_getenv(r, rlen, "QUERY_STRING"))) {
  148. if (0 == strcmp(p, "lf"))
  149. cdata = "Status: 200 OK\n\n";
  150. else if (0 == strcmp(p, "crlf"))
  151. cdata = "Status: 200 OK\r\n\r\n";
  152. else if (0 == strcmp(p, "slow-lf")) {
  153. printf("Status: 200 OK\n");
  154. fflush(stdout);
  155. cdata = "\n";
  156. }
  157. else if (0 == strcmp(p,"slow-crlf")) {
  158. printf("Status: 200 OK\r\n");
  159. fflush(stdout);
  160. cdata = "\r\n";
  161. }
  162. else if (0 == strcmp(p, "die-at-end")) {
  163. cdata = "Status: 200 OK\r\n\r\n";
  164. finished = 1;
  165. }
  166. else
  167. cdata = "Status: 200 OK\r\n\r\n";
  168. }
  169. else {
  170. cdata = "Status: 500 Internal Foo\r\n\r\n";
  171. p = NULL;
  172. }
  173. if (cdata) printf("%s", cdata);
  174. if (NULL == p)
  175. cdata = NULL;
  176. else if (0 == strncmp(p, "env=", 4))
  177. cdata = scgi_getenv(r, rlen, p+4);
  178. else
  179. cdata = "test123";
  180. if (cdata) printf("%s", cdata);
  181. fflush(stdout);
  182. }
  183. int
  184. main (void)
  185. {
  186. int fd;
  187. fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) & ~O_NONBLOCK);
  188. close(STDOUT_FILENO); /*(so that accept() returns fd to STDOUT_FILENO)*/
  189. do {
  190. fd = accept(STDIN_FILENO, NULL, NULL);
  191. if (fd < 0)
  192. continue;
  193. assert(fd == STDOUT_FILENO);
  194. scgi_process(fd);
  195. } while (fd > 0 ? 0 == close(fd) && !finished : errno == EINTR);
  196. return 0;
  197. }