Tomato 1.28
[tomato.git] / release / src / router / miniupnpd / upnphttp.c
blob0b34bf145873bbc67f81dc629d7ed0fd30c8816d
1 /* $Id: upnphttp.c,v 1.57 2009/02/12 23:38:40 nanard Exp $ */
2 /* Project : miniupnp
3 * Website : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
4 * Author : Thomas Bernard
5 * Copyright (c) 2005-2008 Thomas Bernard
6 * This software is subject to the conditions detailed in the
7 * LICENCE file included in this distribution.
8 * */
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <sys/types.h>
14 #include <sys/socket.h>
15 #include <sys/param.h>
16 #include <syslog.h>
17 #include <ctype.h>
18 #include "config.h"
19 #include "upnphttp.h"
20 #include "upnpdescgen.h"
21 #include "miniupnpdpath.h"
22 #include "upnpsoap.h"
23 #include "upnpevents.h"
25 struct upnphttp *
26 New_upnphttp(int s)
28 struct upnphttp * ret;
29 if(s<0)
30 return NULL;
31 ret = (struct upnphttp *)malloc(sizeof(struct upnphttp));
32 if(ret == NULL)
33 return NULL;
34 memset(ret, 0, sizeof(struct upnphttp));
35 ret->socket = s;
36 return ret;
39 void
40 CloseSocket_upnphttp(struct upnphttp * h)
42 if(close(h->socket) < 0)
44 syslog(LOG_ERR, "CloseSocket_upnphttp: close(%d): %m", h->socket);
46 h->socket = -1;
47 h->state = 100;
50 void
51 Delete_upnphttp(struct upnphttp * h)
53 if(h)
55 if(h->socket >= 0)
56 CloseSocket_upnphttp(h);
57 if(h->req_buf)
58 free(h->req_buf);
59 if(h->res_buf)
60 free(h->res_buf);
61 free(h);
65 /* parse HttpHeaders of the REQUEST */
66 static void
67 ParseHttpHeaders(struct upnphttp * h)
69 char * line;
70 char * colon;
71 char * p;
72 int n;
73 line = h->req_buf;
74 /* TODO : check if req_buf, contentoff are ok */
75 while(line < (h->req_buf + h->req_contentoff))
77 colon = strchr(line, ':');
78 if(colon)
80 if(strncasecmp(line, "Content-Length", 14)==0)
82 p = colon;
83 while(*p < '0' || *p > '9')
84 p++;
85 h->req_contentlen = atoi(p);
86 /*printf("*** Content-Lenght = %d ***\n", h->req_contentlen);
87 printf(" readbufflen=%d contentoff = %d\n",
88 h->req_buflen, h->req_contentoff);*/
90 else if(strncasecmp(line, "SOAPAction", 10)==0)
92 p = colon;
93 n = 0;
94 while(*p == ':' || *p == ' ' || *p == '\t')
95 p++;
96 while(p[n]>=' ')
98 n++;
100 if((p[0] == '"' && p[n-1] == '"')
101 || (p[0] == '\'' && p[n-1] == '\''))
103 p++; n -= 2;
105 h->req_soapAction = p;
106 h->req_soapActionLen = n;
108 #ifdef ENABLE_EVENTS
109 else if(strncasecmp(line, "Callback", 8)==0)
111 p = colon;
112 while(*p != '<' && *p != '\r' )
113 p++;
114 n = 0;
115 while(p[n] != '>' && p[n] != '\r' )
116 n++;
117 h->req_Callback = p + 1;
118 h->req_CallbackLen = MAX(0, n - 1);
120 else if(strncasecmp(line, "SID", 3)==0)
122 p = colon + 1;
123 while(isspace(*p))
124 p++;
125 n = 0;
126 while(!isspace(p[n]))
127 n++;
128 h->req_SID = p;
129 h->req_SIDLen = n;
131 /* Timeout: Seconds-nnnn */
132 /* TIMEOUT
133 Recommended. Requested duration until subscription expires,
134 either number of seconds or infinite. Recommendation
135 by a UPnP Forum working committee. Defined by UPnP vendor.
136 Consists of the keyword "Second-" followed (without an
137 intervening space) by either an integer or the keyword "infinite". */
138 else if(strncasecmp(line, "Timeout", 7)==0)
140 p = colon + 1;
141 while(isspace(*p))
142 p++;
143 if(strncasecmp(p, "Second-", 7)==0) {
144 h->req_Timeout = atoi(p+7);
147 #endif
149 while(!(line[0] == '\r' && line[1] == '\n'))
150 line++;
151 line += 2;
155 /* very minimalistic 404 error message */
156 static void
157 Send404(struct upnphttp * h)
160 static const char error404[] = "HTTP/1.1 404 Not found\r\n"
161 "Connection: close\r\n"
162 "Content-type: text/html\r\n"
163 "\r\n"
164 "<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>"
165 "<BODY><H1>Not Found</H1>The requested URL was not found"
166 " on this server.</BODY></HTML>\r\n";
167 int n;
168 n = send(h->socket, error404, sizeof(error404) - 1, 0);
169 if(n < 0)
171 syslog(LOG_ERR, "Send404: send(http): %m");
173 static const char body404[] =
174 "<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>"
175 "<BODY><H1>Not Found</H1>The requested URL was not found"
176 " on this server.</BODY></HTML>\r\n";
177 h->respflags = FLAG_HTML;
178 BuildResp2_upnphttp(h, 404, "Not Found",
179 body404, sizeof(body404) - 1);
180 SendResp_upnphttp(h);
181 CloseSocket_upnphttp(h);
184 /* very minimalistic 501 error message */
185 static void
186 Send501(struct upnphttp * h)
189 static const char error501[] = "HTTP/1.1 501 Not Implemented\r\n"
190 "Connection: close\r\n"
191 "Content-type: text/html\r\n"
192 "\r\n"
193 "<HTML><HEAD><TITLE>501 Not Implemented</TITLE></HEAD>"
194 "<BODY><H1>Not Implemented</H1>The HTTP Method "
195 "is not implemented by this server.</BODY></HTML>\r\n";
196 int n;
197 n = send(h->socket, error501, sizeof(error501) - 1, 0);
198 if(n < 0)
200 syslog(LOG_ERR, "Send501: send(http): %m");
203 static const char body501[] =
204 "<HTML><HEAD><TITLE>501 Not Implemented</TITLE></HEAD>"
205 "<BODY><H1>Not Implemented</H1>The HTTP Method "
206 "is not implemented by this server.</BODY></HTML>\r\n";
207 h->respflags = FLAG_HTML;
208 BuildResp2_upnphttp(h, 501, "Not Implemented",
209 body501, sizeof(body501) - 1);
210 SendResp_upnphttp(h);
211 CloseSocket_upnphttp(h);
214 static const char *
215 findendheaders(const char * s, int len)
217 while(len-->0)
219 if(s[0]=='\r' && s[1]=='\n' && s[2]=='\r' && s[3]=='\n')
220 return s;
221 s++;
223 return NULL;
226 #ifdef HAS_DUMMY_SERVICE
227 static void
228 sendDummyDesc(struct upnphttp * h)
230 static const char xml_desc[] = "<?xml version=\"1.0\"?>\r\n"
231 "<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">"
232 " <specVersion>"
233 " <major>1</major>"
234 " <minor>0</minor>"
235 " </specVersion>"
236 " <actionList />"
237 " <serviceStateTable />"
238 "</scpd>\r\n";
239 BuildResp_upnphttp(h, xml_desc, sizeof(xml_desc)-1);
240 SendResp_upnphttp(h);
241 CloseSocket_upnphttp(h);
243 #endif
245 /* Sends the description generated by the parameter */
246 static void
247 sendXMLdesc(struct upnphttp * h, char * (f)(int *))
249 char * desc;
250 int len;
251 desc = f(&len);
252 if(!desc)
254 static const char error500[] = "<HTML><HEAD><TITLE>Error 500</TITLE>"
255 "</HEAD><BODY>Internal Server Error</BODY></HTML>\r\n";
256 syslog(LOG_ERR, "Failed to generate XML description");
257 h->respflags = FLAG_HTML;
258 BuildResp2_upnphttp(h, 500, "Internal Server Error",
259 error500, sizeof(error500)-1);
261 else
263 BuildResp_upnphttp(h, desc, len);
265 SendResp_upnphttp(h);
266 CloseSocket_upnphttp(h);
267 free(desc);
270 /* ProcessHTTPPOST_upnphttp()
271 * executes the SOAP query if it is possible */
272 static void
273 ProcessHTTPPOST_upnphttp(struct upnphttp * h)
275 if((h->req_buflen - h->req_contentoff) >= h->req_contentlen)
277 if(h->req_soapAction)
279 /* we can process the request */
280 syslog(LOG_INFO, "SOAPAction: %.*s",
281 h->req_soapActionLen, h->req_soapAction);
282 ExecuteSoapAction(h,
283 h->req_soapAction,
284 h->req_soapActionLen);
286 else
288 static const char err400str[] =
289 "<html><body>Bad request</body></html>";
290 syslog(LOG_INFO, "No SOAPAction in HTTP headers");
291 h->respflags = FLAG_HTML;
292 BuildResp2_upnphttp(h, 400, "Bad Request",
293 err400str, sizeof(err400str) - 1);
294 SendResp_upnphttp(h);
295 CloseSocket_upnphttp(h);
298 else
300 /* waiting for remaining data */
301 h->state = 1;
305 #ifdef ENABLE_EVENTS
306 static void
307 ProcessHTTPSubscribe_upnphttp(struct upnphttp * h, const char * path)
309 const char * sid;
310 syslog(LOG_DEBUG, "ProcessHTTPSubscribe %s", path);
311 syslog(LOG_DEBUG, "Callback '%.*s' Timeout=%d",
312 h->req_CallbackLen, h->req_Callback, h->req_Timeout);
313 syslog(LOG_DEBUG, "SID '%.*s'", h->req_SIDLen, h->req_SID);
314 if(!h->req_Callback && !h->req_SID) {
315 /* Missing or invalid CALLBACK : 412 Precondition Failed.
316 * If CALLBACK header is missing or does not contain a valid HTTP URL,
317 * the publisher must respond with HTTP error 412 Precondition Failed*/
318 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
319 SendResp_upnphttp(h);
320 CloseSocket_upnphttp(h);
321 } else {
322 /* - add to the subscriber list
323 * - respond HTTP/x.x 200 OK
324 * - Send the initial event message */
325 /* Server:, SID:; Timeout: Second-(xx|infinite) */
326 if(h->req_Callback) {
327 sid = upnpevents_addSubscriber(path, h->req_Callback,
328 h->req_CallbackLen, h->req_Timeout);
329 h->respflags = FLAG_TIMEOUT;
330 if(sid) {
331 syslog(LOG_DEBUG, "generated sid=%s", sid);
332 h->respflags |= FLAG_SID;
333 h->req_SID = sid;
334 h->req_SIDLen = strlen(sid);
336 BuildResp_upnphttp(h, 0, 0);
337 } else {
338 /* subscription renew */
339 /* Invalid SID
340 412 Precondition Failed. If a SID does not correspond to a known,
341 un-expired subscription, the publisher must respond
342 with HTTP error 412 Precondition Failed. */
343 if(renewSubscription(h->req_SID, h->req_SIDLen, h->req_Timeout) < 0) {
344 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
345 } else {
346 BuildResp_upnphttp(h, 0, 0);
349 SendResp_upnphttp(h);
350 CloseSocket_upnphttp(h);
354 static void
355 ProcessHTTPUnSubscribe_upnphttp(struct upnphttp * h, const char * path)
357 syslog(LOG_DEBUG, "ProcessHTTPUnSubscribe %s", path);
358 syslog(LOG_DEBUG, "SID '%.*s'", h->req_SIDLen, h->req_SID);
359 /* Remove from the list */
360 if(upnpevents_removeSubscriber(h->req_SID, h->req_SIDLen) < 0) {
361 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
362 } else {
363 BuildResp_upnphttp(h, 0, 0);
365 SendResp_upnphttp(h);
366 CloseSocket_upnphttp(h);
368 #endif
370 /* Parse and process Http Query
371 * called once all the HTTP headers have been received. */
372 static void
373 ProcessHttpQuery_upnphttp(struct upnphttp * h)
375 char HttpCommand[16];
376 char HttpUrl[128];
377 char * HttpVer;
378 char * p;
379 int i;
380 p = h->req_buf;
381 if(!p)
382 return;
383 for(i = 0; i<15 && *p != ' ' && *p != '\r'; i++)
384 HttpCommand[i] = *(p++);
385 HttpCommand[i] = '\0';
386 while(*p==' ')
387 p++;
388 for(i = 0; i<127 && *p != ' ' && *p != '\r'; i++)
389 HttpUrl[i] = *(p++);
390 HttpUrl[i] = '\0';
391 while(*p==' ')
392 p++;
393 HttpVer = h->HttpVer;
394 for(i = 0; i<15 && *p != '\r'; i++)
395 HttpVer[i] = *(p++);
396 HttpVer[i] = '\0';
397 syslog(LOG_INFO, "HTTP REQUEST : %s %s (%s)",
398 HttpCommand, HttpUrl, HttpVer);
399 ParseHttpHeaders(h);
400 if(strcmp("POST", HttpCommand) == 0)
402 h->req_command = EPost;
403 ProcessHTTPPOST_upnphttp(h);
405 else if(strcmp("GET", HttpCommand) == 0)
407 h->req_command = EGet;
408 if(strcasecmp(ROOTDESC_PATH, HttpUrl) == 0)
410 sendXMLdesc(h, genRootDesc);
412 else if(strcasecmp(WANIPC_PATH, HttpUrl) == 0)
414 sendXMLdesc(h, genWANIPCn);
416 else if(strcasecmp(WANCFG_PATH, HttpUrl) == 0)
418 sendXMLdesc(h, genWANCfg);
420 #ifdef HAS_DUMMY_SERVICE
421 else if(strcasecmp(DUMMY_PATH, HttpUrl) == 0)
423 sendDummyDesc(h);
425 #endif
426 #ifdef ENABLE_L3F_SERVICE
427 else if(strcasecmp(L3F_PATH, HttpUrl) == 0)
429 sendXMLdesc(h, genL3F);
431 #endif
432 else
434 syslog(LOG_NOTICE, "%s not found, responding ERROR 404", HttpUrl);
435 Send404(h);
438 #ifdef ENABLE_EVENTS
439 else if(strcmp("SUBSCRIBE", HttpCommand) == 0)
441 h->req_command = ESubscribe;
442 ProcessHTTPSubscribe_upnphttp(h, HttpUrl);
444 else if(strcmp("UNSUBSCRIBE", HttpCommand) == 0)
446 h->req_command = EUnSubscribe;
447 ProcessHTTPUnSubscribe_upnphttp(h, HttpUrl);
449 #else
450 else if(strcmp("SUBSCRIBE", HttpCommand) == 0)
452 syslog(LOG_NOTICE, "SUBSCRIBE not implemented. ENABLE_EVENTS compile option disabled");
453 Send501(h);
455 #endif
456 else
458 syslog(LOG_NOTICE, "Unsupported HTTP Command %s", HttpCommand);
459 Send501(h);
464 void
465 Process_upnphttp(struct upnphttp * h)
467 char buf[2048];
468 int n;
469 if(!h)
470 return;
471 switch(h->state)
473 case 0:
474 n = recv(h->socket, buf, 2048, 0);
475 if(n<0)
477 syslog(LOG_ERR, "recv (state0): %m");
478 h->state = 100;
480 else if(n==0)
482 syslog(LOG_WARNING, "HTTP Connection closed inexpectedly");
483 h->state = 100;
485 else
487 const char * endheaders;
488 /* if 1st arg of realloc() is null,
489 * realloc behaves the same as malloc() */
490 h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen + 1);
491 memcpy(h->req_buf + h->req_buflen, buf, n);
492 h->req_buflen += n;
493 h->req_buf[h->req_buflen] = '\0';
494 /* search for the string "\r\n\r\n" */
495 endheaders = findendheaders(h->req_buf, h->req_buflen);
496 if(endheaders)
498 h->req_contentoff = endheaders - h->req_buf + 4;
499 ProcessHttpQuery_upnphttp(h);
502 break;
503 case 1:
504 n = recv(h->socket, buf, 2048, 0);
505 if(n<0)
507 syslog(LOG_ERR, "recv (state1): %m");
508 h->state = 100;
510 else if(n==0)
512 syslog(LOG_WARNING, "HTTP Connection closed inexpectedly");
513 h->state = 100;
515 else
517 /*fwrite(buf, 1, n, stdout);*/ /* debug */
518 h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen);
519 memcpy(h->req_buf + h->req_buflen, buf, n);
520 h->req_buflen += n;
521 if((h->req_buflen - h->req_contentoff) >= h->req_contentlen)
523 ProcessHTTPPOST_upnphttp(h);
526 break;
527 default:
528 syslog(LOG_WARNING, "Unexpected state: %d", h->state);
532 static const char httpresphead[] =
533 "%s %d %s\r\n"
534 /*"Content-Type: text/xml; charset=\"utf-8\"\r\n"*/
535 "Content-Type: %s\r\n"
536 "Connection: close\r\n"
537 "Content-Length: %d\r\n"
538 /*"Server: miniupnpd/1.0 UPnP/1.0\r\n"*/
539 "Server: " MINIUPNPD_SERVER_STRING "\r\n"
540 ; /*"\r\n";*/
542 "<?xml version=\"1.0\"?>\n"
543 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
544 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
545 "<s:Body>"
547 "</s:Body>"
548 "</s:Envelope>";
550 /* with response code and response message
551 * also allocate enough memory */
553 void
554 BuildHeader_upnphttp(struct upnphttp * h, int respcode,
555 const char * respmsg,
556 int bodylen)
558 int templen;
559 if(!h->res_buf)
561 templen = sizeof(httpresphead) + 128 + bodylen;
562 h->res_buf = (char *)malloc(templen);
563 h->res_buf_alloclen = templen;
565 h->res_buflen = snprintf(h->res_buf, h->res_buf_alloclen,
566 httpresphead, h->HttpVer,
567 respcode, respmsg,
568 (h->respflags&FLAG_HTML)?"text/html":"text/xml",
569 bodylen);
570 /* Additional headers */
571 #ifdef ENABLE_EVENTS
572 if(h->respflags & FLAG_TIMEOUT) {
573 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
574 h->res_buf_alloclen - h->res_buflen,
575 "Timeout: Second-");
576 if(h->req_Timeout) {
577 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
578 h->res_buf_alloclen - h->res_buflen,
579 "%d\r\n", h->req_Timeout);
580 } else {
581 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
582 h->res_buf_alloclen - h->res_buflen,
583 "infinite\r\n");
586 if(h->respflags & FLAG_SID) {
587 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
588 h->res_buf_alloclen - h->res_buflen,
589 "SID: %s\r\n", h->req_SID);
591 #endif
592 h->res_buf[h->res_buflen++] = '\r';
593 h->res_buf[h->res_buflen++] = '\n';
594 if(h->res_buf_alloclen < (h->res_buflen + bodylen))
596 h->res_buf = (char *)realloc(h->res_buf, (h->res_buflen + bodylen));
597 h->res_buf_alloclen = h->res_buflen + bodylen;
601 void
602 BuildResp2_upnphttp(struct upnphttp * h, int respcode,
603 const char * respmsg,
604 const char * body, int bodylen)
606 BuildHeader_upnphttp(h, respcode, respmsg, bodylen);
607 if(body)
608 memcpy(h->res_buf + h->res_buflen, body, bodylen);
609 h->res_buflen += bodylen;
612 /* responding 200 OK ! */
613 void
614 BuildResp_upnphttp(struct upnphttp * h,
615 const char * body, int bodylen)
617 BuildResp2_upnphttp(h, 200, "OK", body, bodylen);
620 void
621 SendResp_upnphttp(struct upnphttp * h)
623 int n;
624 n = send(h->socket, h->res_buf, h->res_buflen, 0);
625 if(n<0)
627 syslog(LOG_ERR, "send(res_buf): %m");
629 else if(n < h->res_buflen)
631 /* TODO : handle correctly this case */
632 syslog(LOG_ERR, "send(res_buf): %d bytes sent (out of %d)",
633 n, h->res_buflen);