Path: blob/master/thirdparty/miniupnpc/src/miniwget.c
9904 views
/* $Id: miniwget.c,v 1.88 2025/05/25 21:56:49 nanard Exp $ */1/* Project : miniupnp2* Website : http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/3* Author : Thomas Bernard4* Copyright (c) 2005-2025 Thomas Bernard5* This software is subject to the conditions detailed in the6* LICENCE file provided in this distribution. */78#include <stdio.h>9#include <stdlib.h>10#include <string.h>11#include <ctype.h>12#ifdef _WIN3213#define WIN32_LEAN_AND_MEAN14#include <winsock2.h>15#include <ws2tcpip.h>16#include <io.h>17#define MAXHOSTNAMELEN 6418#include "win32_snprintf.h"19#define socklen_t int20#ifndef strncasecmp21#if defined(_MSC_VER) && (_MSC_VER >= 1400)22#define strncasecmp _memicmp23#else /* defined(_MSC_VER) && (_MSC_VER >= 1400) */24#define strncasecmp memicmp25#endif /* defined(_MSC_VER) && (_MSC_VER >= 1400) */26#endif /* #ifndef strncasecmp */27#else /* #ifdef _WIN32 */28#include <unistd.h>29#include <sys/param.h>30#if defined(__amigaos__) && !defined(__amigaos4__)31#define socklen_t int32#else /* #if defined(__amigaos__) && !defined(__amigaos4__) */33#include <sys/select.h>34#endif /* #else defined(__amigaos__) && !defined(__amigaos4__) */35#include <sys/socket.h>36#include <netinet/in.h>37#include <arpa/inet.h>38#include <net/if.h>39#include <netdb.h>40#define closesocket close41#include <strings.h>42#endif /* #else _WIN32 */43#ifdef __GNU__44#define MAXHOSTNAMELEN 6445#endif /* __GNU__ */4647#ifndef MIN48#define MIN(x,y) (((x)<(y))?(x):(y))49#endif /* MIN */505152#include "miniupnpcstrings.h"53#include "miniwget.h"54#include "connecthostport.h"55#include "receivedata.h"5657#ifndef MAXHOSTNAMELEN58#define MAXHOSTNAMELEN 6459#endif6061/*62* Read a HTTP response from a socket.63* Process Content-Length and Transfer-encoding headers.64* return a pointer to the content buffer, which length is saved65* to the length parameter.66*/67void *68getHTTPResponse(SOCKET s, int * size, int * status_code)69{70char buf[2048];71int n;72int endofheaders = 0;73int chunked = 0;74int content_length = -1;75unsigned int chunksize = 0;76unsigned int bytestocopy = 0;77/* buffers : */78char * header_buf;79unsigned int header_buf_len = 2048;80unsigned int header_buf_used = 0;81char * content_buf;82unsigned int content_buf_len = 2048;83unsigned int content_buf_used = 0;84char chunksize_buf[32];85unsigned int chunksize_buf_index;86#ifdef DEBUG87char * reason_phrase = NULL;88int reason_phrase_len = 0;89#endif9091if(status_code) *status_code = -1;92header_buf = malloc(header_buf_len);93if(header_buf == NULL)94{95#ifdef DEBUG96fprintf(stderr, "%s: Memory allocation error\n", "getHTTPResponse");97#endif /* DEBUG */98*size = -1;99return NULL;100}101content_buf = malloc(content_buf_len);102if(content_buf == NULL)103{104free(header_buf);105#ifdef DEBUG106fprintf(stderr, "%s: Memory allocation error\n", "getHTTPResponse");107#endif /* DEBUG */108*size = -1;109return NULL;110}111chunksize_buf[0] = '\0';112chunksize_buf_index = 0;113114while((n = receivedata(s, buf, sizeof(buf), 5000, NULL)) > 0)115{116if(endofheaders == 0)117{118int i;119int linestart=0;120int colon=0;121int valuestart=0;122if(header_buf_used + n > header_buf_len) {123char * tmp = realloc(header_buf, header_buf_used + n);124if(tmp == NULL) {125/* memory allocation error */126free(header_buf);127free(content_buf);128*size = -1;129return NULL;130}131header_buf = tmp;132header_buf_len = header_buf_used + n;133}134memcpy(header_buf + header_buf_used, buf, n);135header_buf_used += n;136/* search for CR LF CR LF (end of headers)137* recognize also LF LF */138i = 0;139while(i < ((int)header_buf_used-1) && (endofheaders == 0)) {140if(header_buf[i] == '\r') {141i++;142if(header_buf[i] == '\n') {143i++;144if(i < (int)header_buf_used && header_buf[i] == '\r') {145i++;146if(i < (int)header_buf_used && header_buf[i] == '\n') {147endofheaders = i+1;148}149}150}151} else if(header_buf[i] == '\n') {152i++;153if(header_buf[i] == '\n') {154endofheaders = i+1;155}156}157i++;158}159if(endofheaders == 0)160continue;161/* parse header lines */162for(i = 0; i < endofheaders - 1; i++) {163if(linestart > 0 && colon <= linestart && header_buf[i]==':')164{165colon = i;166while(i < (endofheaders-1)167&& (header_buf[i+1] == ' ' || header_buf[i+1] == '\t'))168i++;169valuestart = i + 1;170}171/* detecting end of line */172else if(header_buf[i]=='\r' || header_buf[i]=='\n')173{174if(linestart == 0 && status_code)175{176/* Status line177* HTTP-Version SP Status-Code SP Reason-Phrase CRLF */178int sp;179for(sp = 0; sp < i - 1; sp++)180if(header_buf[sp] == ' ')181{182if(*status_code < 0)183{184if (header_buf[sp+1] >= '1' && header_buf[sp+1] <= '9')185*status_code = atoi(header_buf + sp + 1);186}187else188{189#ifdef DEBUG190reason_phrase = header_buf + sp + 1;191reason_phrase_len = i - sp - 1;192#endif193break;194}195}196#ifdef DEBUG197printf("HTTP status code = %d, Reason phrase = %.*s\n",198*status_code, reason_phrase_len, reason_phrase);199#endif200}201else if(colon > linestart && valuestart > colon)202{203#ifdef DEBUG204printf("header='%.*s', value='%.*s'\n",205colon-linestart, header_buf+linestart,206i-valuestart, header_buf+valuestart);207#endif208if(0==strncasecmp(header_buf+linestart, "content-length", colon-linestart))209{210content_length = atoi(header_buf+valuestart);211#ifdef DEBUG212printf("Content-Length: %d\n", content_length);213#endif214}215else if(0==strncasecmp(header_buf+linestart, "transfer-encoding", colon-linestart)216&& 0==strncasecmp(header_buf+valuestart, "chunked", 7))217{218#ifdef DEBUG219printf("chunked transfer-encoding!\n");220#endif221chunked = 1;222}223}224while((i < (int)header_buf_used) && (header_buf[i]=='\r' || header_buf[i] == '\n'))225i++;226linestart = i;227colon = linestart;228valuestart = 0;229}230}231/* copy the remaining of the received data back to buf */232n = header_buf_used - endofheaders;233memcpy(buf, header_buf + endofheaders, n);234/* if(headers) */235}236/* if we get there, endofheaders != 0.237* In the other case, there was a continue above */238/* content */239if(chunked)240{241int i = 0;242while(i < n)243{244if(chunksize == 0)245{246/* reading chunk size */247if(chunksize_buf_index == 0) {248/* skipping any leading CR LF */249if(buf[i] == '\r') i++;250if(i<n && buf[i] == '\n') i++;251}252while(i<n && isxdigit(buf[i])253&& chunksize_buf_index < (sizeof(chunksize_buf)-1))254{255chunksize_buf[chunksize_buf_index++] = buf[i];256chunksize_buf[chunksize_buf_index] = '\0';257i++;258}259while(i<n && buf[i] != '\r' && buf[i] != '\n')260i++; /* discarding chunk-extension */261if(i<n && buf[i] == '\r') i++;262if(i<n && buf[i] == '\n') {263unsigned int j;264for(j = 0; j < chunksize_buf_index; j++) {265if(chunksize_buf[j] >= '0'266&& chunksize_buf[j] <= '9')267chunksize = (chunksize << 4) + (chunksize_buf[j] - '0');268else269chunksize = (chunksize << 4) + ((chunksize_buf[j] | 32) - 'a' + 10);270}271chunksize_buf[0] = '\0';272chunksize_buf_index = 0;273i++;274} else {275/* not finished to get chunksize */276continue;277}278#ifdef DEBUG279printf("chunksize = %u (%x)\n", chunksize, chunksize);280#endif281if(chunksize == 0)282{283#ifdef DEBUG284printf("end of HTTP content - %d %d\n", i, n);285/*printf("'%.*s'\n", n-i, buf+i);*/286#endif287goto end_of_stream;288}289}290/* it is guaranteed that (n >= i) */291bytestocopy = (chunksize < (unsigned int)(n - i))?chunksize:(unsigned int)(n - i);292if((content_buf_used + bytestocopy) > content_buf_len)293{294char * tmp;295if((content_length >= 0) && ((unsigned int)content_length >= (content_buf_used + bytestocopy))) {296content_buf_len = content_length;297} else {298content_buf_len = content_buf_used + bytestocopy;299}300tmp = realloc(content_buf, content_buf_len);301if(tmp == NULL) {302/* memory allocation error */303free(content_buf);304free(header_buf);305*size = -1;306return NULL;307}308content_buf = tmp;309}310memcpy(content_buf + content_buf_used, buf + i, bytestocopy);311content_buf_used += bytestocopy;312i += bytestocopy;313chunksize -= bytestocopy;314}315}316else317{318/* not chunked */319if(content_length > 0320&& (content_buf_used + n) > (unsigned int)content_length) {321/* skipping additional bytes */322n = content_length - content_buf_used;323}324if(content_buf_used + n > content_buf_len)325{326char * tmp;327if(content_length >= 0328&& (unsigned int)content_length >= (content_buf_used + n)) {329content_buf_len = content_length;330} else {331content_buf_len = content_buf_used + n;332}333tmp = realloc(content_buf, content_buf_len);334if(tmp == NULL) {335/* memory allocation error */336free(content_buf);337free(header_buf);338*size = -1;339return NULL;340}341content_buf = tmp;342}343memcpy(content_buf + content_buf_used, buf, n);344content_buf_used += n;345}346/* use the Content-Length header value if available */347if(content_length > 0 && content_buf_used >= (unsigned int)content_length)348{349#ifdef DEBUG350printf("End of HTTP content\n");351#endif352break;353}354}355end_of_stream:356free(header_buf);357*size = content_buf_used;358if(content_buf_used == 0)359{360free(content_buf);361content_buf = NULL;362}363return content_buf;364}365366/* miniwget3() :367* do all the work.368* Return NULL if something failed. */369static void *370miniwget3(const char * host,371unsigned short port, const char * path,372int * size, char * addr_str, int addr_str_len,373const char * httpversion, unsigned int scope_id,374int * status_code)375{376char buf[2048];377SOCKET s;378int n;379int len;380int sent;381void * content;382383*size = 0;384s = connecthostport(host, port, scope_id);385if(ISINVALID(s))386return NULL;387388/* get address for caller ! */389if(addr_str)390{391struct sockaddr_storage saddr;392socklen_t saddrlen;393394saddrlen = sizeof(saddr);395if(getsockname(s, (struct sockaddr *)&saddr, &saddrlen) < 0)396{397perror("getsockname");398}399else400{401#if defined(__amigaos__) && !defined(__amigaos4__)402/* using INT WINAPI WSAAddressToStringA(LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFOA, LPSTR, LPDWORD);403* But his function make a string with the port : nn.nn.nn.nn:port */404/* if(WSAAddressToStringA((SOCKADDR *)&saddr, sizeof(saddr),405NULL, addr_str, (DWORD *)&addr_str_len))406{407printf("WSAAddressToStringA() failed : %d\n", WSAGetLastError());408}*/409/* the following code is only compatible with ip v4 addresses */410strncpy(addr_str, inet_ntoa(((struct sockaddr_in *)&saddr)->sin_addr), addr_str_len);411#else412#if 0413if(saddr.sa_family == AF_INET6) {414inet_ntop(AF_INET6,415&(((struct sockaddr_in6 *)&saddr)->sin6_addr),416addr_str, addr_str_len);417} else {418inet_ntop(AF_INET,419&(((struct sockaddr_in *)&saddr)->sin_addr),420addr_str, addr_str_len);421}422#endif423/* getnameinfo return ip v6 address with the scope identifier424* such as : 2a01:e35:8b2b:7330::%4281128194 */425n = getnameinfo((const struct sockaddr *)&saddr, saddrlen,426addr_str, addr_str_len,427NULL, 0,428NI_NUMERICHOST | NI_NUMERICSERV);429if(n != 0) {430#ifdef _WIN32431fprintf(stderr, "getnameinfo() failed : %d\n", n);432#else433fprintf(stderr, "getnameinfo() failed : %s\n", gai_strerror(n));434#endif435}436#endif437}438#ifdef DEBUG439printf("address miniwget : %s\n", addr_str);440#endif441}442443len = snprintf(buf, sizeof(buf),444"GET %s HTTP/%s\r\n"445"Host: %s:%d\r\n"446"Connection: close\r\n"447"User-Agent: " OS_STRING " " UPNP_VERSION_STRING " MiniUPnPc/" MINIUPNPC_VERSION_STRING "\r\n"448"\r\n",449path, httpversion, host, port);450if ((unsigned int)len >= sizeof(buf))451{452closesocket(s);453return NULL;454}455sent = 0;456/* sending the HTTP request */457while(sent < len)458{459n = send(s, buf+sent, len-sent, 0);460if(n < 0)461{462perror("send");463closesocket(s);464return NULL;465}466else467{468sent += n;469}470}471content = getHTTPResponse(s, size, status_code);472closesocket(s);473return content;474}475476/* parseURL()477* arguments :478* url : source string not modified479* hostname : hostname destination string (size of MAXHOSTNAMELEN+1)480* port : port (destination)481* path : pointer to the path part of the URL482*483* Return values :484* 0 - Failure485* 1 - Success */486int487parseURL(const char * url,488char * hostname, unsigned short * port,489char * * path, unsigned int * scope_id)490{491char * p1, *p2, *p3;492if(!url)493return 0;494p1 = strstr(url, "://");495if(!p1)496return 0;497p1 += 3;498if( (url[0]!='h') || (url[1]!='t')499||(url[2]!='t') || (url[3]!='p'))500return 0;501memset(hostname, 0, MAXHOSTNAMELEN + 1);502if(*p1 == '[')503{504/* IP v6 : http://[2a00:1450:8002::6a]/path/abc */505char * scope;506scope = strchr(p1, '%');507p2 = strchr(p1, ']');508if(p2 && scope && scope < p2 && scope_id) {509/* parse scope */510#ifdef IF_NAMESIZE511char tmp[IF_NAMESIZE];512int l;513scope++;514/* "%25" is just '%' in URL encoding */515if(scope[0] == '2' && scope[1] == '5')516scope += 2; /* skip "25" */517l = p2 - scope;518if(l >= IF_NAMESIZE)519l = IF_NAMESIZE - 1;520memcpy(tmp, scope, l);521tmp[l] = '\0';522*scope_id = if_nametoindex(tmp);523if(*scope_id == 0) {524*scope_id = (unsigned int)strtoul(tmp, NULL, 10);525}526#else527/* under windows, scope is numerical */528char tmp[8];529size_t l;530scope++;531/* "%25" is just '%' in URL encoding */532if(scope[0] == '2' && scope[1] == '5')533scope += 2; /* skip "25" */534l = p2 - scope;535if(l >= sizeof(tmp))536l = sizeof(tmp) - 1;537memcpy(tmp, scope, l);538tmp[l] = '\0';539*scope_id = (unsigned int)strtoul(tmp, NULL, 10);540#endif541}542p3 = strchr(p1, '/');543if(p2 && p3)544{545p2++;546strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1)));547if(*p2 == ':')548{549*port = 0;550p2++;551while( (*p2 >= '0') && (*p2 <= '9'))552{553*port *= 10;554*port += (unsigned short)(*p2 - '0');555p2++;556}557}558else559{560*port = 80;561}562*path = p3;563return 1;564}565}566p2 = strchr(p1, ':');567p3 = strchr(p1, '/');568if(!p3)569return 0;570if(!p2 || (p2>p3))571{572strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p3-p1)));573*port = 80;574}575else576{577strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1)));578*port = 0;579p2++;580while( (*p2 >= '0') && (*p2 <= '9'))581{582*port *= 10;583*port += (unsigned short)(*p2 - '0');584p2++;585}586}587*path = p3;588return 1;589}590591void *592miniwget(const char * url, int * size,593unsigned int scope_id, int * status_code)594{595unsigned short port;596char * path;597/* protocol://host:port/chemin */598char hostname[MAXHOSTNAMELEN+1];599*size = 0;600if(!parseURL(url, hostname, &port, &path, &scope_id))601return NULL;602#ifdef DEBUG603printf("parsed url : hostname='%s' port=%hu path='%s' scope_id=%u\n",604hostname, port, path, scope_id);605#endif606return miniwget3(hostname, port, path, size, 0, 0, "1.1", scope_id, status_code);607}608609void *610miniwget_getaddr(const char * url, int * size,611char * addr, int addrlen, unsigned int scope_id,612int * status_code)613{614unsigned short port;615char * path;616/* protocol://host:port/path */617char hostname[MAXHOSTNAMELEN+1];618*size = 0;619if(addr)620addr[0] = '\0';621if(!parseURL(url, hostname, &port, &path, &scope_id))622return NULL;623#ifdef DEBUG624printf("parsed url : hostname='%s' port=%hu path='%s' scope_id=%u\n",625hostname, port, path, scope_id);626#endif627return miniwget3(hostname, port, path, size, addr, addrlen, "1.1", scope_id, status_code);628}629630631