1 /* $Id: upnphttp.c,v 1.60 2011/06/01 22:35:05 nanard Exp $ */
3 * Website : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
4 * Author : Thomas Bernard
5 * Copyright (c) 2005-2011 Thomas Bernard
6 * This software is subject to the conditions detailed in the
7 * LICENCE file included in this distribution.
13 #include <sys/types.h>
14 #include <sys/socket.h>
15 #include <arpa/inet.h> //!!TB
16 #include <sys/param.h>
21 #include "upnpdescgen.h"
22 #include "miniupnpdpath.h"
24 #include "upnpevents.h"
29 struct upnphttp
* ret
;
32 ret
= (struct upnphttp
*)malloc(sizeof(struct upnphttp
));
35 memset(ret
, 0, sizeof(struct upnphttp
));
41 CloseSocket_upnphttp(struct upnphttp
* h
)
43 if(close(h
->socket
) < 0)
45 syslog(LOG_ERR
, "CloseSocket_upnphttp: close(%d): %m", h
->socket
);
52 Delete_upnphttp(struct upnphttp
* h
)
57 CloseSocket_upnphttp(h
);
66 /* parse HttpHeaders of the REQUEST */
68 ParseHttpHeaders(struct upnphttp
* h
)
75 /* TODO : check if req_buf, contentoff are ok */
76 while(line
< (h
->req_buf
+ h
->req_contentoff
))
78 colon
= strchr(line
, ':');
81 if(strncasecmp(line
, "Content-Length", 14)==0)
84 while(*p
< '0' || *p
> '9')
86 h
->req_contentlen
= atoi(p
);
87 /*printf("*** Content-Lenght = %d ***\n", h->req_contentlen);
88 printf(" readbufflen=%d contentoff = %d\n",
89 h->req_buflen, h->req_contentoff);*/
91 else if(strncasecmp(line
, "SOAPAction", 10)==0)
95 while(*p
== ':' || *p
== ' ' || *p
== '\t')
101 if((p
[0] == '"' && p
[n
-1] == '"')
102 || (p
[0] == '\'' && p
[n
-1] == '\''))
106 h
->req_soapAction
= p
;
107 h
->req_soapActionLen
= n
;
110 else if(strncasecmp(line
, "Callback", 8)==0)
113 while(*p
!= '<' && *p
!= '\r' )
116 while(p
[n
] != '>' && p
[n
] != '\r' )
118 h
->req_Callback
= p
+ 1;
119 h
->req_CallbackLen
= MAX(0, n
- 1);
121 else if(strncasecmp(line
, "SID", 3)==0)
127 while(!isspace(p
[n
]))
132 /* Timeout: Seconds-nnnn */
134 Recommended. Requested duration until subscription expires,
135 either number of seconds or infinite. Recommendation
136 by a UPnP Forum working committee. Defined by UPnP vendor.
137 Consists of the keyword "Second-" followed (without an
138 intervening space) by either an integer or the keyword "infinite". */
139 else if(strncasecmp(line
, "Timeout", 7)==0)
144 if(strncasecmp(p
, "Second-", 7)==0) {
145 h
->req_Timeout
= atoi(p
+7);
150 while(!(line
[0] == '\r' && line
[1] == '\n'))
156 /* very minimalistic 404 error message */
158 Send404(struct upnphttp
* h
)
161 static const char error404[] = "HTTP/1.1 404 Not found\r\n"
162 "Connection: close\r\n"
163 "Content-type: text/html\r\n"
165 "<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>"
166 "<BODY><H1>Not Found</H1>The requested URL was not found"
167 " on this server.</BODY></HTML>\r\n";
169 n = send(h->socket, error404, sizeof(error404) - 1, 0);
172 syslog(LOG_ERR, "Send404: send(http): %m");
174 static const char body404
[] =
175 "<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>"
176 "<BODY><H1>Not Found</H1>The requested URL was not found"
177 " on this server.</BODY></HTML>\r\n";
178 h
->respflags
= FLAG_HTML
;
179 BuildResp2_upnphttp(h
, 404, "Not Found",
180 body404
, sizeof(body404
) - 1);
181 SendResp_upnphttp(h
);
182 CloseSocket_upnphttp(h
);
185 /* very minimalistic 501 error message */
187 Send501(struct upnphttp
* h
)
190 static const char error501[] = "HTTP/1.1 501 Not Implemented\r\n"
191 "Connection: close\r\n"
192 "Content-type: text/html\r\n"
194 "<HTML><HEAD><TITLE>501 Not Implemented</TITLE></HEAD>"
195 "<BODY><H1>Not Implemented</H1>The HTTP Method "
196 "is not implemented by this server.</BODY></HTML>\r\n";
198 n = send(h->socket, error501, sizeof(error501) - 1, 0);
201 syslog(LOG_ERR, "Send501: send(http): %m");
204 static const char body501
[] =
205 "<HTML><HEAD><TITLE>501 Not Implemented</TITLE></HEAD>"
206 "<BODY><H1>Not Implemented</H1>The HTTP Method "
207 "is not implemented by this server.</BODY></HTML>\r\n";
208 h
->respflags
= FLAG_HTML
;
209 BuildResp2_upnphttp(h
, 501, "Not Implemented",
210 body501
, sizeof(body501
) - 1);
211 SendResp_upnphttp(h
);
212 CloseSocket_upnphttp(h
);
216 findendheaders(const char * s
, int len
)
220 if(s
[0]=='\r' && s
[1]=='\n' && s
[2]=='\r' && s
[3]=='\n')
227 #ifdef HAS_DUMMY_SERVICE
229 sendDummyDesc(struct upnphttp
* h
)
231 static const char xml_desc
[] = "<?xml version=\"1.0\"?>\r\n"
232 "<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">"
238 " <serviceStateTable />"
240 BuildResp_upnphttp(h
, xml_desc
, sizeof(xml_desc
)-1);
241 SendResp_upnphttp(h
);
242 CloseSocket_upnphttp(h
);
246 /* Sends the description generated by the parameter */
248 sendXMLdesc(struct upnphttp
* h
, char * (f
)(int *))
255 static const char error500
[] = "<HTML><HEAD><TITLE>Error 500</TITLE>"
256 "</HEAD><BODY>Internal Server Error</BODY></HTML>\r\n";
257 syslog(LOG_ERR
, "Failed to generate XML description");
258 h
->respflags
= FLAG_HTML
;
259 BuildResp2_upnphttp(h
, 500, "Internal Server Error",
260 error500
, sizeof(error500
)-1);
264 BuildResp_upnphttp(h
, desc
, len
);
266 SendResp_upnphttp(h
);
267 CloseSocket_upnphttp(h
);
271 /* ProcessHTTPPOST_upnphttp()
272 * executes the SOAP query if it is possible */
274 ProcessHTTPPOST_upnphttp(struct upnphttp
* h
)
276 if((h
->req_buflen
- h
->req_contentoff
) >= h
->req_contentlen
)
278 if(h
->req_soapAction
)
280 /* we can process the request */
281 syslog(LOG_INFO
, "SOAPAction: %.*s",
282 h
->req_soapActionLen
, h
->req_soapAction
);
285 h
->req_soapActionLen
);
289 static const char err400str
[] =
290 "<html><body>Bad request</body></html>";
291 syslog(LOG_INFO
, "No SOAPAction in HTTP headers");
292 h
->respflags
= FLAG_HTML
;
293 BuildResp2_upnphttp(h
, 400, "Bad Request",
294 err400str
, sizeof(err400str
) - 1);
295 SendResp_upnphttp(h
);
296 CloseSocket_upnphttp(h
);
301 /* waiting for remaining data */
308 ProcessHTTPSubscribe_upnphttp(struct upnphttp
* h
, const char * path
)
311 syslog(LOG_DEBUG
, "ProcessHTTPSubscribe %s", path
);
312 syslog(LOG_DEBUG
, "Callback '%.*s' Timeout=%d",
313 h
->req_CallbackLen
, h
->req_Callback
, h
->req_Timeout
);
314 syslog(LOG_DEBUG
, "SID '%.*s'", h
->req_SIDLen
, h
->req_SID
);
315 if(!h
->req_Callback
&& !h
->req_SID
) {
316 /* Missing or invalid CALLBACK : 412 Precondition Failed.
317 * If CALLBACK header is missing or does not contain a valid HTTP URL,
318 * the publisher must respond with HTTP error 412 Precondition Failed*/
319 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
320 SendResp_upnphttp(h
);
321 CloseSocket_upnphttp(h
);
323 /* - add to the subscriber list
324 * - respond HTTP/x.x 200 OK
325 * - Send the initial event message */
326 /* Server:, SID:; Timeout: Second-(xx|infinite) */
327 if(h
->req_Callback
) {
328 sid
= upnpevents_addSubscriber(path
, h
->req_Callback
,
329 h
->req_CallbackLen
, h
->req_Timeout
);
330 h
->respflags
= FLAG_TIMEOUT
;
332 syslog(LOG_DEBUG
, "generated sid=%s", sid
);
333 h
->respflags
|= FLAG_SID
;
335 h
->req_SIDLen
= strlen(sid
);
337 BuildResp_upnphttp(h
, 0, 0);
339 /* subscription renew */
341 412 Precondition Failed. If a SID does not correspond to a known,
342 un-expired subscription, the publisher must respond
343 with HTTP error 412 Precondition Failed. */
344 if(renewSubscription(h
->req_SID
, h
->req_SIDLen
, h
->req_Timeout
) < 0) {
345 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
347 h
->respflags
= FLAG_TIMEOUT
;
348 BuildResp_upnphttp(h
, 0, 0);
351 SendResp_upnphttp(h
);
352 CloseSocket_upnphttp(h
);
357 ProcessHTTPUnSubscribe_upnphttp(struct upnphttp
* h
, const char * path
)
359 syslog(LOG_DEBUG
, "ProcessHTTPUnSubscribe %s", path
);
360 syslog(LOG_DEBUG
, "SID '%.*s'", h
->req_SIDLen
, h
->req_SID
);
361 /* Remove from the list */
362 if(upnpevents_removeSubscriber(h
->req_SID
, h
->req_SIDLen
) < 0) {
363 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
365 BuildResp_upnphttp(h
, 0, 0);
367 SendResp_upnphttp(h
);
368 CloseSocket_upnphttp(h
);
372 /* Parse and process Http Query
373 * called once all the HTTP headers have been received. */
375 ProcessHttpQuery_upnphttp(struct upnphttp
* h
)
377 char HttpCommand
[16];
385 for(i
= 0; i
<15 && *p
!= ' ' && *p
!= '\r'; i
++)
386 HttpCommand
[i
] = *(p
++);
387 HttpCommand
[i
] = '\0';
390 for(i
= 0; i
<127 && *p
!= ' ' && *p
!= '\r'; i
++)
395 HttpVer
= h
->HttpVer
;
396 for(i
= 0; i
<15 && *p
!= '\r'; i
++)
399 syslog(LOG_INFO
, "HTTP REQUEST : %s %s (%s)",
400 HttpCommand
, HttpUrl
, HttpVer
);
402 if(strcmp("POST", HttpCommand
) == 0)
404 h
->req_command
= EPost
;
405 ProcessHTTPPOST_upnphttp(h
);
407 else if(strcmp("GET", HttpCommand
) == 0)
409 h
->req_command
= EGet
;
410 if(strcasecmp(ROOTDESC_PATH
, HttpUrl
) == 0)
412 sendXMLdesc(h
, genRootDesc
);
414 else if(strcasecmp(WANIPC_PATH
, HttpUrl
) == 0)
416 sendXMLdesc(h
, genWANIPCn
);
418 else if(strcasecmp(WANCFG_PATH
, HttpUrl
) == 0)
420 sendXMLdesc(h
, genWANCfg
);
422 #ifdef HAS_DUMMY_SERVICE
423 else if(strcasecmp(DUMMY_PATH
, HttpUrl
) == 0)
428 #ifdef ENABLE_L3F_SERVICE
429 else if(strcasecmp(L3F_PATH
, HttpUrl
) == 0)
431 sendXMLdesc(h
, genL3F
);
434 #ifdef ENABLE_6FC_SERVICE
435 else if(strcasecmp(WANIP6FC_PATH
, HttpUrl
) == 0)
437 sendXMLdesc(h
, gen6FC
);
440 #ifdef ENABLE_DP_SERVICE
441 else if(strcasecmp(DP_PATH
, HttpUrl
) == 0)
443 sendXMLdesc(h
, genDP
);
448 syslog(LOG_INFO
, "%s not found, responding ERROR 404", HttpUrl
);
453 else if(strcmp("SUBSCRIBE", HttpCommand
) == 0)
455 h
->req_command
= ESubscribe
;
456 ProcessHTTPSubscribe_upnphttp(h
, HttpUrl
);
458 else if(strcmp("UNSUBSCRIBE", HttpCommand
) == 0)
460 h
->req_command
= EUnSubscribe
;
461 ProcessHTTPUnSubscribe_upnphttp(h
, HttpUrl
);
464 else if(strcmp("SUBSCRIBE", HttpCommand
) == 0)
466 syslog(LOG_NOTICE
, "SUBSCRIBE not implemented. ENABLE_EVENTS compile option disabled");
472 syslog(LOG_NOTICE
, "Unsupported HTTP Command %s", HttpCommand
);
479 Process_upnphttp(struct upnphttp
* h
)
488 n
= recv(h
->socket
, buf
, 2048, 0);
491 syslog(LOG_ERR
, "recv (state0): %m");
496 syslog(LOG_WARNING
, "HTTP Connection from %s closed unexpectedly",
497 inet_ntoa(h
->clientaddr
)); //!!TB - added client address
502 const char * endheaders
;
503 /* if 1st arg of realloc() is null,
504 * realloc behaves the same as malloc() */
505 h
->req_buf
= (char *)realloc(h
->req_buf
, n
+ h
->req_buflen
+ 1);
506 memcpy(h
->req_buf
+ h
->req_buflen
, buf
, n
);
508 h
->req_buf
[h
->req_buflen
] = '\0';
509 /* search for the string "\r\n\r\n" */
510 endheaders
= findendheaders(h
->req_buf
, h
->req_buflen
);
513 h
->req_contentoff
= endheaders
- h
->req_buf
+ 4;
514 ProcessHttpQuery_upnphttp(h
);
519 n
= recv(h
->socket
, buf
, 2048, 0);
522 syslog(LOG_ERR
, "recv (state1): %m");
527 syslog(LOG_WARNING
, "HTTP Connection from %s closed unexpectedly",
528 inet_ntoa(h
->clientaddr
)); //!!TB - added client address
533 /*fwrite(buf, 1, n, stdout);*/ /* debug */
534 h
->req_buf
= (char *)realloc(h
->req_buf
, n
+ h
->req_buflen
);
535 memcpy(h
->req_buf
+ h
->req_buflen
, buf
, n
);
537 if((h
->req_buflen
- h
->req_contentoff
) >= h
->req_contentlen
)
539 ProcessHTTPPOST_upnphttp(h
);
544 syslog(LOG_WARNING
, "Unexpected state: %d", h
->state
);
548 static const char httpresphead
[] =
550 /*"Content-Type: text/xml; charset=\"utf-8\"\r\n"*/
551 "Content-Type: %s\r\n"
552 "Connection: close\r\n"
553 "Content-Length: %d\r\n"
554 /*"Server: miniupnpd/1.0 UPnP/1.0\r\n"*/
555 "Server: " MINIUPNPD_SERVER_STRING
"\r\n"
558 "<?xml version=\"1.0\"?>\n"
559 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
560 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
566 /* with response code and response message
567 * also allocate enough memory */
570 BuildHeader_upnphttp(struct upnphttp
* h
, int respcode
,
571 const char * respmsg
,
577 templen
= sizeof(httpresphead
) + 128 + bodylen
;
578 h
->res_buf
= (char *)malloc(templen
);
579 h
->res_buf_alloclen
= templen
;
581 h
->res_buflen
= snprintf(h
->res_buf
, h
->res_buf_alloclen
,
582 httpresphead
, h
->HttpVer
,
584 (h
->respflags
&FLAG_HTML
)?"text/html":"text/xml",
586 /* Additional headers */
588 if(h
->respflags
& FLAG_TIMEOUT
) {
589 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
590 h
->res_buf_alloclen
- h
->res_buflen
,
593 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
594 h
->res_buf_alloclen
- h
->res_buflen
,
595 "%d\r\n", h
->req_Timeout
);
597 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
598 h
->res_buf_alloclen
- h
->res_buflen
,
602 if(h
->respflags
& FLAG_SID
) {
603 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
604 h
->res_buf_alloclen
- h
->res_buflen
,
605 "SID: %s\r\n", h
->req_SID
);
608 h
->res_buf
[h
->res_buflen
++] = '\r';
609 h
->res_buf
[h
->res_buflen
++] = '\n';
610 if(h
->res_buf_alloclen
< (h
->res_buflen
+ bodylen
))
612 h
->res_buf
= (char *)realloc(h
->res_buf
, (h
->res_buflen
+ bodylen
));
613 h
->res_buf_alloclen
= h
->res_buflen
+ bodylen
;
618 BuildResp2_upnphttp(struct upnphttp
* h
, int respcode
,
619 const char * respmsg
,
620 const char * body
, int bodylen
)
622 BuildHeader_upnphttp(h
, respcode
, respmsg
, bodylen
);
624 memcpy(h
->res_buf
+ h
->res_buflen
, body
, bodylen
);
625 h
->res_buflen
+= bodylen
;
628 /* responding 200 OK ! */
630 BuildResp_upnphttp(struct upnphttp
* h
,
631 const char * body
, int bodylen
)
633 BuildResp2_upnphttp(h
, 200, "OK", body
, bodylen
);
637 SendResp_upnphttp(struct upnphttp
* h
)
640 n
= send(h
->socket
, h
->res_buf
, h
->res_buflen
, 0);
643 syslog(LOG_ERR
, "send(res_buf): %m");
645 else if(n
< h
->res_buflen
)
647 /* TODO : handle correctly this case */
648 syslog(LOG_ERR
, "send(res_buf): %d bytes sent (out of %d)",