/*************************************************************************** * Copyright (C) gempa GmbH * * All rights reserved. * * Contact: gempa GmbH (seiscomp-dev@gempa.de) * * * * GNU Affero General Public License Usage * * This file may be used under the terms of the GNU Affero * * Public License version 3.0 as published by the Free Software Foundation * * and appearing in the file LICENSE included in the packaging of this * * file. Please review the following information to ensure the GNU Affero * * Public License version 3.0 requirements will be met: * * https://www.gnu.org/licenses/agpl-3.0.html. * * * * Other Usage * * Alternatively, this file may be used in accordance with the terms and * * conditions contained in a signed written agreement between you and * * gempa GmbH. * ***************************************************************************/ #ifndef SEISCOMP_IO_HTTPSOCKET_IPP__ #define SEISCOMP_IO_HTTPSOCKET_IPP__ #include namespace Seiscomp { namespace IO { namespace { template class SC_SYSTEM_CORE_API HttpSource : public boost::iostreams::source { public: HttpSource(HttpSocket *sock); std::streamsize read(char* buf, std::streamsize size); private: HttpSocket *_sock; }; template HttpSource::HttpSource(HttpSocket *sock): _sock(sock) { } template std::streamsize HttpSource::read(char* buf, std::streamsize size) { std::string data = _sock->httpReadRaw(size); if ( (int)data.size() > size ) { SEISCOMP_ERROR("impossible thing happened"); memcpy(buf, data.data(), size); return size; } memcpy(buf, data.data(), data.size()); return data.size(); } } // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> template HttpSocket::HttpSocket(): _chunkMode(false), _remainingBytes(0), _decomp(nullptr) { } // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> template HttpSocket::~HttpSocket() { if ( _decomp ) delete _decomp; } // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> template void HttpSocket::open(const std::string& serverHost, const std::string& user, const std::string& password) { _serverHost = serverHost; _user = user; _password = password; _chunkMode = false; _remainingBytes = 0; SocketType::open(serverHost); } // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> template void HttpSocket::httpReadResponse() { if ( _decomp != nullptr ) { delete _decomp; _decomp = nullptr; } std::string line = this->readline(); if ( line.compare(0, 7, "HTTP/1.") != 0 ) throw Core::GeneralException(("server sent invalid response: " + line).c_str()); size_t pos; pos = line.find(' '); if ( pos == std::string::npos ) throw Core::GeneralException(("server sent invalid response: " + line).c_str()); line.erase(0, pos+1); pos = line.find(' '); if ( pos == std::string::npos ) throw Core::GeneralException(("server sent invalid response: " + line).c_str()); int code; if ( !Core::fromString(code, line.substr(0, pos)) ) throw Core::GeneralException(("server sent invalid status code: " + line.substr(0, pos)).c_str()); if ( code != 200 && code != 204 ) _error = "server request error: " + line; _remainingBytes = -1; int lc = 0; while ( !this->isInterrupted() ) { ++lc; line = this->readline(); if ( line.empty() ) break; SEISCOMP_DEBUG("[%02d] %s", lc, line.c_str()); if ( line == "Transfer-Encoding: chunked" ) { _chunkMode = true; SEISCOMP_DEBUG(" -> enabled 'chunked' transfer"); } else if ( line == "Content-Encoding: gzip" ) { _decomp = new boost::iostreams::zlib_decompressor(boost::iostreams::zlib::default_window_bits | 16); SEISCOMP_DEBUG(" -> enabled 'gzip' compression"); } else if ( line == "Content-Encoding: deflate" ) { _decomp = new boost::iostreams::zlib_decompressor(boost::iostreams::zlib::default_window_bits); SEISCOMP_DEBUG(" -> enabled 'deflate' compression"); } else if ( line.compare(0, 15, "Content-Length:") == 0 ) { if ( !Core::fromString(_remainingBytes, line.substr(15)) ) throw Core::GeneralException("invalid Content-Length response"); if ( _remainingBytes < 0 ) throw Core::GeneralException("Content-Length must be positive"); } } if ( _chunkMode ) { if ( _remainingBytes >= 0 ) throw Core::GeneralException("protocol error: transfer encoding is chunked and content length given"); _remainingBytes = 0; } } // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> template void HttpSocket::sendAuthorization() { std::string auth = _user + ':' + _password; BIO* b64 = BIO_new(BIO_f_base64()); BIO* bio = BIO_new(BIO_s_mem()); BIO_push(b64, bio); BIO_write(b64, auth.c_str(), auth.length()); BIO_flush(b64); BUF_MEM *mem; BIO_get_mem_ptr(b64, &mem); this->sendRequest("Authorization: Basic " + std::string(mem->data, mem->length - 1), false); BIO_free_all(b64); } // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> template void HttpSocket::httpGet(const std::string &path) { this->sendRequest(std::string("GET ") + path + " HTTP/1.1", false); this->sendRequest(std::string("Host: ") + _serverHost, false); this->sendRequest("User-Agent: Mosaic/1.0", false); this->sendRequest("Accept-Encoding: gzip, deflate", false); if ( _user.length() > 0 ) sendAuthorization(); this->sendRequest("", false); httpReadResponse(); } // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> template void HttpSocket::httpPost(const std::string &path, const std::string &msg) { this->sendRequest(std::string("POST ") + path + " HTTP/1.1", false); this->sendRequest(std::string("Host: ") + _serverHost, false); this->sendRequest("User-Agent: Mosaic/1.0", false); this->sendRequest("Accept-Encoding: gzip, deflate", false); this->sendRequest("Content-Type: application/bson", false); this->sendRequest(std::string("Content-Length: ") + Core::toString(msg.size()), false); if ( _user.length() > 0 ) sendAuthorization(); this->sendRequest("", false); this->write(msg); httpReadResponse(); } // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> template std::string HttpSocket::httpReadRaw(int size) { if ( _chunkMode && _remainingBytes <= 0 ) { std::string r = this->readline(); size_t pos = r.find(' '); unsigned int remainingBytes; if ( sscanf(r.substr(0, pos).c_str(), "%X", &remainingBytes) != 1 ) throw Core::GeneralException((std::string("invalid chunk header: ") + r).c_str()); _remainingBytes = remainingBytes; if ( _remainingBytes <= 0 ) { this->close(); if ( _error.size() ) throw Core::GeneralException(_error.c_str()); } } if ( _remainingBytes <= 0 ) return ""; int toBeRead = _remainingBytes; if ( toBeRead > size ) toBeRead = size; // seiscomp/io/socket.h defines BUFSIZE as the max read size std::string data = this->read(std::min(toBeRead, BUFSIZE)); _remainingBytes -= data.size(); if ( _chunkMode && _remainingBytes <= 0 ) // Read trailing new line this->readline(); if ( _error.size() ) { this->close(); throw Core::GeneralException(_error.c_str()); } if ( !_chunkMode && _remainingBytes <= 0 ) this->close(); return data; } // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> template std::string HttpSocket::httpReadSome(int size) { if ( _decomp != nullptr ) { HttpSource src(this); std::vector tmp(size); std::streamsize bytesRead = _decomp->read(src, &tmp[0], size); return std::string(&tmp[0], bytesRead); } else { return httpReadRaw(size); } } // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> template std::string HttpSocket::httpRead(int size) { std::string data; while ( (int)data.size() < size ) { std::string::size_type bytesRead = data.size(); data += httpReadSome(size - bytesRead); if ( data.size() == bytesRead ) break; } return data; } // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> } } #endif