Patrick Kelley 8fd444092b initial
2025-05-07 15:35:15 -04:00

427 lines
9.5 KiB
C++

// Http.cpp
// 2013/11/20
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <ctype.h>
#include <string>
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include "Http.h"
//#define VERBOSE
namespace portable
{
/*
* read a line from file descriptor
* returns the number of bytes read. negative if a read error occured
* before the end of line or the max.
* cariage returns (CR) are ignored.
*/
int Http::http_read_line (int fd,char *buffer,int max)
{ /* not efficient on long lines (multiple unbuffered 1 char reads) */
int n=0;
while (n<max) {
if (read(fd,buffer,1)!=1) {
n= -n;
break;
}
n++;
if (*buffer=='\015') continue; /* ignore CR */
if (*buffer=='\012') break; /* LF is the separator */
buffer++;
}
*buffer=0;
return n;
}
/*
* read data from file descriptor
* retries reading until the number of bytes requested is read.
* returns the number of bytes read. negative if a read error (EOF) occured
* before the requested length.
*/
int Http::http_read_buffer (int fd,char *buffer,int length)
{ if(!buffer)
{ return 0;
}
const int bytes=read(fd,buffer,length);
if(bytes==length)
{ buffer[length-1]=0;
}
else
{ buffer[bytes]=0;
}
return bytes;
}
/* beware that filename+type+rest of header must not exceed MAXBUF */
/* so we limit filename to 256 and type to 64 chars in put & get */
#define MAXBUF 512
/*
* Pseudo general http query
*
* send a command and additional headers to the http server.
* optionally through the proxy (if http_proxy_server and http_proxy_port are
* set).
*
* Limitations: the url is truncated to first 256 chars and
* the server name to 128 in case of proxy request.
*/
bool Http::http_query(const char *command, const char *url,const char *additional_header, querymode mode, char* data, int length, int *pfd)
{ struct sockaddr_in server;
char header[MAXBUF];
int hlg;
if (pfd)
{ *pfd=-1;
}
#pragma warning(disable : 4996)
hostent* hp = gethostbyname( http_proxy_server.size() ? http_proxy_server.c_str() : http_server.c_str() );
if(!hp)
{ ret = ERRHOST;
return IsGood();
}
memset((char *) &server,0, sizeof(server));
memmove((char *) &server.sin_addr, hp->h_addr, hp->h_length);
server.sin_family = hp->h_addrtype;
server.sin_port = htons( http_proxy_server.size() ? u_short(http_proxy_port):u_short(http_port) );
int s = (int) socket(AF_INET, SOCK_STREAM, 0);
if(s < 0)
{ ret = ERRSOCK;
return IsGood();
}
setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, 0, 0);
if (connect(s, (const sockaddr*) &server, sizeof(server)) < 0)
{ ret=ERRCONN;
return IsGood();
}
if(pfd)
{ *pfd=s;
}
if (http_proxy_server.size())
{ sprintf(header, "%s http://%.128s:%d/%.256s HTTP/1.1\r\n"
"Host: %s:%i\r\n"
"Connection: close\r\n"
"User-Agent: %s\r\n"
"%s\r\n\r\n",
command,http_server.c_str(), http_port, url,
http_server.c_str(),http_port,
http_user_agent,
additional_header);
}
else
{ sprintf(header, "%s %.256s HTTP/1.1\r\n"
"Host: %s:%i\r\n"
"Connection: close\r\n"
"User-Agent: %s\r\n"
"%s\r\n\r\n",
command,url,
http_server.c_str(), http_port,
http_user_agent,
additional_header);
}
hlg=(int)strlen(header);
if(write(s,header,hlg)!=hlg)
{ ret=ERRWRHD;
close(s);
return IsGood();
}
if(length && data && (write(s,data,length)!=length) )
{ ret=ERRWRDT;
close(s);
return IsGood();
}
const int bytes = http_read_line(s,header,MAXBUF-1);
#ifdef VERBOSE
fputs(header,stderr);
putc('\n',stderr);
#endif
if(bytes<=0)
{ ret=ERRRDHD;
close(s);
return IsGood();
}
if(sscanf(header,"HTTP/1.%*d %03d",(int*)&ret)!=1)
{ ret=ERRPAHD;
close(s);
return IsGood();
}
if(mode==KEEP_OPEN)
{ return IsGood();
}
close(s);
return IsGood();
}
/*
* Put data on the server
*
* This function sends data to the http data server.
* The data will be stored under the ressource name filename.
* returns a negative error code or a positive code from the server
*
* limitations: filename is truncated to first 256 characters
* and type to 64.
*/
bool Http::http_put(char *data,int length,int overwrite,char *type)
{
char header[MAXBUF];
if (type)
sprintf(header,"Content-length: %d\015\012Content-type: %.64s\015\012%s",
length,
type ,
overwrite ? "Control: overwrite=1\015\012" : ""
);
else
sprintf(header,"Content-length: %d\015\012%s",length,
overwrite ? "Control: overwrite=1\015\012" : ""
);
return http_query("PUT",filename.c_str(),header,CLOSE, data, length, NULL);
}
#pragma warning(default : 4996)
/*
* Get data from the server
*
* This function gets data from the http data server.
* The data is read from the ressource named filename.
* Address of new new allocated memory block is filled in pdata
* whose length is returned via plength.
*
* returns a negative error code or a positive code from the server
*
*
* limitations: filename is truncated to first 256 characters
*/
#pragma warning(disable : 4996)
bool Http::http_get(char *pdata,int *plength,char *typebuf)
{ char header[MAXBUF];
char *pc;
int fd;
int n,length=-1;
if (!pdata)
{ ret = ERRNULL;
return IsGood();
}
*pdata=0;
if (typebuf)
{ *typebuf='\0';
}
http_query("GET",filename.c_str(),"",KEEP_OPEN, NULL, 0, &fd);
if(ret!=200)
{ if (ret>=0)
{ close(fd);
}
return IsGood();
}
for(;;)
{ n=http_read_line(fd,header,MAXBUF-1);
#ifdef VERBOSE
fputs(header,stderr);
putc('\n',stderr);
#endif
if (n<=0)
{ close(fd);
ret = ERRRDHD;
return IsGood();
}
/* empty line ? (=> end of header) */
if ( n>0 && (*header)=='\0')
{ break;
}
length+=n;
/* try to parse some keywords : */
/* convert to lower case 'till a : is found or end of String */
for (pc=header; (*pc!=':' && *pc) ; pc++)
{ *pc=char(tolower(*pc));
}
// sscanf(header,"content-length: %d",&length);
if (typebuf)
{ sscanf(header,"content-type: %s",typebuf);
} }
if (length<=0)
{ close(fd);
ret = ERRNOLG;
return IsGood();
}
if(*plength<length)
{ length=*plength;
}
n=http_read_buffer(fd,pdata,length);
close(fd);
if (n<0)
{ ret=ERRRDDT;
}
return IsGood();
}
/*
* Request the header
*
* This function outputs the header of thehttp data server.
* The header is from the ressource named filename.
* The length and type of data is eventually returned (like for http_get(3))
*
* returns a negative error code or a positive code from the server
*
* limitations: filename is truncated to first 256 characters
*/
bool Http::http_head(int *plength,char *typebuf)
{
char header[MAXBUF];
char *pc;
int fd;
int n,length=-1;
if (plength) *plength=0;
if (typebuf) *typebuf='\0';
http_query("HEAD",filename.c_str(),"",KEEP_OPEN, NULL, 0, &fd);
if (ret==200) {
for(;;) {
n=http_read_line(fd,header,MAXBUF-1);
#ifdef VERBOSE
fputs(header,stderr);
putc('\n',stderr);
#endif
if (n<=0) {
close(fd);
ret = ERRRDHD;
return IsGood();
}
/* empty line ? (=> end of header) */
if ( n>0 && (*header)=='\0') break;
/* try to parse some keywords : */
/* convert to lower case 'till a : is found or end of String */
for (pc=header; (*pc!=':' && *pc) ; pc++) *pc=char(tolower(*pc));
sscanf(header,"content-length: %d",&length);
if (typebuf) sscanf(header,"content-type: %s",typebuf);
}
if (plength) *plength=length;
close(fd);
} else if (ret>=0) close(fd);
return IsGood();
}
/*
* Delete data on the server
*
* This function request a DELETE on the http data server.
*
* returns a negative error code or a positive code from the server
*
* limitations: filename is truncated to first 256 characters
*/
bool Http::http_delete()
{ return http_query("DELETE",filename.c_str(),"",CLOSE, NULL, 0, NULL);
}
/* parses an url : setting the http_server and http_port global variables
* and returning the filename to pass to http_get/put/...
* returns a negative error code or 0 if sucessfully parsed.
*/
bool Http::http_parse_url(const char *url)
{ if (strncasecmp("http://",url,7))
{
#ifdef VERBOSE
fprintf(stderr,"invalid url (must start with 'http://')\n");
#endif
ret = ERRURLH;
return IsGood();
}
// http://server:port/
url+=7;
http_server.erase();
filename.erase();
http_port=80;
const char* domain=url;
char* endOf=const_cast<char*>(strchr(domain,':'));
if(endOf)
{ http_server=String(domain,endOf-domain);
endOf++;
if (sscanf(endOf,"%d",&http_port)!=1)
{
#ifdef VERBOSE
fprintf(stderr,"invalid port in url\n");
#endif
ret = ERRURLP;
return IsGood();
} }
endOf= const_cast<char*>(strchr(domain,'/'));
if(!http_server.size())
{ if(endOf)
{ http_server=String(domain,endOf-domain);
}
else
{ http_server=String(domain);
ret = OK0;
return IsGood();
} }
filename=String(endOf);
#ifdef VERBOSE
fprintf(stderr,"host=%s, port=%d, filename=%s\n",
http_server.c_str(),http_port,filename.c_str());
#endif
ret = OK0;
return IsGood();
}
int Http::read(int s,char*buf,int len)
{
#ifdef WIN32
int size=recv(s,buf,len,0);
#if 0
if(size==SOCKET_ERROR)
{ const int err=WSAGetLastError();
}
#endif
if(size==16384)
{ size+=recv(s,buf+16384,len,0);
}
// cout<<"Read bytes: "<<size<<endl;
#else
const int size=::read(s,buf,len);
#endif
return size;
}
int Http::write(int s,const char*buf,int len)
{
#ifdef WIN32
return send(s,buf,len,0);
#else
return ::write(s,buf,len);
#endif
}
void Http::close(int s)
{
#ifdef WIN32
closesocket(s);
#else
::close(s);
#endif
}
#ifdef WIN32
int Http::GetSystemError()
{ return WSAGetLastError();
}
#else
int Http::GetSystemError()
{ return 0;
}
#endif
}