|
version 1.1, 2012/02/21 23:16:02
|
version 1.1.1.3, 2013/07/22 00:32:35
|
|
Line 2
|
Line 2
|
| /* Project : miniupnp |
/* Project : miniupnp |
| * Website : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ |
* Website : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ |
| * Author : Thomas Bernard |
* Author : Thomas Bernard |
| * Copyright (c) 2005-2008 Thomas Bernard | * Copyright (c) 2005-2012 Thomas Bernard |
| * This software is subject to the conditions detailed in the |
* This software is subject to the conditions detailed in the |
| * LICENCE file included in this distribution. |
* LICENCE file included in this distribution. |
| * */ |
* */ |
|
Line 13
|
Line 13
|
| #include <sys/types.h> |
#include <sys/types.h> |
| #include <sys/socket.h> |
#include <sys/socket.h> |
| #include <sys/param.h> |
#include <sys/param.h> |
| |
#include <netinet/in.h> |
| |
#include <arpa/inet.h> |
| #include <syslog.h> |
#include <syslog.h> |
| #include <ctype.h> |
#include <ctype.h> |
| |
#include <errno.h> |
| #include "config.h" |
#include "config.h" |
| |
#ifdef ENABLE_HTTP_DATE |
| |
#include <time.h> |
| |
#endif |
| #include "upnphttp.h" |
#include "upnphttp.h" |
| #include "upnpdescgen.h" |
#include "upnpdescgen.h" |
| #include "miniupnpdpath.h" |
#include "miniupnpdpath.h" |
| #include "upnpsoap.h" |
#include "upnpsoap.h" |
| #include "upnpevents.h" |
#include "upnpevents.h" |
| |
#include "upnputils.h" |
| |
|
| struct upnphttp * | struct upnphttp * |
| New_upnphttp(int s) |
New_upnphttp(int s) |
| { |
{ |
| struct upnphttp * ret; |
struct upnphttp * ret; |
|
Line 33 New_upnphttp(int s)
|
Line 40 New_upnphttp(int s)
|
| return NULL; |
return NULL; |
| memset(ret, 0, sizeof(struct upnphttp)); |
memset(ret, 0, sizeof(struct upnphttp)); |
| ret->socket = s; |
ret->socket = s; |
| |
if(!set_non_blocking(s)) |
| |
syslog(LOG_WARNING, "New_upnphttp::set_non_blocking(): %m"); |
| return ret; |
return ret; |
| } |
} |
| |
|
|
Line 44 CloseSocket_upnphttp(struct upnphttp * h)
|
Line 53 CloseSocket_upnphttp(struct upnphttp * h)
|
| syslog(LOG_ERR, "CloseSocket_upnphttp: close(%d): %m", h->socket); |
syslog(LOG_ERR, "CloseSocket_upnphttp: close(%d): %m", h->socket); |
| } |
} |
| h->socket = -1; |
h->socket = -1; |
| h->state = 100; | h->state = EToDelete; |
| } |
} |
| |
|
| void |
void |
|
Line 62 Delete_upnphttp(struct upnphttp * h)
|
Line 71 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 |
static void |
| ParseHttpHeaders(struct upnphttp * h) |
ParseHttpHeaders(struct upnphttp * h) |
| { |
{ |
|
Line 70 ParseHttpHeaders(struct upnphttp * h)
|
Line 81 ParseHttpHeaders(struct upnphttp * h)
|
| char * colon; |
char * colon; |
| char * p; |
char * p; |
| int n; |
int n; |
| |
if((h->req_buf == NULL) || (h->req_contentoff <= 0)) |
| |
return; |
| line = h->req_buf; |
line = h->req_buf; |
| /* TODO : check if req_buf, contentoff are ok */ |
|
| while(line < (h->req_buf + h->req_contentoff)) |
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(colon) |
| { |
{ |
| if(strncasecmp(line, "Content-Length", 14)==0) |
if(strncasecmp(line, "Content-Length", 14)==0) |
|
Line 83 ParseHttpHeaders(struct upnphttp * h)
|
Line 104 ParseHttpHeaders(struct upnphttp * h)
|
| while(*p < '0' || *p > '9') |
while(*p < '0' || *p > '9') |
| p++; |
p++; |
| h->req_contentlen = atoi(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("*** Content-Lenght = %d ***\n", h->req_contentlen); |
| printf(" readbufflen=%d contentoff = %d\n", |
printf(" readbufflen=%d contentoff = %d\n", |
| h->req_buflen, h->req_contentoff);*/ |
h->req_buflen, h->req_contentoff);*/ |
|
Line 94 ParseHttpHeaders(struct upnphttp * h)
|
Line 119 ParseHttpHeaders(struct upnphttp * h)
|
| while(*p == ':' || *p == ' ' || *p == '\t') |
while(*p == ':' || *p == ' ' || *p == '\t') |
| p++; |
p++; |
| while(p[n]>=' ') |
while(p[n]>=' ') |
| { |
|
| n++; |
n++; |
| } |
|
| if((p[0] == '"' && p[n-1] == '"') |
if((p[0] == '"' && p[n-1] == '"') |
| || (p[0] == '\'' && p[n-1] == '\'')) |
|| (p[0] == '\'' && p[n-1] == '\'')) |
| { |
{ |
| p++; n -= 2; |
p++; n -= 2; |
| } |
} |
| h->req_soapAction = p; | h->req_soapActionOff = p - h->req_buf; |
| h->req_soapActionLen = n; |
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 |
#ifdef ENABLE_EVENTS |
| else if(strncasecmp(line, "Callback", 8)==0) |
else if(strncasecmp(line, "Callback", 8)==0) |
| { |
{ |
|
Line 114 ParseHttpHeaders(struct upnphttp * h)
|
Line 168 ParseHttpHeaders(struct upnphttp * h)
|
| n = 0; |
n = 0; |
| while(p[n] != '>' && p[n] != '\r' ) |
while(p[n] != '>' && p[n] != '\r' ) |
| n++; |
n++; |
| h->req_Callback = p + 1; | h->req_CallbackOff = p + 1 - h->req_buf; |
| h->req_CallbackLen = MAX(0, n - 1); |
h->req_CallbackLen = MAX(0, n - 1); |
| } |
} |
| else if(strncasecmp(line, "SID", 3)==0) |
else if(strncasecmp(line, "SID", 3)==0) |
|
Line 125 ParseHttpHeaders(struct upnphttp * h)
|
Line 179 ParseHttpHeaders(struct upnphttp * h)
|
| n = 0; |
n = 0; |
| while(!isspace(p[n])) |
while(!isspace(p[n])) |
| n++; |
n++; |
| h->req_SID = p; | h->req_SIDOff = p - h->req_buf; |
| h->req_SIDLen = n; |
h->req_SIDLen = n; |
| } |
} |
| /* Timeout: Seconds-nnnn */ |
/* Timeout: Seconds-nnnn */ |
|
Line 144 intervening space) by either an integer or the keyword
|
Line 198 intervening space) by either an integer or the keyword
|
| h->req_Timeout = atoi(p+7); |
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 |
| |
#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')) |
while(!(line[0] == '\r' && line[1] == '\n')) |
| line++; |
line++; |
| line += 2; |
line += 2; |
|
Line 156 intervening space) by either an integer or the keyword
|
Line 226 intervening space) by either an integer or the keyword
|
| static void |
static void |
| Send404(struct upnphttp * h) |
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" |
|
| "<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>" |
|
| "<BODY><H1>Not Found</H1>The requested URL was not found" |
|
| " on this server.</BODY></HTML>\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[] = |
static const char body404[] = |
| "<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>" |
"<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>" |
| "<BODY><H1>Not Found</H1>The requested URL was not found" |
"<BODY><H1>Not Found</H1>The requested URL was not found" |
| " on this server.</BODY></HTML>\r\n"; |
" on this server.</BODY></HTML>\r\n"; |
| |
|
| h->respflags = FLAG_HTML; |
h->respflags = FLAG_HTML; |
| BuildResp2_upnphttp(h, 404, "Not Found", |
BuildResp2_upnphttp(h, 404, "Not Found", |
| body404, sizeof(body404) - 1); |
body404, sizeof(body404) - 1); |
| SendResp_upnphttp(h); | SendRespAndClose_upnphttp(h); |
| CloseSocket_upnphttp(h); | |
| } |
} |
| |
|
| |
static void |
| |
Send405(struct upnphttp * h) |
| |
{ |
| |
static const char body405[] = |
| |
"<HTML><HEAD><TITLE>405 Method Not Allowed</TITLE></HEAD>" |
| |
"<BODY><H1>Method Not Allowed</H1>The HTTP Method " |
| |
"is not allowed on this resource.</BODY></HTML>\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 */ |
/* very minimalistic 501 error message */ |
| static void |
static void |
| Send501(struct upnphttp * h) |
Send501(struct upnphttp * h) |
| { |
{ |
| /* | static const char body501[] = |
| static const char error501[] = "HTTP/1.1 501 Not Implemented\r\n" | |
| "Connection: close\r\n" | |
| "Content-type: text/html\r\n" | |
| "\r\n" | |
| "<HTML><HEAD><TITLE>501 Not Implemented</TITLE></HEAD>" |
"<HTML><HEAD><TITLE>501 Not Implemented</TITLE></HEAD>" |
| "<BODY><H1>Not Implemented</H1>The HTTP Method " |
"<BODY><H1>Not Implemented</H1>The HTTP Method " |
| "is not implemented by this server.</BODY></HTML>\r\n"; |
"is not implemented by this server.</BODY></HTML>\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[] = | |
| "<HTML><HEAD><TITLE>501 Not Implemented</TITLE></HEAD>" | |
| "<BODY><H1>Not Implemented</H1>The HTTP Method " | |
| "is not implemented by this server.</BODY></HTML>\r\n"; | |
| h->respflags = FLAG_HTML; |
h->respflags = FLAG_HTML; |
| BuildResp2_upnphttp(h, 501, "Not Implemented", |
BuildResp2_upnphttp(h, 501, "Not Implemented", |
| body501, sizeof(body501) - 1); |
body501, sizeof(body501) - 1); |
| SendResp_upnphttp(h); | SendRespAndClose_upnphttp(h); |
| CloseSocket_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 * |
static const char * |
| findendheaders(const char * s, int len) |
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') |
if(s[0]=='\r' && s[1]=='\n' && s[2]=='\r' && s[3]=='\n') |
| return s; |
return s; |
|
Line 237 sendDummyDesc(struct upnphttp * h)
|
Line 295 sendDummyDesc(struct upnphttp * h)
|
| " <serviceStateTable />" |
" <serviceStateTable />" |
| "</scpd>\r\n"; |
"</scpd>\r\n"; |
| BuildResp_upnphttp(h, xml_desc, sizeof(xml_desc)-1); |
BuildResp_upnphttp(h, xml_desc, sizeof(xml_desc)-1); |
| SendResp_upnphttp(h); | SendRespAndClose_upnphttp(h); |
| CloseSocket_upnphttp(h); | |
| } |
} |
| #endif |
#endif |
| |
|
|
Line 262 sendXMLdesc(struct upnphttp * h, char * (f)(int *))
|
Line 319 sendXMLdesc(struct upnphttp * h, char * (f)(int *))
|
| { |
{ |
| BuildResp_upnphttp(h, desc, len); |
BuildResp_upnphttp(h, desc, len); |
| } |
} |
| SendResp_upnphttp(h); | SendRespAndClose_upnphttp(h); |
| CloseSocket_upnphttp(h); | |
| free(desc); |
free(desc); |
| } |
} |
| |
|
|
Line 274 ProcessHTTPPOST_upnphttp(struct upnphttp * h)
|
Line 330 ProcessHTTPPOST_upnphttp(struct upnphttp * h)
|
| { |
{ |
| if((h->req_buflen - h->req_contentoff) >= h->req_contentlen) |
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 */ |
/* we can process the request */ |
| syslog(LOG_INFO, "SOAPAction: %.*s", |
syslog(LOG_INFO, "SOAPAction: %.*s", |
| h->req_soapActionLen, h->req_soapAction); | h->req_soapActionLen, h->req_buf + h->req_soapActionOff); |
| ExecuteSoapAction(h, | ExecuteSoapAction(h, |
| h->req_soapAction, | h->req_buf + h->req_soapActionOff, |
| h->req_soapActionLen); |
h->req_soapActionLen); |
| } |
} |
| else |
else |
|
Line 291 ProcessHTTPPOST_upnphttp(struct upnphttp * h)
|
Line 348 ProcessHTTPPOST_upnphttp(struct upnphttp * h)
|
| h->respflags = FLAG_HTML; |
h->respflags = FLAG_HTML; |
| BuildResp2_upnphttp(h, 400, "Bad Request", |
BuildResp2_upnphttp(h, 400, "Bad Request", |
| err400str, sizeof(err400str) - 1); |
err400str, sizeof(err400str) - 1); |
| SendResp_upnphttp(h); | SendRespAndClose_upnphttp(h); |
| CloseSocket_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 |
else |
| { |
{ |
| /* waiting for remaining data */ |
/* waiting for remaining data */ |
| h->state = 1; | h->state = EWaitingForHttpContent; |
| } |
} |
| } |
} |
| |
|
| #ifdef ENABLE_EVENTS |
#ifdef ENABLE_EVENTS |
| |
/** |
| |
* returns 0 if the callback header value is not valid |
| |
* 1 if it is valid. |
| |
*/ |
| |
static int |
| |
checkCallbackURL(struct upnphttp * h) |
| |
{ |
| |
char addrstr[48]; |
| |
int ipv6; |
| |
const char * p; |
| |
unsigned int i; |
| |
|
| |
if(h->req_CallbackOff <= 0 || h->req_CallbackLen < 8) |
| |
return 0; |
| |
if(memcmp(h->req_buf + h->req_CallbackOff, "http://", 7) != 0) |
| |
return 0; |
| |
ipv6 = 0; |
| |
i = 0; |
| |
p = h->req_buf + h->req_CallbackOff + 7; |
| |
if(*p == '[') { |
| |
p++; |
| |
ipv6 = 1; |
| |
while(*p != ']' && i < (sizeof(addrstr)-1) |
| |
&& p < (h->req_buf + h->req_CallbackOff + h->req_CallbackLen)) |
| |
addrstr[i++] = *(p++); |
| |
} else { |
| |
while(*p != '/' && *p != ':' && i < (sizeof(addrstr)-1) |
| |
&& p < (h->req_buf + h->req_CallbackOff + h->req_CallbackLen)) |
| |
addrstr[i++] = *(p++); |
| |
} |
| |
addrstr[i] = '\0'; |
| |
if(ipv6) { |
| |
struct in6_addr addr; |
| |
if(inet_pton(AF_INET6, addrstr, &addr) <= 0) |
| |
return 0; |
| |
#ifdef ENABLE_IPV6 |
| |
if(!h->ipv6 |
| |
|| (0!=memcmp(&addr, &(h->clientaddr_v6), sizeof(struct in6_addr)))) |
| |
return 0; |
| |
#else |
| |
return 0; |
| |
#endif |
| |
} else { |
| |
struct in_addr addr; |
| |
if(inet_pton(AF_INET, addrstr, &addr) <= 0) |
| |
return 0; |
| |
#ifdef ENABLE_IPV6 |
| |
if(h->ipv6) { |
| |
if(!IN6_IS_ADDR_V4MAPPED(&(h->clientaddr_v6))) |
| |
return 0; |
| |
if(0!=memcmp(&addr, ((const char *)&(h->clientaddr_v6) + 12), 4)) |
| |
return 0; |
| |
} else { |
| |
if(0!=memcmp(&addr, &(h->clientaddr), sizeof(struct in_addr))) |
| |
return 0; |
| |
} |
| |
#else |
| |
if(0!=memcmp(&addr, &(h->clientaddr), sizeof(struct in_addr))) |
| |
return 0; |
| |
#endif |
| |
} |
| |
return 1; |
| |
} |
| |
|
| static void |
static void |
| ProcessHTTPSubscribe_upnphttp(struct upnphttp * h, const char * path) |
ProcessHTTPSubscribe_upnphttp(struct upnphttp * h, const char * path) |
| { |
{ |
| const char * sid; |
const char * sid; |
| syslog(LOG_DEBUG, "ProcessHTTPSubscribe %s", path); |
syslog(LOG_DEBUG, "ProcessHTTPSubscribe %s", path); |
| syslog(LOG_DEBUG, "Callback '%.*s' Timeout=%d", |
syslog(LOG_DEBUG, "Callback '%.*s' Timeout=%d", |
| h->req_CallbackLen, h->req_Callback, h->req_Timeout); | h->req_CallbackLen, h->req_buf + h->req_CallbackOff, |
| syslog(LOG_DEBUG, "SID '%.*s'", h->req_SIDLen, h->req_SID); | h->req_Timeout); |
| if(!h->req_Callback && !h->req_SID) { | 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. |
/* Missing or invalid CALLBACK : 412 Precondition Failed. |
| * If CALLBACK header is missing or does not contain a valid HTTP URL, |
* If CALLBACK header is missing or does not contain a valid HTTP URL, |
| * the publisher must respond with HTTP error 412 Precondition Failed*/ |
* the publisher must respond with HTTP error 412 Precondition Failed*/ |
| BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); |
BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); |
| SendResp_upnphttp(h); | SendRespAndClose_upnphttp(h); |
| CloseSocket_upnphttp(h); | |
| } else { |
} else { |
| /* - add to the subscriber list |
/* - add to the subscriber list |
| * - respond HTTP/x.x 200 OK | * - respond HTTP/x.x 200 OK |
| * - Send the initial event message */ |
* - Send the initial event message */ |
| /* Server:, SID:; Timeout: Second-(xx|infinite) */ |
/* Server:, SID:; Timeout: Second-(xx|infinite) */ |
| if(h->req_Callback) { | /* Check that the callback URL is on the same IP as |
| sid = upnpevents_addSubscriber(path, h->req_Callback, | * the request, and not on the internet, nor on ourself (DOS attack ?) */ |
| h->req_CallbackLen, h->req_Timeout); | if(h->req_CallbackOff > 0) { |
| h->respflags = FLAG_TIMEOUT; | #ifdef UPNP_STRICT |
| if(sid) { | /* SID: and Callback: are incompatible */ |
| syslog(LOG_DEBUG, "generated sid=%s", sid); | if(h->req_SIDOff > 0) { |
| h->respflags |= FLAG_SID; | syslog(LOG_WARNING, "Both Callback: and SID: in SUBSCRIBE"); |
| h->req_SID = sid; | BuildResp2_upnphttp(h, 400, "Incompatible header fields", 0, 0); |
| h->req_SIDLen = strlen(sid); | /* "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_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->res_SID = sid; |
| | } |
| | BuildResp_upnphttp(h, 0, 0); |
| | } else { |
| | syslog(LOG_WARNING, "Invalid Callback in SUBSCRIBE %.*s", |
| | h->req_CallbackLen, h->req_buf + h->req_CallbackOff); |
| | BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); |
| } |
} |
| BuildResp_upnphttp(h, 0, 0); |
|
| } else { |
} else { |
| /* subscription renew */ |
/* subscription renew */ |
| /* Invalid SID |
/* Invalid SID |
| 412 Precondition Failed. If a SID does not correspond to a known, |
412 Precondition Failed. If a SID does not correspond to a known, |
| un-expired subscription, the publisher must respond |
un-expired subscription, the publisher must respond |
| with HTTP error 412 Precondition Failed. */ |
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); |
BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); |
| } else { |
} else { |
| |
h->respflags = FLAG_TIMEOUT; |
| BuildResp_upnphttp(h, 0, 0); |
BuildResp_upnphttp(h, 0, 0); |
| } |
} |
| } |
} |
| SendResp_upnphttp(h); | SendRespAndClose_upnphttp(h); |
| CloseSocket_upnphttp(h); | |
| } |
} |
| } |
} |
| |
|
|
Line 355 static void
|
Line 517 static void
|
| ProcessHTTPUnSubscribe_upnphttp(struct upnphttp * h, const char * path) |
ProcessHTTPUnSubscribe_upnphttp(struct upnphttp * h, const char * path) |
| { |
{ |
| syslog(LOG_DEBUG, "ProcessHTTPUnSubscribe %s", 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 */ |
/* 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); |
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 { |
} else { |
| BuildResp_upnphttp(h, 0, 0); |
BuildResp_upnphttp(h, 0, 0); |
| } |
} |
| SendResp_upnphttp(h); | SendRespAndClose_upnphttp(h); |
| CloseSocket_upnphttp(h); | |
| } |
} |
| #endif |
#endif |
| |
|
| /* Parse and process Http Query | /* Parse and process Http Query |
| * called once all the HTTP headers have been received. */ | * 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 |
static void |
| ProcessHttpQuery_upnphttp(struct upnphttp * h) |
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 HttpCommand[16]; |
| char HttpUrl[128]; |
char HttpUrl[128]; |
| char * HttpVer; |
char * HttpVer; |
|
Line 380 ProcessHttpQuery_upnphttp(struct upnphttp * h)
|
Line 573 ProcessHttpQuery_upnphttp(struct upnphttp * h)
|
| p = h->req_buf; |
p = h->req_buf; |
| if(!p) |
if(!p) |
| return; |
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++) |
for(i = 0; i<15 && *p != ' ' && *p != '\r'; i++) |
| HttpCommand[i] = *(p++); |
HttpCommand[i] = *(p++); |
| HttpCommand[i] = '\0'; |
HttpCommand[i] = '\0'; |
|
Line 405 ProcessHttpQuery_upnphttp(struct upnphttp * h)
|
Line 601 ProcessHttpQuery_upnphttp(struct upnphttp * h)
|
| else if(strcmp("GET", HttpCommand) == 0) |
else if(strcmp("GET", HttpCommand) == 0) |
| { |
{ |
| h->req_command = EGet; |
h->req_command = EGet; |
| if(strcasecmp(ROOTDESC_PATH, HttpUrl) == 0) | for(i=0; path_desc[i].path; i++) { |
| { | if(strcasecmp(path_desc[i].path, HttpUrl) == 0) { |
| sendXMLdesc(h, genRootDesc); | if(path_desc[i].f) |
| } | sendXMLdesc(h, path_desc[i].f); |
| else if(strcasecmp(WANIPC_PATH, HttpUrl) == 0) | else |
| { | |
| sendXMLdesc(h, genWANIPCn); | |
| } | |
| else if(strcasecmp(WANCFG_PATH, HttpUrl) == 0) | |
| { | |
| sendXMLdesc(h, genWANCfg); | |
| } | |
| #ifdef HAS_DUMMY_SERVICE |
#ifdef HAS_DUMMY_SERVICE |
| else if(strcasecmp(DUMMY_PATH, HttpUrl) == 0) | sendDummyDesc(h); |
| { | #else |
| sendDummyDesc(h); | continue; |
| } | |
| #endif |
#endif |
| #ifdef ENABLE_L3F_SERVICE | return; |
| else if(strcasecmp(L3F_PATH, HttpUrl) == 0) | } |
| { | |
| sendXMLdesc(h, genL3F); | |
| } |
} |
| #endif | if(0 == memcmp(HttpUrl, "/ctl/", 5)) { |
| else | /* 405 Method Not Allowed |
| { | * Allow: POST */ |
| syslog(LOG_NOTICE, "%s not found, responding ERROR 404", HttpUrl); | h->respflags = FLAG_ALLOW_POST; |
| Send404(h); | Send405(h); |
| | return; |
| } |
} |
| |
#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 |
| |
syslog(LOG_NOTICE, "%s not found, responding ERROR 404", HttpUrl); |
| |
Send404(h); |
| } |
} |
| #ifdef ENABLE_EVENTS |
#ifdef ENABLE_EVENTS |
| else if(strcmp("SUBSCRIBE", HttpCommand) == 0) |
else if(strcmp("SUBSCRIBE", HttpCommand) == 0) |
|
Line 464 ProcessHttpQuery_upnphttp(struct upnphttp * h)
|
Line 662 ProcessHttpQuery_upnphttp(struct upnphttp * h)
|
| void |
void |
| Process_upnphttp(struct upnphttp * h) |
Process_upnphttp(struct upnphttp * h) |
| { |
{ |
| |
char * h_tmp; |
| char buf[2048]; |
char buf[2048]; |
| int n; |
int n; |
| |
|
| if(!h) |
if(!h) |
| return; |
return; |
| switch(h->state) |
switch(h->state) |
| { |
{ |
| case 0: | case EWaitingForHttpRequest: |
| n = recv(h->socket, buf, 2048, 0); | n = recv(h->socket, buf, sizeof(buf), 0); |
| if(n<0) |
if(n<0) |
| { |
{ |
| syslog(LOG_ERR, "recv (state0): %m"); | if(errno != EAGAIN && |
| h->state = 100; | 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) |
else if(n==0) |
| { |
{ |
| syslog(LOG_WARNING, "HTTP Connection closed inexpectedly"); | syslog(LOG_WARNING, "HTTP Connection closed unexpectedly"); |
| h->state = 100; | h->state = EToDelete; |
| } |
} |
| else |
else |
| { |
{ |
| const char * endheaders; |
const char * endheaders; |
| /* if 1st arg of realloc() is null, |
/* if 1st arg of realloc() is null, |
| * realloc behaves the same as malloc() */ |
* realloc behaves the same as malloc() */ |
| h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen + 1); | h_tmp = (char *)realloc(h->req_buf, n + h->req_buflen + 1); |
| memcpy(h->req_buf + h->req_buflen, buf, n); | if (h_tmp == NULL) |
| h->req_buflen += n; | { |
| h->req_buf[h->req_buflen] = '\0'; | 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" */ |
/* search for the string "\r\n\r\n" */ |
| endheaders = findendheaders(h->req_buf, h->req_buflen); |
endheaders = findendheaders(h->req_buf, h->req_buflen); |
| if(endheaders) |
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; |
h->req_contentoff = endheaders - h->req_buf + 4; |
| ProcessHttpQuery_upnphttp(h); |
ProcessHttpQuery_upnphttp(h); |
| } |
} |
| } |
} |
| break; |
break; |
| case 1: | case EWaitingForHttpContent: |
| n = recv(h->socket, buf, 2048, 0); | n = recv(h->socket, buf, sizeof(buf), 0); |
| if(n<0) |
if(n<0) |
| { |
{ |
| syslog(LOG_ERR, "recv (state1): %m"); | if(errno != EAGAIN && |
| h->state = 100; | 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) |
else if(n==0) |
| { |
{ |
| syslog(LOG_WARNING, "HTTP Connection closed inexpectedly"); |
syslog(LOG_WARNING, "HTTP Connection closed inexpectedly"); |
| h->state = 100; | h->state = EToDelete; |
| } |
} |
| else |
else |
| { |
{ |
| /*fwrite(buf, 1, n, stdout);*/ /* debug */ | void * tmp = realloc(h->req_buf, n + h->req_buflen); |
| h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen); | if(!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); | 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; |
break; |
| |
case ESendingContinue: |
| |
if(SendResp_upnphttp(h)) |
| |
h->state = EWaitingForHttpContent; |
| |
break; |
| |
case ESendingAndClosing: |
| |
SendRespAndClose_upnphttp(h); |
| |
break; |
| default: |
default: |
| syslog(LOG_WARNING, "Unexpected state: %d", h->state); |
syslog(LOG_WARNING, "Unexpected state: %d", h->state); |
| } |
} |
|
Line 531 Process_upnphttp(struct upnphttp * h)
|
Line 769 Process_upnphttp(struct upnphttp * h)
|
| |
|
| static const char httpresphead[] = |
static const char httpresphead[] = |
| "%s %d %s\r\n" |
"%s %d %s\r\n" |
| /*"Content-Type: text/xml; charset=\"utf-8\"\r\n"*/ |
|
| "Content-Type: %s\r\n" |
"Content-Type: %s\r\n" |
| "Connection: close\r\n" |
"Connection: close\r\n" |
| "Content-Length: %d\r\n" |
"Content-Length: %d\r\n" |
| /*"Server: miniupnpd/1.0 UPnP/1.0\r\n"*/ |
|
| "Server: " MINIUPNPD_SERVER_STRING "\r\n" |
"Server: " MINIUPNPD_SERVER_STRING "\r\n" |
| ; /*"\r\n";*/ |
; /*"\r\n";*/ |
| /* |
/* |
|
Line 556 BuildHeader_upnphttp(struct upnphttp * h, int respcode
|
Line 792 BuildHeader_upnphttp(struct upnphttp * h, int respcode
|
| int bodylen) |
int bodylen) |
| { |
{ |
| int templen; |
int templen; |
| if(!h->res_buf) | if(!h->res_buf || |
| { | h->res_buf_alloclen < ((int)sizeof(httpresphead) + 256 + bodylen)) { |
| templen = sizeof(httpresphead) + 128 + bodylen; | if(h->res_buf) |
| | free(h->res_buf); |
| | templen = sizeof(httpresphead) + 256 + bodylen; |
| h->res_buf = (char *)malloc(templen); |
h->res_buf = (char *)malloc(templen); |
| |
if(!h->res_buf) { |
| |
syslog(LOG_ERR, "malloc error in BuildHeader_upnphttp()"); |
| |
return; |
| |
} |
| h->res_buf_alloclen = templen; |
h->res_buf_alloclen = templen; |
| } |
} |
| |
h->res_sent = 0; |
| h->res_buflen = snprintf(h->res_buf, h->res_buf_alloclen, |
h->res_buflen = snprintf(h->res_buf, h->res_buf_alloclen, |
| httpresphead, h->HttpVer, |
httpresphead, h->HttpVer, |
| respcode, respmsg, |
respcode, respmsg, |
| (h->respflags&FLAG_HTML)?"text/html":"text/xml", | (h->respflags&FLAG_HTML)?"text/html":"text/xml; charset=\"utf-8\"", |
| bodylen); |
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 */ |
/* 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 |
#ifdef ENABLE_EVENTS |
| if(h->respflags & FLAG_TIMEOUT) { |
if(h->respflags & FLAG_TIMEOUT) { |
| h->res_buflen += snprintf(h->res_buf + h->res_buflen, |
h->res_buflen += snprintf(h->res_buf + h->res_buflen, |
|
Line 586 BuildHeader_upnphttp(struct upnphttp * h, int respcode
|
Line 846 BuildHeader_upnphttp(struct upnphttp * h, int respcode
|
| if(h->respflags & FLAG_SID) { |
if(h->respflags & FLAG_SID) { |
| h->res_buflen += snprintf(h->res_buf + h->res_buflen, |
h->res_buflen += snprintf(h->res_buf + h->res_buflen, |
| h->res_buf_alloclen - 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 |
#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++] = '\r'; |
| h->res_buf[h->res_buflen++] = '\n'; |
h->res_buf[h->res_buflen++] = '\n'; |
| if(h->res_buf_alloclen < (h->res_buflen + bodylen)) |
if(h->res_buf_alloclen < (h->res_buflen + bodylen)) |
| { |
{ |
| h->res_buf = (char *)realloc(h->res_buf, (h->res_buflen + bodylen)); | char * tmp; |
| h->res_buf_alloclen = h->res_buflen + bodylen; | tmp = (char *)realloc(h->res_buf, (h->res_buflen + bodylen)); |
| | if(tmp) |
| | { |
| | h->res_buf = tmp; |
| | h->res_buf_alloclen = h->res_buflen + bodylen; |
| | } |
| | else |
| | { |
| | syslog(LOG_ERR, "realloc error in BuildHeader_upnphttp()"); |
| | } |
| } |
} |
| } |
} |
| |
|
|
Line 617 BuildResp_upnphttp(struct upnphttp * h,
|
Line 902 BuildResp_upnphttp(struct upnphttp * h,
|
| BuildResp2_upnphttp(h, 200, "OK", body, bodylen); |
BuildResp2_upnphttp(h, 200, "OK", body, bodylen); |
| } |
} |
| |
|
| void | int |
| SendResp_upnphttp(struct upnphttp * h) |
SendResp_upnphttp(struct upnphttp * h) |
| { |
{ |
| int n; | ssize_t n; |
| n = send(h->socket, h->res_buf, h->res_buflen, 0); | |
| if(n<0) | while (h->res_sent < h->res_buflen) |
| { |
{ |
| syslog(LOG_ERR, "send(res_buf): %m"); | 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): %d bytes sent (out of %d)", |
| | h->res_sent, h->res_buflen); |
| | break; |
| | } |
| | else |
| | { |
| | h->res_sent += n; |
| | } |
| } |
} |
| else if(n < h->res_buflen) | return 1; /* finished */ |
| | } |
| | |
| | void |
| | SendRespAndClose_upnphttp(struct upnphttp * h) |
| | { |
| | ssize_t n; |
| | |
| | while (h->res_sent < h->res_buflen) |
| { |
{ |
| /* TODO : handle correctly this case */ | n = send(h->socket, h->res_buf + h->res_sent, |
| syslog(LOG_ERR, "send(res_buf): %d bytes sent (out of %d)", | h->res_buflen - h->res_sent, 0); |
| n, h->res_buflen); | 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); |
| } |
} |
| |
|