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