//
// VMime library (http://www.vmime.org)
// Copyright (C) 2002 Vincent Richard <vincent@vmime.org>
//
// 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, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// Linking this library statically or dynamically with other modules is making
// a combined work based on this library. Thus, the terms and conditions of
// the GNU General Public License cover the whole combination.
//
#include "vmime/config.hpp"
#if VMIME_PLATFORM_IS_WINDOWS && VMIME_HAVE_MESSAGING_FEATURES
#pragma warning(disable: 4267)
#include "vmime/platforms/windows/windowsSocket.hpp"
#include "vmime/utility/stringUtils.hpp"
#include "vmime/exception.hpp"
#include <ws2tcpip.h>
namespace vmime {
namespace platforms {
namespace windows {
//
// windowsSocket
//
windowsSocket::windowsSocket(shared_ptr <vmime::net::timeoutHandler> th)
: m_timeoutHandler(th),
m_desc(INVALID_SOCKET),
m_status(0) {
WSAData wsaData;
WSAStartup(MAKEWORD(1, 1), &wsaData);
}
windowsSocket::~windowsSocket() {
if (m_desc != INVALID_SOCKET) {
::closesocket(m_desc);
}
WSACleanup();
}
void windowsSocket::connect(const vmime::string& address, const vmime::port_t port) {
// Close current connection, if any
if (m_desc != INVALID_SOCKET) {
::closesocket(m_desc);
m_desc = INVALID_SOCKET;
}
// Resolve address
::sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(static_cast <unsigned short>(port));
addr.sin_addr.s_addr = ::inet_addr(address.c_str());
if (m_tracer) {
std::ostringstream trace;
trace << "Connecting to " << address << ", port " << port;
m_tracer->traceSend(trace.str());
}
if (addr.sin_addr.s_addr == static_cast <int>(-1)) {
::hostent* hostInfo = ::gethostbyname(address.c_str());
if (!hostInfo) {
// Error: cannot resolve address
throw vmime::exceptions::connection_error("Cannot resolve address.");
}
memcpy(reinterpret_cast <char*>(&addr.sin_addr), hostInfo->h_addr, hostInfo->h_length);
}
m_serverAddress = address;
// Get a new socket
m_desc = ::socket(AF_INET, SOCK_STREAM, 0);
if (m_desc == INVALID_SOCKET) {
try {
int err = WSAGetLastError();
throwSocketError(err);
} catch (exceptions::socket_exception& e) {
throw vmime::exceptions::connection_error("Error while creating socket.", e);
}
}
// Start connection
if (::connect(m_desc, reinterpret_cast <sockaddr*>(&addr), sizeof(addr)) == -1) {
try {
int err = WSAGetLastError();
throwSocketError(err);
} catch (exceptions::socket_exception& e) {
::closesocket(m_desc);
m_desc = INVALID_SOCKET;
// Error
throw vmime::exceptions::connection_error("Error while connecting socket.", e);
}
}
// Set socket to non-blocking
unsigned long non_blocking = 1;
::ioctlsocket(m_desc, FIONBIO, &non_blocking);
}
bool windowsSocket::isConnected() const {
if (m_desc == INVALID_SOCKET) {
return false;
}
char buff;
return ::recv(m_desc, &buff, 1, MSG_PEEK) != 0;
}
void windowsSocket::disconnect() {
if (m_desc != INVALID_SOCKET) {
if (m_tracer) {
m_tracer->traceSend("Disconnecting");
}
::shutdown(m_desc, SD_BOTH);
::closesocket(m_desc);
m_desc = INVALID_SOCKET;
}
}
static bool isNumericAddress(const char* address) {
struct addrinfo hint, *info = NULL;
memset(&hint, 0, sizeof(hint));
hint.ai_family = AF_UNSPEC;
hint.ai_flags = AI_NUMERICHOST;
if (getaddrinfo(address, 0, &hint, &info) == 0) {
freeaddrinfo(info);
return true;
} else {
return false;
}
}
const string windowsSocket::getPeerAddress() const {
// Get address of connected peer
sockaddr peer;
socklen_t peerLen = sizeof(peer);
getpeername(m_desc, reinterpret_cast <sockaddr*>(&peer), &peerLen);
// Convert to numerical presentation format
char host[NI_MAXHOST + 1];
char service[NI_MAXSERV + 1];
if (getnameinfo(reinterpret_cast <sockaddr *>(&peer), peerLen,
host, sizeof(host), service, sizeof(service),
/* flags */ NI_NUMERICHOST) == 0) {
return string(host);
}
return ""; // should not happen
}
const string windowsSocket::getPeerName() const {
// Get address of connected peer
sockaddr peer;
socklen_t peerLen = sizeof(peer);
getpeername(m_desc, reinterpret_cast <sockaddr*>(&peer), &peerLen);
// If server address as specified when connecting is a numeric
// address, try to get a host name for it
if (isNumericAddress(m_serverAddress.c_str())) {
char host[NI_MAXHOST + 1];
char service[NI_MAXSERV + 1];
if (getnameinfo(reinterpret_cast <sockaddr *>(&peer), peerLen,
host, sizeof(host), service, sizeof(service),
/* flags */ NI_NAMEREQD) == 0) {
return string(host);
}
}
return m_serverAddress;
}
size_t windowsSocket::getBlockSize() const {
return 16384; // 16 KB
}
void windowsSocket::receive(vmime::string& buffer) {
const size_t size = receiveRaw(m_buffer, sizeof(m_buffer));
buffer = utility::stringUtils::makeStringFromBytes(m_buffer, size);
}
size_t windowsSocket::receiveRaw(byte_t* buffer, const size_t count) {
m_status &= ~STATUS_WOULDBLOCK;
// Check whether data is available
if (!waitForRead(50 /* msecs */)) {
// No data available at this time
// Check if we are timed out
if (m_timeoutHandler &&
m_timeoutHandler->isTimeOut()) {
if (!m_timeoutHandler->handleTimeOut()) {
// Server did not react within timeout delay
throwSocketError(WSAETIMEDOUT);
} else {
// Reset timeout
m_timeoutHandler->resetTimeOut();
}
}
// Continue waiting for data
return 0;
}
// Read available data
int ret = ::recv(m_desc, reinterpret_cast <char*>(buffer), count, 0);
if (ret == SOCKET_ERROR) {
int err = WSAGetLastError();
if (err != WSAEWOULDBLOCK) {
throwSocketError(err);
}
m_status |= STATUS_WOULDBLOCK;
// Error or no data
return 0;
} else if (ret == 0) {
// Host shutdown
throwSocketError(WSAENOTCONN);
} else {
// Data received, reset timeout
if (m_timeoutHandler) {
m_timeoutHandler->resetTimeOut();
}
return ret;
}
}
void windowsSocket::send(const vmime::string& buffer) {
sendRaw(reinterpret_cast <const byte_t*>(buffer.data()), buffer.length());
}
void windowsSocket::send(const char* str) {
sendRaw(reinterpret_cast <const byte_t*>(str), strlen(str));
}
void windowsSocket::sendRaw(const byte_t* buffer, const size_t count) {
m_status &= ~STATUS_WOULDBLOCK;
size_t size = count;
while (size > 0) {
const int ret = ::send(m_desc, reinterpret_cast <const char*>(buffer), size, 0);
if (ret == SOCKET_ERROR) {
int err = WSAGetLastError();
if (err != WSAEWOULDBLOCK) {
throwSocketError(err);
}
waitForWrite(50 /* msecs */);
} else {
buffer += ret;
size -= ret;
}
}
// Reset timeout
if (m_timeoutHandler) {
m_timeoutHandler->resetTimeOut();
}
}
size_t windowsSocket::sendRawNonBlocking(const byte_t* buffer, const size_t count) {
m_status &= ~STATUS_WOULDBLOCK;
const int ret = ::send(m_desc, reinterpret_cast <const char*>(buffer), count, 0);
if (ret == SOCKET_ERROR) {
int err = WSAGetLastError();
if (err == WSAEWOULDBLOCK) {
// Check if we are timed out
if (m_timeoutHandler &&
m_timeoutHandler->isTimeOut()) {
if (!m_timeoutHandler->handleTimeOut()) {
// Could not send data within timeout delay
throwSocketError(err);
} else {
// Reset timeout
m_timeoutHandler->resetTimeOut();
}
}
m_status |= STATUS_WOULDBLOCK;
// No data can be written at this time
return 0;
} else {
throwSocketError(err);
}
}
// Reset timeout
if (m_timeoutHandler) {
m_timeoutHandler->resetTimeOut();
}
return ret;
}
unsigned int windowsSocket::getStatus() const {
return m_status;
}
void windowsSocket::throwSocketError(const int err) {
std::ostringstream oss;
string msg;
LPTSTR str;
if (::FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL, err, 0, (LPTSTR) &str, 0, NULL) == 0) {
// Failed getting message
oss << "Unknown socket error (code " << err << ")";
} else {
oss << str;
::LocalFree(str);
}
msg = oss.str();
throw exceptions::socket_exception(msg);
}
bool windowsSocket::waitForData(const bool read, const bool write, const int msecs) {
for (int i = 0 ; i <= msecs / 10 ; ++i) {
// Check whether data is available
fd_set fds;
FD_ZERO(&fds);
FD_SET(m_desc, &fds);
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 10000; // 10 ms
int ret = ::select(m_desc + 1, read ? &fds : NULL, write ? &fds : NULL, NULL, &tv);
if (ret == SOCKET_ERROR) {
int err = WSAGetLastError();
throwSocketError(err);
} else if (ret > 0) {
return true;
}
// No data available at this time
// Check if we are timed out
if (m_timeoutHandler &&
m_timeoutHandler->isTimeOut()) {
if (!m_timeoutHandler->handleTimeOut()) {
// Server did not react within timeout delay
throw exceptions::operation_timed_out();
} else {
// Reset timeout
m_timeoutHandler->resetTimeOut();
}
}
}
return false; // time out
}
bool windowsSocket::waitForRead(const int msecs) {
return waitForData(/* read */ true, /* write */ false, msecs);
}
bool windowsSocket::waitForWrite(const int msecs) {
return waitForData(/* read */ false, /* write */ true, msecs);
}
shared_ptr <net::timeoutHandler> windowsSocket::getTimeoutHandler() {
return m_timeoutHandler;
}
void windowsSocket::setTracer(const shared_ptr <net::tracer>& tracer) {
m_tracer = tracer;
}
shared_ptr <net::tracer> windowsSocket::getTracer() {
return m_tracer;
}
//
// posixSocketFactory
//
shared_ptr <vmime::net::socket> windowsSocketFactory::create() {
shared_ptr <vmime::net::timeoutHandler> th;
return make_shared <windowsSocket>(th);
}
shared_ptr <vmime::net::socket> windowsSocketFactory::create(const shared_ptr <vmime::net::timeoutHandler>& th) {
return make_shared <windowsSocket>(th);
}
} // posix
} // platforms
} // vmime
#endif // VMIME_PLATFORM_IS_WINDOWS && VMIME_HAVE_MESSAGING_FEATURES