You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

333 lines
9.6 KiB
C++

/***************************************************************************
* 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 <boost/iostreams/concepts.hpp>
namespace Seiscomp {
namespace IO {
namespace {
template <typename SocketType>
class SC_SYSTEM_CORE_API HttpSource : public boost::iostreams::source {
public:
HttpSource(HttpSocket<SocketType> *sock);
std::streamsize read(char* buf, std::streamsize size);
private:
HttpSocket<SocketType> *_sock;
};
template <typename SocketType>
HttpSource<SocketType>::HttpSource(HttpSocket<SocketType> *sock): _sock(sock)
{
}
template <typename SocketType>
std::streamsize HttpSource<SocketType>::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 <typename SocketType>
HttpSocket<SocketType>::HttpSocket(): _chunkMode(false), _remainingBytes(0),
_decomp(nullptr)
{
}
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
template <typename SocketType>
HttpSocket<SocketType>::~HttpSocket()
{
if ( _decomp )
delete _decomp;
}
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
template <typename SocketType>
void HttpSocket<SocketType>::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 <typename SocketType>
void HttpSocket<SocketType>::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 <typename SocketType>
void HttpSocket<SocketType>::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 <typename SocketType>
void HttpSocket<SocketType>::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 <typename SocketType>
void HttpSocket<SocketType>::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 <typename SocketType>
std::string HttpSocket<SocketType>::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 <typename SocketType>
std::string HttpSocket<SocketType>::httpReadSome(int size)
{
if ( _decomp != nullptr ) {
HttpSource<SocketType> src(this);
std::vector<char> tmp(size);
std::streamsize bytesRead = _decomp->read(src, &tmp[0], size);
return std::string(&tmp[0], bytesRead);
}
else {
return httpReadRaw(size);
}
}
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
template <typename SocketType>
std::string HttpSocket<SocketType>::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