/******************************************************************************* * File: MySock.cpp * * Version: 0.4 * * Date: 20260809 * * Author: Gaspar Fernández (helyo@totaki.com) * * * * Copyright (C) 2008 Gaspar Fernández * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ******************************************************************************** * Description: * Class and functions to create socket connections and fetch information * from http servers. It is part of another project I'm in. (Some comments are * in Spanish. * * Change History: * Date Author Modification * 10.02.2008 Gaspar Fernández Initial release * 31.10.2010 Gaspar Fernández Bug Corrections * 09.08.2026 Koudai Kudo fetch(1) for FreeBSD ********************************************************************************/ #include "MySock.h" #include #include #include /************************************************************* * Function: extract_key_value * ************************************************************* * Description: * * Extract information separated with a white space. * * We consider KEY before the whitespace and VALUE * * after that. * * * * Input: * * string str - String containing the relation * * * * Output: * * TKey_Value struct containing separated key and value. * * * * Change History: * * Date Author Modification * * * *************************************************************/ TKey_Value extract_key_value(string str) { TKey_Value data; char buf[512]; // Max characters try // Sólo si es posible { sscanf(str.data(), "%s", buf); // Extract header data.value=str.substr(strlen(buf)); data.key=buf; } catch(exception& e) { // Do nothing } return data; } /************************************************************* * Constructor MySock() * ************************************************************* * Description: * * Creates MySock class and initialize it. The second * * constructor uses an HTTP URI to read information stored * * there. * * * * Input: * * string uri (in the second one) * * * * Change History: * * Date Author Modification * * * *************************************************************/ MySock::MySock() { this->error=NO_ERROR; this->connected=false; this->use_external_fetch=false; } MySock::MySock(string uri) { int port; string host; string params; string protostr; string req; this->error=NO_ERROR; this->use_external_fetch=false; string::size_type pos = uri.find("//", 0); string::size_type pos2; if ((pos!=string::npos) && (pos==uri.find("/", 0)) && (pos>0)) // Sin barras antes { protostr = uri.substr(0,(pos-1)); if (protostr=="http") { port=80; pos2=uri.find("/", (pos+2)); if (pos2==string::npos) pos2=uri.length(); host=uri.substr(pos+2, pos2-pos-2); params=uri.substr(pos2); this->MakeConnection(host, port); req="GET "+params+" HTTP/1.0"+CRLF+ "Host: "+host+"\r\n"+ CRLF; if (this->connected) this->SendData(req); } else if (protostr=="https") { if(this->safe_url(uri)) { this->https_url=url; this->use_external_fetch=true; this->connection; } } else { this->error=NO_VALID_PROTOCOL; } } else this->error=NO_VALID_PROTOCOL; } /************************************************************* * Constructor MySock() * ************************************************************* * Description: * * Creates MySock class and initialize it. Connect with * * a remote server using specified port. * * * * Input: * * string server - Server to connect to * * int port - Port in the remote server * * * * Change History: * * Date Author Modification * * * *************************************************************/ MySock::MySock(string server, int port) { this->MakeConnection(server, port); this->use_external_fetch=false; } /************************************************************* * Method: is_safe_url ************************************************************** * Description: Since https:// URIs end up inside a popen() * * command, nad only allow a conservation character set. * * * Input: * string url *************************************************************/ bool MySock::is_safe_url(const string &url) { for (string::size_type i=0; i0) && (url.length()<2048); } /************************************************************* * Method: GetHTTPDataViaExternalFetch() * ************************************************************* * Description: * * Retrieves an https:// URL by shelling out to fetch(1) * * (FreeBSD base system) via popen(), since this class has * * no TLS implementation of its own. fetch -o - writes the * * response body to stdout, so unlike GetHTTPData()'s raw * * socket path there are no HTTP headers to parse here - we * * only know success/failure from fetch(1)'s exit status. * * * * Input: * * Nothing (uses this->https_uri) * * * * Output: * * HTTP_Request* - status is 200 on success, 502 if * * fetch(1) reported an error (bad URL, network failure, * * HTTP error status, etc); data holds the response body. * * * * Change History: * * Date Author Modification * * * *************************************************************/ HTTP_Request *MySock::GetHTTPDataViaExternalFetch() { HTTP_Request *http; string cmd; FILE *pipe; char buffer[4096]; size_t n; cmd=(string)HTTPS_FETCH_CMD+"'"+this->https_uri+"'"+" 2>/dev/null"; pipe=popen(cmd.data(), "r"); if (pipe==NULL) { this->error=CANT_RUN_FETCH; return NULL; } http=new HTTP_Request; http->data=""; while ((n=fread(buffer, 1, sizeof(buffer), pipe))>0) http->data.append(buffer, n); int rc=pclose(pipe); if ((rc==0) && (!http->data.empty())) { http->status=200; http->statusstr="HTTP/1.1 200 OK (via fetch)"; } else { http->status=502; http->statusstr="HTTP/1.1 502 Bad Gateway (fetch failed)"; } this->connected=false; // "Connection" (the popen pipe) is closed now return http; } /************************************************************* * Method: MakeConnection() * ************************************************************* * Description: * * Makes a connection to an specified server using a * * remote port * * * * Input: * * string server * * int port * * * * Output: * * Nothing * * * * Change History: * * Date Author Modification * * * *************************************************************/ void MySock::MakeConnection(string server, int port) { struct sockaddr_in serverAddress; struct hostent *hostInfo; this->connected=false; this->error=NO_ERROR; hostInfo = gethostbyname(server.data()); this->error=(hostInfo==NULL)?CANT_RESOLVE_HOST:NO_ERROR; if (this->error==0) { this->sockd = socket(AF_INET, SOCK_STREAM, 0); this->error = (this->sockd<0)?CANT_CREATE_SOCKET:NO_ERROR; } // Connect to server. First we have to set some fields in the // serverAddress structure. The system will assign me an arbitrary // local port that is not in use. if (this->error==0) { serverAddress.sin_family = hostInfo->h_addrtype; memcpy((char *) &serverAddress.sin_addr.s_addr, hostInfo->h_addr_list[0], hostInfo->h_length); serverAddress.sin_port = htons(port); this->error= (connect(this->sockd, (struct sockaddr *) &serverAddress, sizeof(serverAddress)) < 0)?CANT_CONNECT:NO_ERROR; } this->connected=(error==NO_ERROR); } /************************************************************* * Method: SendData() * ************************************************************* * Description: * * Sends information through the already stablished * * connection. * * * * Input: * * string data - Data being sent * * * * Output: * * Nothing * * * * Change History: * * Date Author Modification * * * *************************************************************/ int MySock::SendData(string data) { if (this->error==0) this->error = (write(this->sockd,data.data(),strlen(data.data()))<0)?CANT_SEND_DATA:NO_ERROR; return this->error; } /************************************************************* * Method: GetTextData() * ************************************************************* * Description: * * Read received data * * * * Input: * * Nothing * * * * Output: * * string - returns data received. * * * * Change History: * * Date Author Modification * * * *************************************************************/ string MySock::GetTextData() { int n; char buffer[1024]; string out(""); if (this->error==0) { bzero(buffer,1024); while ((n=read(this->sockd,buffer,1023)>0)) { if (n>=0) out+=buffer; else { error=CANT_READ_DATA; break; } bzero(buffer,1024); } return out; } return NULL; } /************************************************************* * Method: GetHTTPData() * ************************************************************* * Description: * * We connect to an HTTP server and We Want to Read and * * identify headers and contents. We use this function * * * * Input: * * Nothing * * * * Output: * * HTTP_Request structure - Returns headers and data * * * * Change History: * * Date Author Modification * * * *************************************************************/ HTTP_Request *MySock::GetHTTPData() { string txtData; int i=0; // Cuenta líneas string::size_type pos, pos2; HTTP_Request *http; string header; string value; bool err, data; char buf[255], buf2[255]; TKey_Value hdata; if (this->use_external_fetch) return this->GetHTTPDataViaExternalFetch(); if (this->error==NO_ERROR) { txtData=this->GetTextData(); // Close the socket, we will analyze data this->closeConnection(); data=false; err=false; pos=0; http=new HTTP_Request; while ((!data) && (!err)) { pos2=txtData.find("\r\n",pos); if (pos2==string::npos) err=true; else if (pos2==pos) data=true; else { header=txtData.substr(pos,pos2-pos); // cout<statusstr=header; sscanf(header.data(),"%s %s", buf, buf2); // HTTP/1.0 200 http->status=atoi(buf2); } else { hdata=extract_key_value(header); if (hdata.key=="Date:") http->date=hdata.value; else if (hdata.key=="Server: ") http->server=hdata.value; else if (hdata.key=="Last-Modified") http->last_modified=hdata.value; else if (hdata.key=="Content-Type") http->content_type=hdata.value; else if (hdata.key=="Content-Length") http->content_length=hdata.value; } pos=pos2+2; i++; } } if (err) { this->error=NO_VALID_HEADERS; return NULL; } else http->data=txtData.substr(pos2+2); // Skip \r\n return http; } return NULL; } void MySock::closeConnection() { if (connected && !use_external_fetch) { close(sockd); } connected=false; } MySock::~MySock() { closeConnection(); } // It is a test function to use MySock class. It was written for DWGO // int main(int argc, char *argv[]) // { // string servidor; // MySock sock; // int portno; // HTTP_Request *http; // if (argc < 3) { // fprintf(stderr,"usage %s hostname port\n", argv[0]); // exit(0); // } // portno = atoi(argv[2]); // servidor=argv[1]; // // sock = MySock(servidor, portno); // // sock.SendData("GET /pub/data/observations/metar/decoded/LEMG.TXT HTTP/1.0\n\n"); // // cout<status<<"Fecha: "<date<data; // return 0; // }