1 /* $Id: upnphttp.c,v 1.57 2009/02/12 23:38:40 nanard Exp $ */
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.
13 #include <sys/types.h>
14 #include <sys/socket.h>
15 #include <sys/param.h>
20 #include "upnpdescgen.h"
21 #include "miniupnpdpath.h"
23 #include "upnpevents.h"
28 struct upnphttp
* ret
;
31 ret
= (struct upnphttp
*)malloc(sizeof(struct upnphttp
));
34 memset(ret
, 0, sizeof(struct upnphttp
));
40 CloseSocket_upnphttp(struct upnphttp
* h
)
42 if(close(h
->socket
) < 0)
44 syslog(LOG_ERR
, "CloseSocket_upnphttp: close(%d): %m", h
->socket
);
51 Delete_upnphttp(struct upnphttp
* h
)
56 CloseSocket_upnphttp(h
);
65 /* parse HttpHeaders of the REQUEST */
67 ParseHttpHeaders(struct upnphttp
* h
)
74 /* TODO : check if req_buf, contentoff are ok */
75 while(line
< (h
->req_buf
+ h
->req_contentoff
))
77 colon
= strchr(line
, ':');
80 if(strncasecmp(line
, "Content-Length", 14)==0)
83 while(*p
< '0' || *p
> '9')
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)
94 while(*p
== ':' || *p
== ' ' || *p
== '\t')
100 if((p
[0] == '"' && p
[n
-1] == '"')
101 || (p
[0] == '\'' && p
[n
-1] == '\''))
105 h
->req_soapAction
= p
;
106 h
->req_soapActionLen
= n
;
109 else if(strncasecmp(line
, "Callback", 8)==0)
112 while(*p
!= '<' && *p
!= '\r' )
115 while(p
[n
] != '>' && p
[n
] != '\r' )
117 h
->req_Callback
= p
+ 1;
118 h
->req_CallbackLen
= MAX(0, n
- 1);
120 else if(strncasecmp(line
, "SID", 3)==0)
126 while(!isspace(p
[n
]))
131 /* Timeout: Seconds-nnnn */
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)
143 if(strncasecmp(p
, "Second-", 7)==0) {
144 h
->req_Timeout
= atoi(p
+7);
149 while(!(line
[0] == '\r' && line
[1] == '\n'))
155 /* very minimalistic 404 error message */
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"
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";
168 n = send(h->socket, error404, sizeof(error404) - 1, 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 */
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"
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";
197 n = send(h->socket, error501, sizeof(error501) - 1, 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
);
215 findendheaders(const char * s
, int len
)
219 if(s
[0]=='\r' && s
[1]=='\n' && s
[2]=='\r' && s
[3]=='\n')
226 #ifdef HAS_DUMMY_SERVICE
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\">"
237 " <serviceStateTable />"
239 BuildResp_upnphttp(h
, xml_desc
, sizeof(xml_desc
)-1);
240 SendResp_upnphttp(h
);
241 CloseSocket_upnphttp(h
);
245 /* Sends the description generated by the parameter */
247 sendXMLdesc(struct upnphttp
* h
, char * (f
)(int *))
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);
263 BuildResp_upnphttp(h
, desc
, len
);
265 SendResp_upnphttp(h
);
266 CloseSocket_upnphttp(h
);
270 /* ProcessHTTPPOST_upnphttp()
271 * executes the SOAP query if it is possible */
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
);
284 h
->req_soapActionLen
);
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
);
300 /* waiting for remaining data */
307 ProcessHTTPSubscribe_upnphttp(struct upnphttp
* h
, const char * path
)
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
);
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
;
331 syslog(LOG_DEBUG
, "generated sid=%s", sid
);
332 h
->respflags
|= FLAG_SID
;
334 h
->req_SIDLen
= strlen(sid
);
336 BuildResp_upnphttp(h
, 0, 0);
338 /* subscription renew */
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);
346 BuildResp_upnphttp(h
, 0, 0);
349 SendResp_upnphttp(h
);
350 CloseSocket_upnphttp(h
);
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);
363 BuildResp_upnphttp(h
, 0, 0);
365 SendResp_upnphttp(h
);
366 CloseSocket_upnphttp(h
);
370 /* Parse and process Http Query
371 * called once all the HTTP headers have been received. */
373 ProcessHttpQuery_upnphttp(struct upnphttp
* h
)
375 char HttpCommand
[16];
383 for(i
= 0; i
<15 && *p
!= ' ' && *p
!= '\r'; i
++)
384 HttpCommand
[i
] = *(p
++);
385 HttpCommand
[i
] = '\0';
388 for(i
= 0; i
<127 && *p
!= ' ' && *p
!= '\r'; i
++)
393 HttpVer
= h
->HttpVer
;
394 for(i
= 0; i
<15 && *p
!= '\r'; i
++)
397 syslog(LOG_INFO
, "HTTP REQUEST : %s %s (%s)",
398 HttpCommand
, HttpUrl
, HttpVer
);
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)
426 #ifdef ENABLE_L3F_SERVICE
427 else if(strcasecmp(L3F_PATH
, HttpUrl
) == 0)
429 sendXMLdesc(h
, genL3F
);
434 syslog(LOG_NOTICE
, "%s not found, responding ERROR 404", HttpUrl
);
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
);
450 else if(strcmp("SUBSCRIBE", HttpCommand
) == 0)
452 syslog(LOG_NOTICE
, "SUBSCRIBE not implemented. ENABLE_EVENTS compile option disabled");
458 syslog(LOG_NOTICE
, "Unsupported HTTP Command %s", HttpCommand
);
465 Process_upnphttp(struct upnphttp
* h
)
474 n
= recv(h
->socket
, buf
, 2048, 0);
477 syslog(LOG_ERR
, "recv (state0): %m");
482 syslog(LOG_WARNING
, "HTTP Connection closed inexpectedly");
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
);
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
);
498 h
->req_contentoff
= endheaders
- h
->req_buf
+ 4;
499 ProcessHttpQuery_upnphttp(h
);
504 n
= recv(h
->socket
, buf
, 2048, 0);
507 syslog(LOG_ERR
, "recv (state1): %m");
512 syslog(LOG_WARNING
, "HTTP Connection closed inexpectedly");
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
);
521 if((h
->req_buflen
- h
->req_contentoff
) >= h
->req_contentlen
)
523 ProcessHTTPPOST_upnphttp(h
);
528 syslog(LOG_WARNING
, "Unexpected state: %d", h
->state
);
532 static const char httpresphead
[] =
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"
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/\">"
550 /* with response code and response message
551 * also allocate enough memory */
554 BuildHeader_upnphttp(struct upnphttp
* h
, int respcode
,
555 const char * respmsg
,
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
,
568 (h
->respflags
&FLAG_HTML
)?"text/html":"text/xml",
570 /* Additional headers */
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
,
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
);
581 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
582 h
->res_buf_alloclen
- h
->res_buflen
,
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
);
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
;
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
);
608 memcpy(h
->res_buf
+ h
->res_buflen
, body
, bodylen
);
609 h
->res_buflen
+= bodylen
;
612 /* responding 200 OK ! */
614 BuildResp_upnphttp(struct upnphttp
* h
,
615 const char * body
, int bodylen
)
617 BuildResp2_upnphttp(h
, 200, "OK", body
, bodylen
);
621 SendResp_upnphttp(struct upnphttp
* h
)
624 n
= send(h
->socket
, h
->res_buf
, h
->res_buflen
, 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)",