]> git.vomp.tv Git - jsonserver.git/blob - httpdclient.c
Fix httpd not accepting empty last post variable
[jsonserver.git] / httpdclient.c
1 #include <stdio.h>
2 #include <string.h>
3 #include <malloc.h>
4 #include <sys/stat.h>
5 #include <unistd.h>
6 #include <fcntl.h>
7
8 #include <memory>
9
10 #include "httpdclient.h"
11
12 #include "vdrclient.h"
13
14 #define POSTBUFFERSIZE  512
15
16 std::shared_ptr<spd::logger> HTTPDClient::logger;
17 struct MHD_Daemon* HTTPDClient::httpdserver = NULL;
18 std::string HTTPDClient::docRoot;
19 std::string HTTPDClient::configDir;
20
21 bool HTTPDClient::StartServer(std::string _docRoot, int port, const char* _configDir)
22 {
23   docRoot = _docRoot;
24   configDir = std::string(_configDir);
25
26   logger = spd::get("jsonserver_spdlog");
27
28   httpdserver = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY | MHD_USE_IPv6, port, NULL, NULL,
29                            &HTTPDClient::handle_connection, NULL,
30                            MHD_OPTION_NOTIFY_CONNECTION, &HTTPDClient::connection_notify, NULL,
31                            MHD_OPTION_NOTIFY_COMPLETED, &HTTPDClient::request_completed, NULL,
32                            MHD_OPTION_CONNECTION_TIMEOUT, 70,
33                            MHD_OPTION_END);
34   if (httpdserver == NULL) { logger.reset(); return false; }
35   logger->info("HTTPDClient: Started");
36   return true;
37 }
38
39 void HTTPDClient::StopServer()
40 {
41   if (httpdserver) MHD_stop_daemon(httpdserver);
42   httpdserver = NULL;
43   logger.reset();
44 }
45
46 // Now for the libmicrohttpd callbacks
47
48 int HTTPDClient::uri_key_value(void *cls, enum MHD_ValueKind kind, const char* key, const char* value)
49 {
50   if (kind != MHD_GET_ARGUMENT_KIND) return MHD_NO;
51   HTTPDClient* httpdclient = (HTTPDClient*)cls;
52   httpdclient->addGetVar(key, value);
53   return MHD_YES;
54 }
55
56 int HTTPDClient::iterate_post(void *clientIdentifier, enum MHD_ValueKind kind, const char *key,
57               const char *filename, const char* content_type,
58               const char *transfer_encoding, const char *data, uint64_t off, size_t size)
59 {
60   // logger->info("HTTPDClient: add post field: {} {} {}", key, data, size);
61   HTTPDClient* httpdclient = (HTTPDClient*)clientIdentifier;
62   httpdclient->addPostField(key, data, size);
63   return MHD_YES; //Return MHD_YES to continue iterating, MHD_NO to abort the iteration.
64 }
65
66 void HTTPDClient::request_completed(void *cls, struct MHD_Connection *mhd_connection,
67                                     void **unused, enum MHD_RequestTerminationCode toe)
68 {
69   const MHD_ConnectionInfo* mhdc = MHD_get_connection_info(mhd_connection, MHD_CONNECTION_INFO_SOCKET_CONTEXT);
70   HTTPDClient* httpdclient = (HTTPDClient*)mhdc->socket_context;
71   if (!httpdclient) return;
72   httpdclient->requestComplete();
73 }
74
75 void HTTPDClient::connection_notify(void *cls, struct MHD_Connection* mhd_connection,
76                                     void **socket_context, enum MHD_ConnectionNotificationCode toe)
77 {
78   if (toe == MHD_CONNECTION_NOTIFY_STARTED)
79   {
80     *socket_context = (void*)new HTTPDClient(mhd_connection, configDir);
81   }
82   else if (toe == MHD_CONNECTION_NOTIFY_CLOSED)
83   {
84     HTTPDClient* httpdclient = (HTTPDClient*)*socket_context;
85     if (!httpdclient) return;
86     delete httpdclient;
87     *socket_context = NULL;
88   }
89 }
90
91 int HTTPDClient::handle_connection(void* cls, struct MHD_Connection* mhd_connection,
92                                    const char* url, const char* method,
93                                    const char* version, const char* upload_data,
94                                    size_t* upload_data_size, void** userData)
95 {
96   const MHD_ConnectionInfo* mhdc = MHD_get_connection_info(mhd_connection, MHD_CONNECTION_INFO_SOCKET_CONTEXT);
97   HTTPDClient* httpdclient = (HTTPDClient*)mhdc->socket_context;
98   return httpdclient->handleConnection(url, method, version, upload_data, upload_data_size, userData);
99 }
100
101 // End of static callbacks, now for the client object itself
102
103 HTTPDClient::HTTPDClient(struct MHD_Connection* _mhd_connection, const std::string& _configDir)
104 : postprocessor(NULL), url(NULL), mhd_connection(_mhd_connection), vdrclient(_configDir)
105 {
106 //  printf("HTTPDClient created %p\n", this);
107 }
108
109 HTTPDClient::~HTTPDClient()
110 {
111 //  printf("%p HTTPDClient destructor\n", this);
112   if (url) free(url);
113 }
114
115 int HTTPDClient::handleConnection(const char* turl, const char* method,
116                                   const char* version, const char* upload_data,
117                                   size_t* upload_data_size, void** userData)
118 {
119   /*
120     logger->debug("handle_connection called");
121     logger->debug("hc: cls {}", (void*)cls);
122     logger->debug("hc: mhd_connection {}", (void*)mhd_connection);
123     logger->debug("hc: url {}", (void*)url);
124     if (url) logger->debug("hc: url: {}", url);
125     logger->debug("hc: method {}", (void*)method);
126     if (url) logger->debug("hc: method: {}",  method);
127     logger->debug("hc: version {}", (void*)version);
128     if (url) logger->debug("hc: version: {}",  version);
129     logger->debug("hc: upload_data {}", (void*)upload_data);
130     logger->debug("hc: upload_data_size {}", *upload_data_size);
131     logger->debug("hc: userData {}", (void*)*userData);
132   */
133
134   // Now we are going to use userData as a flag to say first run or not
135
136   if (!*userData) // new request
137   {
138     *userData = (void*)1;
139     int size __attribute__((unused)) = asprintf(&url, "%s", turl);
140
141     if (!strcmp(method, "POST"))
142     {
143       MHD_get_connection_values(mhd_connection, MHD_GET_ARGUMENT_KIND, HTTPDClient::uri_key_value, (void*)this);
144
145       // The following returns NULL if there are no POST fields to come
146       postprocessor = MHD_create_post_processor(mhd_connection, POSTBUFFERSIZE,
147                                                 HTTPDClient::iterate_post, (void*)this);
148     }
149     else if (!strcmp(method, "GET"))
150     {
151       // OK, nothing else to do here
152     }
153     else
154     {
155       return MHD_NO;
156     }
157
158     return MHD_YES;
159   }
160
161   // Not first go at this request
162
163   if (!strcmp(method, "GET"))
164   {
165     return processGET();
166   }
167   else if (!strcmp(method, "POST"))
168   {
169     // HC will be called at least three times. Once above to create the HTTPDClient object (above).
170     // Here the middle calls will be called with upload_data_size > 0
171     // The last call is with upload_data_size == 0 and signals the end, a response must be queued
172
173     if (*upload_data_size != 0) // There is more to process, and signal run again
174     {
175       MHD_post_process(postprocessor, upload_data, *upload_data_size);
176       *upload_data_size = 0;
177       return MHD_YES;
178     }
179     else
180     {
181 //      printf("hc: zero post provided, end of upload\n");
182
183       MHD_destroy_post_processor(postprocessor);
184       postprocessor = NULL;
185
186       return processPOST();
187     }
188   }
189   else
190   {
191     return sendStockResponse(405);
192   }
193 }
194
195 void HTTPDClient::requestComplete()
196 {
197 //  printf("%p HTTPDClient request complete\n", this);
198   if (postprocessor)
199   {
200 //    printf("here\n");
201     MHD_destroy_post_processor(postprocessor);
202     postprocessor = NULL;
203   }
204 }
205
206 void HTTPDClient::addGetVar(const char* key, const char* value)
207 {
208   if (strlen(key) > 50) return;
209   if (value && (strlen(value) > 1000)) return;
210
211   getVars[std::string(key)] = std::string(value);
212 /*
213   for(auto gv : getVars)
214   {
215     printf("%s %s\n", gv.first.c_str(), gv.second.c_str());
216   }
217   */
218 }
219
220 void HTTPDClient::addPostField(const char* key, const char* value, int valueLength)
221 {
222   if (strlen(key) > 50) return;
223   if (strlen(value) > 1000) return;
224
225   postFields[std::string(key)] = std::string(value, valueLength);
226
227   /*
228   for(auto pf : postFields)
229   {
230     printf("%s %s\n", pf.first.c_str(), pf.second.c_str());
231   }
232   */
233 }
234
235 int HTTPDClient::processGET()
236 {
237   const char* defaultfilename = "index.html";
238
239   if (url == NULL) return MHD_NO;
240   if (strstr(url, "..")) return MHD_NO; // MHD deals with these already and limits it. If it occurs here, error.
241
242   int size  __attribute__((unused));
243
244   int slen = strlen(url);
245   char* fullpath;
246   if (url[slen-1] == '/')
247     size = asprintf(&fullpath, "%s%s%s", docRoot.c_str(), url, defaultfilename);
248   else
249     size = asprintf(&fullpath, "%s%s", docRoot.c_str(), url);
250
251   //printf("FILENAME: '%s'\n", fullpath);
252
253   struct stat sbuf;
254   if (   (stat(fullpath, &sbuf) == -1)               // failed to stat
255       || ((sbuf.st_mode & S_IFMT) != S_IFREG)  )     // must be regular file
256   {
257     free(fullpath);
258     return sendStockResponse(404);
259   }
260
261   int fd = open(fullpath, O_RDONLY);
262   free(fullpath);
263
264   if (fd == -1) return MHD_NO;
265
266   struct MHD_Response* response = MHD_create_response_from_fd(sbuf.st_size, fd);
267   int ret = MHD_queue_response(mhd_connection, MHD_HTTP_OK, response);
268
269   getVars.clear();
270   postFields.clear();
271   MHD_destroy_response(response);
272
273   return ret;
274 }
275
276 int HTTPDClient::processPOST()
277 {
278  // printf("Process POST:\n");
279   //printf("REQ: %s\n", getVars["req"].c_str());
280
281   if (strcmp(url, "/jsonserver")) return sendStockResponse(404);
282   if (getVars["req"].empty()) return sendStockResponse(400);
283
284   std::string returnData;
285
286   bool success = vdrclient.process(getVars["req"], postFields, returnData);
287   if (!success) return sendStockResponse(500);
288
289   struct MHD_Response* response = MHD_create_response_from_buffer(strlen(returnData.c_str()), (void *)returnData.c_str(), MHD_RESPMEM_MUST_COPY);
290   MHD_add_response_header(response, "Content-Type", "application/json");
291   int ret = MHD_queue_response(mhd_connection, MHD_HTTP_OK, response);
292
293   getVars.clear();
294   postFields.clear();
295   MHD_destroy_response(response);
296   return ret;
297 }
298
299 int HTTPDClient::sendStockResponse(int code)
300 {
301   const char *page400 = "<html><body>Bad request</body></html>\n";
302   const char *page404 = "<html><body>File not found</body></html>\n";
303   const char *page405 = "<html><body>Method not allowed</body></html>\n";
304   const char *page500 = "<html><body>Internal server error</body></html>\n";
305   const char* page;
306   if      (code == 400) page = page400;
307   else if (code == 404) page = page404;
308   else if (code == 405) page = page405;
309   else if (code == 500) page = page500;
310   else return MHD_NO;
311
312   struct MHD_Response* response = MHD_create_response_from_buffer(strlen(page), (void *)page, MHD_RESPMEM_PERSISTENT);
313   int ret = MHD_queue_response(mhd_connection, code, response);
314
315   getVars.clear();
316   postFields.clear();
317   MHD_destroy_response(response);
318   return ret;
319 }