largerpost.inc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. The previous chapter introduced a way to upload data to the server, but the developed example program
  2. has some shortcomings, such as not being able to handle larger chunks of data. In this chapter, we
  3. are going to discuss a more advanced server program that allows clients to upload a file in order to
  4. have it stored on the server's filesystem. The server shall also watch and limit the number of
  5. clients concurrently uploading, responding with a proper busy message if necessary.
  6. @heading Prepared answers
  7. We choose to operate the server with the @code{SELECT_INTERNALLY} method. This makes it easier to
  8. synchronize the global states at the cost of possible delays for other connections if the processing
  9. of a request is too slow. One of these variables that needs to be shared for all connections is the
  10. total number of clients that are uploading.
  11. @verbatim
  12. #define MAXCLIENTS 2
  13. static unsigned int nr_of_uploading_clients = 0;
  14. @end verbatim
  15. @noindent
  16. If there are too many clients uploading, we want the server to respond to all requests with a busy
  17. message.
  18. @verbatim
  19. const char* busypage =
  20. "<html><body>This server is busy, please try again later.</body></html>";
  21. @end verbatim
  22. @noindent
  23. Otherwise, the server will send a @emph{form} that informs the user of the current number of uploading clients,
  24. and ask her to pick a file on her local filesystem which is to be uploaded.
  25. @verbatim
  26. const char* askpage = "<html><body>\n\
  27. Upload a file, please!<br>\n\
  28. There are %u clients uploading at the moment.<br>\n\
  29. <form action=\"/filepost\" method=\"post\" \
  30. enctype=\"multipart/form-data\">\n\
  31. <input name=\"file\" type=\"file\">\n\
  32. <input type=\"submit\" value=\" Send \"></form>\n\
  33. </body></html>";
  34. @end verbatim
  35. @noindent
  36. If the upload has succeeded, the server will respond with a message saying so.
  37. @verbatim
  38. const char* completepage = "<html><body>The upload has been completed.</body></html>";
  39. @end verbatim
  40. @noindent
  41. We want the server to report internal errors, such as memory shortage or file access problems,
  42. adequately.
  43. @verbatim
  44. const char* servererrorpage
  45. = "<html><body>An internal server error has occured.</body></html>";
  46. const char* fileexistspage
  47. = "<html><body>This file already exists.</body></html>";
  48. @end verbatim
  49. @noindent
  50. It would be tolerable to send all these responses undifferentiated with a @code{200 HTTP_OK}
  51. status code but in order to improve the @code{HTTP} conformance of our server a bit, we extend the
  52. @code{send_page} function so that it accepts individual status codes.
  53. @verbatim
  54. static int
  55. send_page (struct MHD_Connection *connection,
  56. const char* page, int status_code)
  57. {
  58. int ret;
  59. struct MHD_Response *response;
  60. response = MHD_create_response_from_buffer (strlen (page), (void*) page,
  61. MHD_RESPMEM_MUST_COPY);
  62. if (!response) return MHD_NO;
  63. ret = MHD_queue_response (connection, status_code, response);
  64. MHD_destroy_response (response);
  65. return ret;
  66. }
  67. @end verbatim
  68. @noindent
  69. Note how we ask @emph{MHD} to make its own copy of the message data. The reason behind this will
  70. become clear later.
  71. @heading Connection cycle
  72. The decision whether the server is busy or not is made right at the beginning of the connection. To
  73. do that at this stage is especially important for @emph{POST} requests because if no response is
  74. queued at this point, and @code{MHD_YES} returned, @emph{MHD} will not sent any queued messages until
  75. a postprocessor has been created and the post iterator is called at least once.
  76. @verbatim
  77. static int
  78. answer_to_connection (void *cls, struct MHD_Connection *connection,
  79. const char *url,
  80. const char *method, const char *version,
  81. const char *upload_data,
  82. size_t *upload_data_size, void **con_cls)
  83. {
  84. if (NULL == *con_cls)
  85. {
  86. struct connection_info_struct *con_info;
  87. if (nr_of_uploading_clients >= MAXCLIENTS)
  88. return send_page(connection, busypage, MHD_HTTP_SERVICE_UNAVAILABLE);
  89. @end verbatim
  90. @noindent
  91. If the server is not busy, the @code{connection_info} structure is initialized as usual, with
  92. the addition of a filepointer for each connection.
  93. @verbatim
  94. con_info = malloc (sizeof (struct connection_info_struct));
  95. if (NULL == con_info) return MHD_NO;
  96. con_info->fp = 0;
  97. if (0 == strcmp (method, "POST"))
  98. {
  99. ...
  100. }
  101. else con_info->connectiontype = GET;
  102. *con_cls = (void*) con_info;
  103. return MHD_YES;
  104. }
  105. @end verbatim
  106. @noindent
  107. For @emph{POST} requests, the postprocessor is created and we register a new uploading client. From
  108. this point on, there are many possible places for errors to occur that make it necessary to interrupt
  109. the uploading process. We need a means of having the proper response message ready at all times.
  110. Therefore, the @code{connection_info} structure is extended to hold the most current response
  111. message so that whenever a response is sent, the client will get the most informative message. Here,
  112. the structure is initialized to "no error".
  113. @verbatim
  114. if (0 == strcmp (method, "POST"))
  115. {
  116. con_info->postprocessor
  117. = MHD_create_post_processor (connection, POSTBUFFERSIZE,
  118. iterate_post, (void*) con_info);
  119. if (NULL == con_info->postprocessor)
  120. {
  121. free (con_info);
  122. return MHD_NO;
  123. }
  124. nr_of_uploading_clients++;
  125. con_info->connectiontype = POST;
  126. con_info->answercode = MHD_HTTP_OK;
  127. con_info->answerstring = completepage;
  128. }
  129. else con_info->connectiontype = GET;
  130. @end verbatim
  131. @noindent
  132. If the connection handler is called for the second time, @emph{GET} requests will be answered with
  133. the @emph{form}. We can keep the buffer under function scope, because we asked @emph{MHD} to make its
  134. own copy of it for as long as it is needed.
  135. @verbatim
  136. if (0 == strcmp (method, "GET"))
  137. {
  138. int ret;
  139. char buffer[1024];
  140. sprintf (buffer, askpage, nr_of_uploading_clients);
  141. return send_page (connection, buffer, MHD_HTTP_OK);
  142. }
  143. @end verbatim
  144. @noindent
  145. The rest of the @code{answer_to_connection} function is very similar to the @code{simplepost.c}
  146. example, except the more flexible content of the responses. The @emph{POST} data is processed until
  147. there is none left and the execution falls through to return an error page if the connection
  148. constituted no expected request method.
  149. @verbatim
  150. if (0 == strcmp (method, "POST"))
  151. {
  152. struct connection_info_struct *con_info = *con_cls;
  153. if (0 != *upload_data_size)
  154. {
  155. MHD_post_process (con_info->postprocessor,
  156. upload_data, *upload_data_size);
  157. *upload_data_size = 0;
  158. return MHD_YES;
  159. }
  160. else
  161. return send_page (connection, con_info->answerstring,
  162. con_info->answercode);
  163. }
  164. return send_page(connection, errorpage, MHD_HTTP_BAD_REQUEST);
  165. }
  166. @end verbatim
  167. @noindent
  168. @heading Storing to data
  169. Unlike the @code{simplepost.c} example, here it is to be expected that post iterator will be called
  170. several times now. This means that for any given connection (there might be several concurrent of them)
  171. the posted data has to be written to the correct file. That is why we store a file handle in every
  172. @code{connection_info}, so that the it is preserved between successive iterations.
  173. @verbatim
  174. static int
  175. iterate_post (void *coninfo_cls, enum MHD_ValueKind kind,
  176. const char *key,
  177. const char *filename, const char *content_type,
  178. const char *transfer_encoding, const char *data,
  179. uint64_t off, size_t size)
  180. {
  181. struct connection_info_struct *con_info = coninfo_cls;
  182. @end verbatim
  183. @noindent
  184. Because the following actions depend heavily on correct file processing, which might be error prone,
  185. we default to reporting internal errors in case anything will go wrong.
  186. @verbatim
  187. con_info->answerstring = servererrorpage;
  188. con_info->answercode = MHD_HTTP_INTERNAL_SERVER_ERROR;
  189. @end verbatim
  190. @noindent
  191. In the "askpage" @emph{form}, we told the client to label its post data with the "file" key. Anything else
  192. would be an error.
  193. @verbatim
  194. if (0 != strcmp (key, "file")) return MHD_NO;
  195. @end verbatim
  196. @noindent
  197. If the iterator is called for the first time, no file will have been opened yet. The @code{filename}
  198. string contains the name of the file (without any paths) the user selected on his system. We want to
  199. take this as the name the file will be stored on the server and make sure no file of that name exists
  200. (or is being uploaded) before we create one (note that the code below technically contains a
  201. race between the two "fopen" calls, but we will overlook this for portability sake).
  202. @verbatim
  203. if (!con_info->fp)
  204. {
  205. if (NULL != (fp = fopen (filename, "rb")) )
  206. {
  207. fclose (fp);
  208. con_info->answerstring = fileexistspage;
  209. con_info->answercode = MHD_HTTP_FORBIDDEN;
  210. return MHD_NO;
  211. }
  212. con_info->fp = fopen (filename, "ab");
  213. if (!con_info->fp) return MHD_NO;
  214. }
  215. @end verbatim
  216. @noindent
  217. Occasionally, the iterator function will be called even when there are 0 new bytes to process. The
  218. server only needs to write data to the file if there is some.
  219. @verbatim
  220. if (size > 0)
  221. {
  222. if (!fwrite (data, size, sizeof(char), con_info->fp))
  223. return MHD_NO;
  224. }
  225. @end verbatim
  226. @noindent
  227. If this point has been reached, everything worked well for this iteration and the response can
  228. be set to success again. If the upload has finished, this iterator function will not be called again.
  229. @verbatim
  230. con_info->answerstring = completepage;
  231. con_info->answercode = MHD_HTTP_OK;
  232. return MHD_YES;
  233. }
  234. @end verbatim
  235. @noindent
  236. The new client was registered when the postprocessor was created. Likewise, we unregister the client
  237. on destroying the postprocessor when the request is completed.
  238. @verbatim
  239. void request_completed (void *cls, struct MHD_Connection *connection,
  240. void **con_cls,
  241. enum MHD_RequestTerminationCode toe)
  242. {
  243. struct connection_info_struct *con_info = *con_cls;
  244. if (NULL == con_info) return;
  245. if (con_info->connectiontype == POST)
  246. {
  247. if (NULL != con_info->postprocessor)
  248. {
  249. MHD_destroy_post_processor (con_info->postprocessor);
  250. nr_of_uploading_clients--;
  251. }
  252. if (con_info->fp) fclose (con_info->fp);
  253. }
  254. free (con_info);
  255. *con_cls = NULL;
  256. }
  257. @end verbatim
  258. @noindent
  259. This is essentially the whole example @code{largepost.c}.
  260. @heading Remarks
  261. Now that the clients are able to create files on the server, security aspects are becoming even more
  262. important than before. Aside from proper client authentication, the server should always make sure
  263. explicitly that no files will be created outside of a dedicated upload directory. In particular,
  264. filenames must be checked to not contain strings like "../".