• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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