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