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