--- embedaddon/miniupnpd/upnphttp.c 2012/05/29 12:55:57 1.1.1.2 +++ embedaddon/miniupnpd/upnphttp.c 2013/07/22 00:32:35 1.1.1.3 @@ -1,8 +1,8 @@ -/* $Id: upnphttp.c,v 1.1.1.2 2012/05/29 12:55:57 misho Exp $ */ +/* $Id: upnphttp.c,v 1.1.1.3 2013/07/22 00:32:35 misho Exp $ */ /* Project : miniupnp * Website : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * Author : Thomas Bernard - * Copyright (c) 2005-2011 Thomas Bernard + * Copyright (c) 2005-2012 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file included in this distribution. * */ @@ -13,17 +13,23 @@ #include #include #include +#include #include #include #include +#include #include "config.h" +#ifdef ENABLE_HTTP_DATE +#include +#endif #include "upnphttp.h" #include "upnpdescgen.h" #include "miniupnpdpath.h" #include "upnpsoap.h" #include "upnpevents.h" +#include "upnputils.h" -struct upnphttp * +struct upnphttp * New_upnphttp(int s) { struct upnphttp * ret; @@ -34,6 +40,8 @@ New_upnphttp(int s) return NULL; memset(ret, 0, sizeof(struct upnphttp)); ret->socket = s; + if(!set_non_blocking(s)) + syslog(LOG_WARNING, "New_upnphttp::set_non_blocking(): %m"); return ret; } @@ -45,7 +53,7 @@ CloseSocket_upnphttp(struct upnphttp * h) syslog(LOG_ERR, "CloseSocket_upnphttp: close(%d): %m", h->socket); } h->socket = -1; - h->state = 100; + h->state = EToDelete; } void @@ -63,7 +71,9 @@ Delete_upnphttp(struct upnphttp * h) } } -/* parse HttpHeaders of the REQUEST */ +/* parse HttpHeaders of the REQUEST + * This function is called after the \r\n\r\n character + * sequence has been found in h->req_buf */ static void ParseHttpHeaders(struct upnphttp * h) { @@ -71,11 +81,21 @@ ParseHttpHeaders(struct upnphttp * h) char * colon; char * p; int n; + if((h->req_buf == NULL) || (h->req_contentoff <= 0)) + return; line = h->req_buf; - /* TODO : check if req_buf, contentoff are ok */ while(line < (h->req_buf + h->req_contentoff)) { - colon = strchr(line, ':'); + colon = line; + while(*colon != ':') + { + if(*colon == '\r' || *colon == '\n') + { + colon = NULL; /* no ':' character found on the line */ + break; + } + colon++; + } if(colon) { if(strncasecmp(line, "Content-Length", 14)==0) @@ -84,6 +104,10 @@ ParseHttpHeaders(struct upnphttp * h) while(*p < '0' || *p > '9') p++; h->req_contentlen = atoi(p); + if(h->req_contentlen < 0) { + syslog(LOG_WARNING, "ParseHttpHeaders() invalid Content-Length %d", h->req_contentlen); + h->req_contentlen = 0; + } /*printf("*** Content-Lenght = %d ***\n", h->req_contentlen); printf(" readbufflen=%d contentoff = %d\n", h->req_buflen, h->req_contentoff);*/ @@ -95,17 +119,46 @@ ParseHttpHeaders(struct upnphttp * h) while(*p == ':' || *p == ' ' || *p == '\t') p++; while(p[n]>=' ') - { n++; - } if((p[0] == '"' && p[n-1] == '"') || (p[0] == '\'' && p[n-1] == '\'')) { p++; n -= 2; } - h->req_soapAction = p; + h->req_soapActionOff = p - h->req_buf; h->req_soapActionLen = n; } + else if(strncasecmp(line, "accept-language", 15) == 0) + { + p = colon; + n = 0; + while(*p == ':' || *p == ' ' || *p == '\t') + p++; + while(p[n]>=' ') + n++; + syslog(LOG_DEBUG, "accept-language HTTP header : '%.*s'", n, p); + /* keep only the 1st accepted language */ + n = 0; + while(p[n]>' ' && p[n] != ',') + n++; + if(n >= (int)sizeof(h->accept_language)) + n = (int)sizeof(h->accept_language) - 1; + memcpy(h->accept_language, p, n); + h->accept_language[n] = '\0'; + } + else if(strncasecmp(line, "expect", 6) == 0) + { + p = colon; + n = 0; + while(*p == ':' || *p == ' ' || *p == '\t') + p++; + while(p[n]>=' ') + n++; + if(strncasecmp(p, "100-continue", 12) == 0) { + h->respflags |= FLAG_CONTINUE; + syslog(LOG_DEBUG, "\"Expect: 100-Continue\" header detected"); + } + } #ifdef ENABLE_EVENTS else if(strncasecmp(line, "Callback", 8)==0) { @@ -115,7 +168,7 @@ ParseHttpHeaders(struct upnphttp * h) n = 0; while(p[n] != '>' && p[n] != '\r' ) n++; - h->req_Callback = p + 1; + h->req_CallbackOff = p + 1 - h->req_buf; h->req_CallbackLen = MAX(0, n - 1); } else if(strncasecmp(line, "SID", 3)==0) @@ -126,7 +179,7 @@ ParseHttpHeaders(struct upnphttp * h) n = 0; while(!isspace(p[n])) n++; - h->req_SID = p; + h->req_SIDOff = p - h->req_buf; h->req_SIDLen = n; } /* Timeout: Seconds-nnnn */ @@ -145,8 +198,24 @@ intervening space) by either an integer or the keyword h->req_Timeout = atoi(p+7); } } +#ifdef UPNP_STRICT + else if(strncasecmp(line, "nt", 2)==0) + { + p = colon + 1; + while(isspace(*p)) + p++; + n = 0; + while(!isspace(p[n])) + n++; + h->req_NTOff = p - h->req_buf; + h->req_NTLen = n; + } #endif +#endif } + /* the loop below won't run off the end of the buffer + * because the buffer is guaranteed to contain the \r\n\r\n + * character sequence */ while(!(line[0] == '\r' && line[1] == '\n')) line++; line += 2; @@ -157,65 +226,53 @@ intervening space) by either an integer or the keyword static void Send404(struct upnphttp * h) { -/* - static const char error404[] = "HTTP/1.1 404 Not found\r\n" - "Connection: close\r\n" - "Content-type: text/html\r\n" - "\r\n" - "404 Not Found" - "

Not Found

The requested URL was not found" - " on this server.\r\n"; - int n; - n = send(h->socket, error404, sizeof(error404) - 1, 0); - if(n < 0) - { - syslog(LOG_ERR, "Send404: send(http): %m"); - }*/ static const char body404[] = "404 Not Found" "

Not Found

The requested URL was not found" " on this server.\r\n"; + h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 404, "Not Found", body404, sizeof(body404) - 1); - SendResp_upnphttp(h); - CloseSocket_upnphttp(h); + SendRespAndClose_upnphttp(h); } +static void +Send405(struct upnphttp * h) +{ + static const char body405[] = + "405 Method Not Allowed" + "

Method Not Allowed

The HTTP Method " + "is not allowed on this resource.\r\n"; + + h->respflags |= FLAG_HTML; + BuildResp2_upnphttp(h, 405, "Method Not Allowed", + body405, sizeof(body405) - 1); + SendRespAndClose_upnphttp(h); +} + /* very minimalistic 501 error message */ static void Send501(struct upnphttp * h) { -/* - static const char error501[] = "HTTP/1.1 501 Not Implemented\r\n" - "Connection: close\r\n" - "Content-type: text/html\r\n" - "\r\n" + static const char body501[] = "501 Not Implemented" "

Not Implemented

The HTTP Method " "is not implemented by this server.\r\n"; - int n; - n = send(h->socket, error501, sizeof(error501) - 1, 0); - if(n < 0) - { - syslog(LOG_ERR, "Send501: send(http): %m"); - } -*/ - static const char body501[] = - "501 Not Implemented" - "

Not Implemented

The HTTP Method " - "is not implemented by this server.\r\n"; + h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 501, "Not Implemented", body501, sizeof(body501) - 1); - SendResp_upnphttp(h); - CloseSocket_upnphttp(h); + SendRespAndClose_upnphttp(h); } +/* findendheaders() find the \r\n\r\n character sequence and + * return a pointer to it. + * It returns NULL if not found */ static const char * findendheaders(const char * s, int len) { - while(len-->0) + while(len-->3) { if(s[0]=='\r' && s[1]=='\n' && s[2]=='\r' && s[3]=='\n') return s; @@ -238,8 +295,7 @@ sendDummyDesc(struct upnphttp * h) " " "\r\n"; BuildResp_upnphttp(h, xml_desc, sizeof(xml_desc)-1); - SendResp_upnphttp(h); - CloseSocket_upnphttp(h); + SendRespAndClose_upnphttp(h); } #endif @@ -263,8 +319,7 @@ sendXMLdesc(struct upnphttp * h, char * (f)(int *)) { BuildResp_upnphttp(h, desc, len); } - SendResp_upnphttp(h); - CloseSocket_upnphttp(h); + SendRespAndClose_upnphttp(h); free(desc); } @@ -275,13 +330,14 @@ ProcessHTTPPOST_upnphttp(struct upnphttp * h) { if((h->req_buflen - h->req_contentoff) >= h->req_contentlen) { - if(h->req_soapAction) + /* the request body is received */ + if(h->req_soapActionOff > 0) { /* we can process the request */ syslog(LOG_INFO, "SOAPAction: %.*s", - h->req_soapActionLen, h->req_soapAction); - ExecuteSoapAction(h, - h->req_soapAction, + h->req_soapActionLen, h->req_buf + h->req_soapActionOff); + ExecuteSoapAction(h, + h->req_buf + h->req_soapActionOff, h->req_soapActionLen); } else @@ -292,14 +348,27 @@ ProcessHTTPPOST_upnphttp(struct upnphttp * h) h->respflags = FLAG_HTML; BuildResp2_upnphttp(h, 400, "Bad Request", err400str, sizeof(err400str) - 1); - SendResp_upnphttp(h); - CloseSocket_upnphttp(h); + SendRespAndClose_upnphttp(h); } } + else if(h->respflags & FLAG_CONTINUE) + { + /* Sending the 100 Continue response */ + if(!h->res_buf) { + h->res_buf = malloc(256); + h->res_buf_alloclen = 256; + } + h->res_buflen = snprintf(h->res_buf, h->res_buf_alloclen, + "%s 100 Continue\r\n\r\n", h->HttpVer); + h->res_sent = 0; + h->state = ESendingContinue; + if(SendResp_upnphttp(h)) + h->state = EWaitingForHttpContent; + } else { /* waiting for remaining data */ - h->state = 1; + h->state = EWaitingForHttpContent; } } @@ -314,24 +383,24 @@ checkCallbackURL(struct upnphttp * h) char addrstr[48]; int ipv6; const char * p; - int i; + unsigned int i; - if(!h->req_Callback || h->req_CallbackLen < 8) + if(h->req_CallbackOff <= 0 || h->req_CallbackLen < 8) return 0; - if(memcmp(h->req_Callback, "http://", 7) != 0) + if(memcmp(h->req_buf + h->req_CallbackOff, "http://", 7) != 0) return 0; ipv6 = 0; i = 0; - p = h->req_Callback + 7; + p = h->req_buf + h->req_CallbackOff + 7; if(*p == '[') { p++; ipv6 = 1; while(*p != ']' && i < (sizeof(addrstr)-1) - && p < (h->req_Callback + h->req_CallbackLen)) + && p < (h->req_buf + h->req_CallbackOff + h->req_CallbackLen)) addrstr[i++] = *(p++); } else { while(*p != '/' && *p != ':' && i < (sizeof(addrstr)-1) - && p < (h->req_Callback + h->req_CallbackLen)) + && p < (h->req_buf + h->req_CallbackOff + h->req_CallbackLen)) addrstr[i++] = *(p++); } addrstr[i] = '\0'; @@ -374,37 +443,49 @@ ProcessHTTPSubscribe_upnphttp(struct upnphttp * h, con const char * sid; syslog(LOG_DEBUG, "ProcessHTTPSubscribe %s", path); syslog(LOG_DEBUG, "Callback '%.*s' Timeout=%d", - h->req_CallbackLen, h->req_Callback, h->req_Timeout); - syslog(LOG_DEBUG, "SID '%.*s'", h->req_SIDLen, h->req_SID); - if(!h->req_Callback && !h->req_SID) { + h->req_CallbackLen, h->req_buf + h->req_CallbackOff, + h->req_Timeout); + syslog(LOG_DEBUG, "SID '%.*s'", h->req_SIDLen, h->req_buf + h->req_SIDOff); + if((h->req_CallbackOff <= 0) && (h->req_SIDOff <= 0)) { /* Missing or invalid CALLBACK : 412 Precondition Failed. * If CALLBACK header is missing or does not contain a valid HTTP URL, * the publisher must respond with HTTP error 412 Precondition Failed*/ BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); - SendResp_upnphttp(h); - CloseSocket_upnphttp(h); + SendRespAndClose_upnphttp(h); } else { /* - add to the subscriber list - * - respond HTTP/x.x 200 OK + * - respond HTTP/x.x 200 OK * - Send the initial event message */ /* Server:, SID:; Timeout: Second-(xx|infinite) */ /* Check that the callback URL is on the same IP as * the request, and not on the internet, nor on ourself (DOS attack ?) */ - if(h->req_Callback) { + if(h->req_CallbackOff > 0) { +#ifdef UPNP_STRICT + /* SID: and Callback: are incompatible */ + if(h->req_SIDOff > 0) { + syslog(LOG_WARNING, "Both Callback: and SID: in SUBSCRIBE"); + BuildResp2_upnphttp(h, 400, "Incompatible header fields", 0, 0); + /* "NT: upnp:event" header is mandatory */ + } else if(h->req_NTOff <= 0 || h->req_NTLen != 10 || + 0 != memcmp("upnp:event", h->req_buf + h->req_NTOff, 10)) { + syslog(LOG_WARNING, "Invalid NT in SUBSCRIBE %.*s", + h->req_NTLen, h->req_buf + h->req_NTOff); + BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); + } else +#endif if(checkCallbackURL(h)) { - sid = upnpevents_addSubscriber(path, h->req_Callback, + sid = upnpevents_addSubscriber(path, h->req_buf + h->req_CallbackOff, h->req_CallbackLen, h->req_Timeout); h->respflags = FLAG_TIMEOUT; if(sid) { syslog(LOG_DEBUG, "generated sid=%s", sid); h->respflags |= FLAG_SID; - h->req_SID = sid; - h->req_SIDLen = strlen(sid); + h->res_SID = sid; } BuildResp_upnphttp(h, 0, 0); } else { syslog(LOG_WARNING, "Invalid Callback in SUBSCRIBE %.*s", - h->req_CallbackLen, h->req_Callback); + h->req_CallbackLen, h->req_buf + h->req_CallbackOff); BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); } } else { @@ -413,15 +494,22 @@ ProcessHTTPSubscribe_upnphttp(struct upnphttp * h, con 412 Precondition Failed. If a SID does not correspond to a known, un-expired subscription, the publisher must respond with HTTP error 412 Precondition Failed. */ - if(renewSubscription(h->req_SID, h->req_SIDLen, h->req_Timeout) < 0) { +#ifdef UPNP_STRICT + /* SID: and NT: headers are incompatibles */ + if(h->req_NTOff > 0) { + syslog(LOG_WARNING, "Both NT: and SID: in SUBSCRIBE"); + BuildResp2_upnphttp(h, 400, "Incompatible header fields", 0, 0); + } else +#endif + if(renewSubscription(h->req_buf + h->req_SIDOff, h->req_SIDLen, + h->req_Timeout) < 0) { BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); } else { h->respflags = FLAG_TIMEOUT; BuildResp_upnphttp(h, 0, 0); } } - SendResp_upnphttp(h); - CloseSocket_upnphttp(h); + SendRespAndClose_upnphttp(h); } } @@ -429,23 +517,54 @@ static void ProcessHTTPUnSubscribe_upnphttp(struct upnphttp * h, const char * path) { syslog(LOG_DEBUG, "ProcessHTTPUnSubscribe %s", path); - syslog(LOG_DEBUG, "SID '%.*s'", h->req_SIDLen, h->req_SID); + syslog(LOG_DEBUG, "SID '%.*s'", h->req_SIDLen, h->req_buf + h->req_SIDOff); /* Remove from the list */ - if(upnpevents_removeSubscriber(h->req_SID, h->req_SIDLen) < 0) { +#ifdef UPNP_STRICT + if(h->req_SIDOff <= 0 || h->req_SIDLen == 0) { + /* SID: header missing or empty */ BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); + } else if(h->req_CallbackOff > 0 || h->req_NTOff > 0) { + /* no NT: or Callback: header must be present */ + BuildResp2_upnphttp(h, 400, "Incompatible header fields", 0, 0); + } else +#endif + if(upnpevents_removeSubscriber(h->req_buf + h->req_SIDOff, h->req_SIDLen) < 0) { + BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); } else { BuildResp_upnphttp(h, 0, 0); } - SendResp_upnphttp(h); - CloseSocket_upnphttp(h); + SendRespAndClose_upnphttp(h); } #endif -/* Parse and process Http Query - * called once all the HTTP headers have been received. */ +/* Parse and process Http Query + * called once all the HTTP headers have been received, + * so it is guaranteed that h->req_buf contains the \r\n\r\n + * character sequence */ static void ProcessHttpQuery_upnphttp(struct upnphttp * h) { + static const struct { + const char * path; + char * (* f)(int *); + } path_desc[] = { + { ROOTDESC_PATH, genRootDesc}, + { WANIPC_PATH, genWANIPCn}, + { WANCFG_PATH, genWANCfg}, +#ifdef HAS_DUMMY_SERVICE + { DUMMY_PATH, NULL}, +#endif +#ifdef ENABLE_L3F_SERVICE + { L3F_PATH, genL3F}, +#endif +#ifdef ENABLE_6FC_SERVICE + { WANIP6FC_PATH, gen6FC}, +#endif +#ifdef ENABLE_DP_SERVICE + { DP_PATH, genDP}, +#endif + { NULL, NULL} + }; char HttpCommand[16]; char HttpUrl[128]; char * HttpVer; @@ -454,6 +573,9 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h) p = h->req_buf; if(!p) return; + /* note : checking (*p != '\r') is enough to avoid runing off the + * end of the buffer, because h->req_buf is guaranteed to contain + * the \r\n\r\n character sequence */ for(i = 0; i<15 && *p != ' ' && *p != '\r'; i++) HttpCommand[i] = *(p++); HttpCommand[i] = '\0'; @@ -479,47 +601,37 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h) else if(strcmp("GET", HttpCommand) == 0) { h->req_command = EGet; - if(strcasecmp(ROOTDESC_PATH, HttpUrl) == 0) - { - sendXMLdesc(h, genRootDesc); - } - else if(strcasecmp(WANIPC_PATH, HttpUrl) == 0) - { - sendXMLdesc(h, genWANIPCn); - } - else if(strcasecmp(WANCFG_PATH, HttpUrl) == 0) - { - sendXMLdesc(h, genWANCfg); - } + for(i=0; path_desc[i].path; i++) { + if(strcasecmp(path_desc[i].path, HttpUrl) == 0) { + if(path_desc[i].f) + sendXMLdesc(h, path_desc[i].f); + else #ifdef HAS_DUMMY_SERVICE - else if(strcasecmp(DUMMY_PATH, HttpUrl) == 0) - { - sendDummyDesc(h); - } + sendDummyDesc(h); +#else + continue; #endif -#ifdef ENABLE_L3F_SERVICE - else if(strcasecmp(L3F_PATH, HttpUrl) == 0) - { - sendXMLdesc(h, genL3F); + return; + } } -#endif -#ifdef ENABLE_6FC_SERVICE - else if(strcasecmp(WANIP6FC_PATH, HttpUrl) == 0) - { - sendXMLdesc(h, gen6FC); + if(0 == memcmp(HttpUrl, "/ctl/", 5)) { + /* 405 Method Not Allowed + * Allow: POST */ + h->respflags = FLAG_ALLOW_POST; + Send405(h); + return; } -#endif -#ifdef ENABLE_DP_SERVICE - else if(strcasecmp(DP_PATH, HttpUrl) == 0) - { - sendXMLdesc(h, genDP); +#ifdef ENABLE_EVENTS + if(0 == memcmp(HttpUrl, "/evt/", 5)) { + /* 405 Method Not Allowed + * Allow: SUBSCRIBE, UNSUBSCRIBE */ + h->respflags = FLAG_ALLOW_SUB_UNSUB; + Send405(h); + return; } #endif - else - { - syslog(LOG_NOTICE, "%s not found, responding ERROR 404", HttpUrl); - Send404(h); - } + syslog(LOG_NOTICE, "%s not found, responding ERROR 404", HttpUrl); + Send404(h); } #ifdef ENABLE_EVENTS else if(strcmp("SUBSCRIBE", HttpCommand) == 0) @@ -550,66 +662,106 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h) void Process_upnphttp(struct upnphttp * h) { + char * h_tmp; char buf[2048]; int n; + if(!h) return; switch(h->state) { - case 0: - n = recv(h->socket, buf, 2048, 0); + case EWaitingForHttpRequest: + n = recv(h->socket, buf, sizeof(buf), 0); if(n<0) { - syslog(LOG_ERR, "recv (state0): %m"); - h->state = 100; + if(errno != EAGAIN && + errno != EWOULDBLOCK && + errno != EINTR) + { + syslog(LOG_ERR, "recv (state0): %m"); + h->state = EToDelete; + } + /* if errno is EAGAIN, EWOULDBLOCK or EINTR, try again later */ } else if(n==0) { - syslog(LOG_WARNING, "HTTP Connection closed inexpectedly"); - h->state = 100; + syslog(LOG_WARNING, "HTTP Connection closed unexpectedly"); + h->state = EToDelete; } else { const char * endheaders; /* if 1st arg of realloc() is null, * realloc behaves the same as malloc() */ - h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen + 1); - memcpy(h->req_buf + h->req_buflen, buf, n); - h->req_buflen += n; - h->req_buf[h->req_buflen] = '\0'; + h_tmp = (char *)realloc(h->req_buf, n + h->req_buflen + 1); + if (h_tmp == NULL) + { + syslog(LOG_WARNING, "Unable to allocate new memory for h->req_buf)"); + h->state = EToDelete; + } + else + { + h->req_buf = h_tmp; + memcpy(h->req_buf + h->req_buflen, buf, n); + h->req_buflen += n; + h->req_buf[h->req_buflen] = '\0'; + } /* search for the string "\r\n\r\n" */ endheaders = findendheaders(h->req_buf, h->req_buflen); if(endheaders) { + /* at this point, the request buffer (h->req_buf) + * is guaranteed to contain the \r\n\r\n character sequence */ h->req_contentoff = endheaders - h->req_buf + 4; ProcessHttpQuery_upnphttp(h); } } break; - case 1: - n = recv(h->socket, buf, 2048, 0); + case EWaitingForHttpContent: + n = recv(h->socket, buf, sizeof(buf), 0); if(n<0) { - syslog(LOG_ERR, "recv (state1): %m"); - h->state = 100; + if(errno != EAGAIN && + errno != EWOULDBLOCK && + errno != EINTR) + { + syslog(LOG_ERR, "recv (state1): %m"); + h->state = EToDelete; + } + /* if errno is EAGAIN, EWOULDBLOCK or EINTR, try again later */ } else if(n==0) { syslog(LOG_WARNING, "HTTP Connection closed inexpectedly"); - h->state = 100; + h->state = EToDelete; } else { - /*fwrite(buf, 1, n, stdout);*/ /* debug */ - h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen); - memcpy(h->req_buf + h->req_buflen, buf, n); - h->req_buflen += n; - if((h->req_buflen - h->req_contentoff) >= h->req_contentlen) + void * tmp = realloc(h->req_buf, n + h->req_buflen); + if(!tmp) { - ProcessHTTPPOST_upnphttp(h); + syslog(LOG_ERR, "memory allocation error %m"); + h->state = EToDelete; } + else + { + h->req_buf = tmp; + memcpy(h->req_buf + h->req_buflen, buf, n); + h->req_buflen += n; + if((h->req_buflen - h->req_contentoff) >= h->req_contentlen) + { + ProcessHTTPPOST_upnphttp(h); + } + } } break; + case ESendingContinue: + if(SendResp_upnphttp(h)) + h->state = EWaitingForHttpContent; + break; + case ESendingAndClosing: + SendRespAndClose_upnphttp(h); + break; default: syslog(LOG_WARNING, "Unexpected state: %d", h->state); } @@ -617,7 +769,6 @@ Process_upnphttp(struct upnphttp * h) static const char httpresphead[] = "%s %d %s\r\n" - /*"Content-Type: text/xml; charset=\"utf-8\"\r\n"*/ "Content-Type: %s\r\n" "Connection: close\r\n" "Content-Length: %d\r\n" @@ -641,23 +792,42 @@ BuildHeader_upnphttp(struct upnphttp * h, int respcode int bodylen) { int templen; - if(!h->res_buf) - { - templen = sizeof(httpresphead) + 128 + bodylen; + if(!h->res_buf || + h->res_buf_alloclen < ((int)sizeof(httpresphead) + 256 + bodylen)) { + if(h->res_buf) + free(h->res_buf); + templen = sizeof(httpresphead) + 256 + bodylen; h->res_buf = (char *)malloc(templen); - if(!h->res_buf) - { + if(!h->res_buf) { syslog(LOG_ERR, "malloc error in BuildHeader_upnphttp()"); return; } h->res_buf_alloclen = templen; } + h->res_sent = 0; h->res_buflen = snprintf(h->res_buf, h->res_buf_alloclen, httpresphead, h->HttpVer, respcode, respmsg, - (h->respflags&FLAG_HTML)?"text/html":"text/xml", + (h->respflags&FLAG_HTML)?"text/html":"text/xml; charset=\"utf-8\"", bodylen); + /* Content-Type MUST be 'text/xml; charset="utf-8"' according to UDA v1.1 */ + /* Content-Type MUST be 'text/xml' according to UDA v1.0 */ /* Additional headers */ +#ifdef ENABLE_HTTP_DATE + { + char http_date[64]; + time_t t; + struct tm tm; + time(&t); + gmtime_r(&t, &tm); + /* %a and %b depend on locale */ + strftime(http_date, sizeof(http_date), + "%a, %d %b %Y %H:%M:%S GMT", &tm); + h->res_buflen += snprintf(h->res_buf + h->res_buflen, + h->res_buf_alloclen - h->res_buflen, + "Date: %s\r\n", http_date); + } +#endif #ifdef ENABLE_EVENTS if(h->respflags & FLAG_TIMEOUT) { h->res_buflen += snprintf(h->res_buf + h->res_buflen, @@ -676,9 +846,25 @@ BuildHeader_upnphttp(struct upnphttp * h, int respcode if(h->respflags & FLAG_SID) { h->res_buflen += snprintf(h->res_buf + h->res_buflen, h->res_buf_alloclen - h->res_buflen, - "SID: %s\r\n", h->req_SID); + "SID: %s\r\n", h->res_SID); } #endif + if(h->respflags & FLAG_ALLOW_POST) { + h->res_buflen += snprintf(h->res_buf + h->res_buflen, + h->res_buf_alloclen - h->res_buflen, + "Allow: %s\r\n", "POST"); + } else if(h->respflags & FLAG_ALLOW_SUB_UNSUB) { + h->res_buflen += snprintf(h->res_buf + h->res_buflen, + h->res_buf_alloclen - h->res_buflen, + "Allow: %s\r\n", "SUBSCRIBE, UNSUBSCRIBE"); + } + if(h->accept_language[0] != '\0') { + /* defaulting to "en" */ + h->res_buflen += snprintf(h->res_buf + h->res_buflen, + h->res_buf_alloclen - h->res_buflen, + "Content-Language: %s\r\n", + h->accept_language[0] == '*' ? "en" : h->accept_language); + } h->res_buf[h->res_buflen++] = '\r'; h->res_buf[h->res_buflen++] = '\n'; if(h->res_buf_alloclen < (h->res_buflen + bodylen)) @@ -716,32 +902,74 @@ BuildResp_upnphttp(struct upnphttp * h, BuildResp2_upnphttp(h, 200, "OK", body, bodylen); } -void +int SendResp_upnphttp(struct upnphttp * h) { - char * p; ssize_t n; - size_t len; - p = h->res_buf; - len = h->res_buflen; - while (len > 0) + + while (h->res_sent < h->res_buflen) { - n = send(h->socket, p, len, 0); + n = send(h->socket, h->res_buf + h->res_sent, + h->res_buflen - h->res_sent, 0); if(n<0) { + if(errno == EINTR) + continue; /* try again immediatly */ + if(errno == EAGAIN || errno == EWOULDBLOCK) + { + /* try again later */ + return 0; + } syslog(LOG_ERR, "send(res_buf): %m"); + break; /* avoid infinite loop */ } else if(n == 0) { - syslog(LOG_ERR, "send(res_buf): %zd bytes sent (out of %zu)", - n, len); + syslog(LOG_ERR, "send(res_buf): %d bytes sent (out of %d)", + h->res_sent, h->res_buflen); break; } else { - p += n; - len -= n; + h->res_sent += n; } } + return 1; /* finished */ +} + +void +SendRespAndClose_upnphttp(struct upnphttp * h) +{ + ssize_t n; + + while (h->res_sent < h->res_buflen) + { + n = send(h->socket, h->res_buf + h->res_sent, + h->res_buflen - h->res_sent, 0); + if(n<0) + { + if(errno == EINTR) + continue; /* try again immediatly */ + if(errno == EAGAIN || errno == EWOULDBLOCK) + { + /* try again later */ + h->state = ESendingAndClosing; + return; + } + syslog(LOG_ERR, "send(res_buf): %m"); + break; /* avoid infinite loop */ + } + else if(n == 0) + { + syslog(LOG_ERR, "send(res_buf): %d bytes sent (out of %d)", + h->res_sent, h->res_buflen); + break; + } + else + { + h->res_sent += n; + } + } + CloseSocket_upnphttp(h); }