From a08592e3f852b4eded1bf3559d5e46a118dd521a Mon Sep 17 00:00:00 2001 From: Steffen Pohle Date: Thu, 3 Feb 2022 21:24:48 +0100 Subject: [PATCH] TCP::GetWebFile is working fine now. Windows WinSock initialisation is working better. --- Changelog | 4 + UDPTCPNetwork.h | 9 +- tcp.cc | 329 +++++++++++++++++++++++++++++++++++++----------- 3 files changed, 265 insertions(+), 77 deletions(-) diff --git a/Changelog b/Changelog index 0c69f7c..0ab4f32 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,7 @@ +2022-02-02: +- TCP::WebGetFile downloaading from web is working fine now. +- TCP constructor is dealing better on windows now. WinSock is Hell. + 2020-10-18: - needed to disable ipv6 on windows for the moment, as long as i can't get both ipv4 and ipv6 to work at the same time. diff --git a/UDPTCPNetwork.h b/UDPTCPNetwork.h index 5268af9..1198bb2 100644 --- a/UDPTCPNetwork.h +++ b/UDPTCPNetwork.h @@ -106,12 +106,13 @@ class TCP { private: SOCKET sock; struct sockaddr_storage peeraddr; - string remote_host; string remote_port; int readcnt; int writecnt; int islisten; + + int WebHeaderGetParamValue(std::string line, std::string *parm, std::string *value); public: TCP(); TCP(SOCKET s); @@ -140,10 +141,8 @@ public: const string GetRemoteAddr(); const string GetLocalAddr(); - const string WebGetURLHost (string url); - const string WebGetURLPort (string url); - const string WebGetURLFile (string url); - int WebGetFile (string url, char *buffer, int maxsize); + int WebGetURLElements (string url, string *host, string *port, string *file); + int WebGetFile (string url, char *buffer, int maxsize, char *formdata); }; diff --git a/tcp.cc b/tcp.cc index 2ab0bb9..1989f77 100644 --- a/tcp.cc +++ b/tcp.cc @@ -1,21 +1,48 @@ -/* - * - */ +///////////////////////////////////////////////////////////////////////////////// +// +// tcp.cc is part of TestModbus-Server. +// +///////////////////////////////////////////////////////////////////////////////// +#include "UDPTCPNetwork.h" #include -#include /* close() */ +#if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__) +#else + #include /* close() */ +#endif #include #include /* memset() */ #include -#include "UDPTCPNetwork.h" +using namespace std; + +int __tcpinit__ = 0; TCP::~TCP() { Close(); } TCP::TCP() { - UDPTCPNetwork(); + if (__tcpinit__ == 0) { +#ifdef BUILD_WINDOWS + + WORD wVersionRequested; + WSADATA wsaData; + int err; + + /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */ + wVersionRequested = MAKEWORD(2, 2); + + err = WSAStartup(wVersionRequested, &wsaData); + if (err != 0) { + /* Tell the user that we could not find a usable */ + /* Winsock DLL. */ + printf("WSAStartup failed with error: %d\n", err); + return; + } +#endif + __tcpinit__ = 1; + } sock = 0; writecnt = 0; readcnt = 0; @@ -98,12 +125,10 @@ int TCP::Listen(int port) { TCP* TCP::Accept() { fd_set rfds; struct timeval tv; - int retval, err, i; + int retval; SOCKET newsock; struct sockaddr_storage cliaddr; socklen_t cliaddr_len = sizeof(struct sockaddr_storage); - char host[NET_BUFFERSIZE]; - char port[NET_BUFFERSIZE]; TCP *tcp = NULL; if (sock <= 0) return NULL; @@ -137,8 +162,6 @@ TCP* TCP::Accept() { int TCP::Connect(string h, string p) { - int i = 0; - remote_host = h; remote_port = p; @@ -163,7 +186,7 @@ int TCP::Connect(string hostport, int defaultport) { int TCP::Connect() { - int err, s; + int err, s = 0; struct addrinfo hints, *res, *rp; struct timeval timeout; @@ -175,7 +198,7 @@ int TCP::Connect() { err = getaddrinfo(remote_host.c_str(), remote_port.c_str(), &hints, &res); if (err != 0) { - fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); + fprintf(stderr, "getaddrinfo: Host:'%s' Port:'%s' Error:%s\n", remote_host.c_str(), remote_port.c_str(), gai_strerror(err)); return 0; } @@ -340,53 +363,174 @@ int TCP::IsData(int timeout) { }; -int TCP::WebGetFile (string url, char *buffer, int maxsize) { +/* + * Read a single line and split in parameter and value + * if value and parm is set to NULL this field will not be set + */ +int TCP::WebHeaderGetParamValue(std::string line, std::string *parm, std::string *value) { + size_t pos = line.find(" "); + if (pos == std::string::npos) { + fprintf (stderr, "%s error with protocol. HTTP/ without space? Line:'%s'\n", + __FUNCTION__, line.c_str()); + return -1; + } + + if (parm != NULL) *parm = line.substr (0, pos); + if (value != NULL) *value = line.substr (pos+1, std::string::npos); + + return 0; +} + + +/* + * read any data from server + * Return Value: + * on error: -1 + * success : size of buffer + */ +int TCP::WebGetFile (string url, char *buffer, int maxsize, char *formdata) { char outdata[NET_BUFFERSIZE]; char indata[NET_BUFFERSIZE]; - char host[NET_HOSTLEN]; - char port[NET_PORTLEN]; - int breakup, len, head, i, buffpos = 0; - + string host, port, file; + int len, line_cnt, i, buffpos = 0, c; + string protocol = ""; + string type = ""; + string line = ""; + string lineUP = ""; + string status = ""; + int chunked_transfer = 0; + int chunked_bytes = 0; + + // + // clear input buffer + if (buffer == NULL) return -1; + buffer[0] = '\0'; + + WebGetURLElements(url, &host, &port, &file); if (!IsConnected()) { - strncpy (host, WebGetURLHost (url).c_str(), NET_HOSTLEN); - strncpy (port, WebGetURLPort (url).c_str(), NET_PORTLEN); Connect (host, port); } if (!IsConnected()) return -1; + // // send http request - snprintf (outdata, NET_BUFFERSIZE, "GET %s HTTP/1.0\nUser-Agent: tcpclient \nHost: %s\n\n", WebGetURLFile (url).c_str(), host); + // + if (formdata == NULL) + snprintf (outdata, NET_BUFFERSIZE, "GET %s HTTP/1.0\r\nUser-Agent: unknown\r\nHost: %s\r\nAccept:*.*\r\n\r\n", file.c_str(), host.c_str()); + else + snprintf (outdata, NET_BUFFERSIZE, "POST %s HTTP/1.1\r\nUser-Agent: unknown\r\nHost: %s\r\nContent-Length: %ld\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept:*.*\r\n\r\n%s\r\n", + file.c_str(), host.c_str(), strlen (formdata), formdata); Write (outdata, strlen (outdata)); - // wait for data start - i = breakup = 0; - head = 1; - while (!breakup && head && (len = ReadTimeout (indata, NET_BUFFERSIZE-1, 2000)) >= 0) { - indata[len] = 0; - while (i < len -4 && head && !breakup) { - if (indata[i] == 0x0d && indata [i+1] == 0x0a && indata [i+2] == 0x0d && indata [i+3] == 0x0a) { - head = 0; - i = i + 4; - buffpos = len-i; - if (buffpos > maxsize) buffpos = maxsize; - memcpy (buffer, indata+i, buffpos); + // + // read header + // + indata[0] = '\0'; + len = ReadTimeout (indata, NET_BUFFERSIZE-1, 2000); + if (len <= 0) { + fprintf (stderr, "%s:%d reading header Error:%s\n", __FILE__, __LINE__, strerror(errno)); + return -1; + } + indata[len] = 0; + + // + // read all lines until an empty line is reached + for (line = "", lineUP = "", line_cnt = 0, i = 0; i < len; i++) { + if (indata[i] == '\n' || indata[i] == '\r') { + if (i < len && (indata[i+1] == '\n' || indata[i+1] == '\r')) i++; + + if (line.length() == 0) { + i++; + break; // break loop, read data + } + + // + // HTTP Header + else if (lineUP.find ("HTTP/") == 0) { + if (WebHeaderGetParamValue(line, &protocol, &status) < 0) { + fprintf (stderr, "%s:%d reading header Error:%s\n", __FILE__, __LINE__, strerror(errno)); + return -1; + } } - else if (strncmp (indata+i, "Content-Length:", 15) == 0) { - i = i + 16; + // + // Transfer Encoding + else if (lineUP.find ("TRANSFER-ENCODING:") == 0) { + std::string temp; + if (WebHeaderGetParamValue(line, NULL, &temp) < 0) { + fprintf (stderr, "%s:%d reading header Error:%s\n", __FILE__, __LINE__, strerror(errno)); + return -1; + } + for (int j = 0; (long int)j < (long int) temp.length(); j++) + temp[i] = toupper(temp[i]); + if (temp.find("chunked") != std::string::npos) chunked_transfer = 1; } - else if (strncmp (indata+i, "403 ", 4) == 0) breakup = 1; - else i++; + // + // type of file + else if (lineUP.find ("CONTENT-TYPE:") == 0) { + if (WebHeaderGetParamValue(line, NULL, &type) < 0) { + fprintf (stderr, "%s:%d reading header Error:%s\n", __FILE__, __LINE__, strerror(errno)); + return -1; + } + } + lineUP = line = ""; + line_cnt++; + } + + else { + line += indata[i]; + lineUP += toupper(indata[i]); } } - // read data - while (!breakup && (len = ReadTimeout (indata, NET_BUFFERSIZE-1, 2000)) >= 0) { + // + // chunked data? + // size we need to load + if (chunked_transfer) { + int v; + for (v = 0, line_cnt = 0; i < len; i++) { + if (indata[i] == '\r' || indata[i] == '\n') { + i++; + if (i < len && (indata[i] == '\n' || indata[i] == '\r')) i++; + chunked_bytes = v; + break; + } + else { + v = v << 4; + indata[i] = tolower(indata[i]); + if (indata[i] >= '0' && indata[i] <= '9') v += (indata[i] - '0'); + else if (indata[i] >= 'a' && indata[i] <= 'f') v += (10 + indata[i] - 'a'); + else { + fprintf (stderr, "%s:%d reading chunk bytes, invalid HEX code indata:'%s'\n", __FILE__, __LINE__, indata); + errno = EINVAL; + return -1; + } + } + } + + // + // check bytes to read, and remove these from chunked_bytes + if (chunked_bytes < len-i) c = chunked_bytes; + else c = len-i; + chunked_bytes -= c; + } + else c = len-i; + + // + // read data, but first copy the left over bytes into the output buffer + if (c > 0) memcpy (buffer, indata+i, c); + buffpos = c; + + while ((len = ReadTimeout (indata, NET_BUFFERSIZE-1, 2000)) > 0) { i = len; if (i > maxsize-buffpos) i = maxsize-buffpos; if (i > 0) { + if (chunked_transfer) { + if (i > chunked_bytes) i = chunked_bytes; + chunked_bytes -= i; + } memcpy (buffer+buffpos, indata, i); buffpos += i; } @@ -395,46 +539,87 @@ int TCP::WebGetFile (string url, char *buffer, int maxsize) { return buffpos; }; -const string TCP::WebGetURLHost (string url) { - string result; - int posSServPort = 7; // begin server:port - int posEServPort = 0; // end Server:Port - int posPort = -1; // port Position - - posEServPort = url.find("/", 7); - result = url.substr (posSServPort, posEServPort-posSServPort); - posPort = result.find(":"); - if (posPort > 0) - result = url.substr (posSServPort, posPort); - return result; +// FIXME: everything below here is wrong... +/* + * fills the elements and returns 0 on success. Returns set bits if value was found + * 1 ... hostname + * 2 ... port + * 3 ... file + */ +enum _webgeturl_step_ { + _WEBGET_URL_PROTO = 0, + _WEBGET_URL_SERVER, + _WEBGET_URL_PORT, + _WEBGET_URL_FILE }; +int TCP::WebGetURLElements (string url, string *host, string *port, string *file) { + string h = ""; + string p = ""; + string f = "/"; + const string prefix1 = "HTTP://"; + const string prefix2 = "HTTPS://"; + int retval = 0; + int i; + int curpos = 0; + int step = _WEBGET_URL_PROTO; -const string TCP::WebGetURLPort (string url) { - string result; - int posSServPort = 7; // begin server:port - int posEServPort = 0; // end Server:Port - int posPort = -1; // port Position - - posEServPort = url.find("/", 7); - result = url.substr (posSServPort, posEServPort-posSServPort); - posPort = result.find(":"); - if (posPort > 0) - result = result.substr (posPort+1); - else - result = "80"; + for (i = 0; i < 5; i++) url[i] = toupper(url[i]); - return result; -}; + // + // try to find the protocol and // + if (url.find(prefix1) == 0) { + retval |= 2; + p = "80"; + curpos = prefix1.length(); + } + else if (url.find(prefix2) == 0) { + retval |= 2; + p = "443"; + curpos = prefix2.length(); + // FIXME: SSL needs to be implemented + } + else { + if (url.find ("//") != std::string::npos) { + errno = EPROTONOSUPPORT; + return -1; + } + else curpos = 0; + } -const string TCP::WebGetURLFile (string url) { - string result; - int posEServPort = 0; // end Server:Port + for (step = _WEBGET_URL_SERVER;curpos < (int)url.length(); curpos++) { + if (step == _WEBGET_URL_SERVER) { + if (url[curpos] == '/') step = _WEBGET_URL_FILE; + else if (url[curpos] == ':') { + step = _WEBGET_URL_PORT; + p = ""; + } + else { + h += url[curpos]; + retval |= 1; + } + + } + + else if (step == _WEBGET_URL_PORT) { + if (url[curpos] == '/') step = _WEBGET_URL_FILE; + else { + p += url[curpos]; + retval |= 2; + } + } + + else if (step == _WEBGET_URL_FILE) { + f += url[curpos]; + retval |= 3; + } + } - posEServPort = url.find("/", 7); - result = url.substr (posEServPort); + if (host != NULL) *host = h; + if (file != NULL) *file = f; + if (port != NULL) *port = p; - return result; + return retval; };