blob: 8880b79a9d19b587a5940270e5ce93a7a06455ca [file] [log] [blame]
// Copyright (C) 2001-2005 Open Source Telecom Corporation.
// Copyright (C) 2006-2010 David Sugar, Tycho Softworks.
//
// 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 2 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
// As a special exception, you may use this file as part of a free software
// library without restriction. Specifically, if other files instantiate
// templates or use macros or inline functions from this file, or you compile
// this file and link it with other files to produce an executable, this
// file does not by itself cause the resulting executable to be covered by
// the GNU General Public License. This exception does not however
// invalidate any other reasons why the executable file might be covered by
// the GNU General Public License.
//
// This exception applies only to the code released under the name GNU
// Common C++. If you copy code from other releases into a copy of GNU
// Common C++, as the General Public License permits, the exception does
// not apply to the code that you add in this way. To avoid misleading
// anyone as to the status of such modified files, you must delete
// this exception notice from them.
//
// If you write modifications of your own for GNU Common C++, it is your choice
// whether to permit this exception to apply to your modifications.
// If you do not wish that, delete this exception notice.
//
#include <cc++/config.h>
#ifdef CCXX_WITHOUT_EXTRAS
#include <cc++/export.h>
#endif
#include <cc++/file.h>
#include <cc++/thread.h>
#include <cc++/exception.h>
#include <cc++/address.h>
#include <cc++/socket.h>
#ifndef CCXX_WITHOUT_EXTRAS
#include <cc++/export.h>
#endif
#include <cc++/url.h>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <fcntl.h>
#include <cerrno>
#include <iostream>
#ifdef WIN32
#include <io.h>
#endif
#ifdef HAVE_SSTREAM
#include <sstream>
#else
#include <strstream>
#endif
#include <cctype>
#ifndef WIN32
// cause problem on Solaris
#if !defined(__sun) && !defined(__SUN__)
#ifdef HAVE_NET_IF_H
#include <net/if.h>
#endif
#endif
#include <sys/ioctl.h>
#endif
#ifdef CCXX_NAMESPACES
namespace ost {
using namespace std;
#endif
URLStream::URLStream(Family fam, timeout_t to) :
TCPStream(fam)
{
persistent = false;
proxyPort = 0;
timeout = to;
protocol = protocolHttp1_0;
follow = true;
proxyAuth = authAnonymous;
encoding = encodingBinary;
proxyUser = proxyPasswd = NULL;
auth = authAnonymous;
cookie = agent = pragma = referer = user = password = NULL;
localif = NULL;
setError(false);
}
int URLStream::aRead(char *buffer, size_t len, timeout_t timer)
{
return readData(buffer, len, 0, timer);
}
int URLStream::aWrite(char *buffer, size_t len, timeout_t timer)
{
return writeData(buffer, len, timer);
}
void URLStream::httpHeader(const char *header, const char *value)
{
}
char **URLStream::extraHeader(void)
{
return NULL;
}
int URLStream::underflow(void)
{
ssize_t len = 0, rlen;
char *buf;
if(bufsize == 1)
return TCPStream::underflow();
if(!gptr())
return EOF;
if(gptr() < egptr())
return (unsigned char)*gptr();
rlen = (ssize_t)((gbuf + bufsize) - eback());
if(encoding == encodingChunked) {
buf = (char *)eback();
*buf = '\n';
while(!chunk && (*buf == '\n' || *buf == '\r')) {
*buf = 0;
len = readLine(buf, rlen, timeout);
}
if(len) {
if(!chunk)
chunk = strtol(buf, NULL, 16);
if(rlen > (int)chunk)
rlen = chunk;
}
else
rlen = -1;
}
if(rlen > 0) {
if(Socket::state == STREAM)
rlen = aRead((char *)eback(), rlen, timeout);
else if(timeout) {
if(Socket::isPending(pendingInput, timeout))
rlen = readData(eback(), rlen);
else
rlen = -1;
}
else
rlen = readData(eback(), rlen);
}
if(encoding == encodingChunked && rlen > 0)
chunk -= rlen;
if(rlen < 1) {
if(rlen < 0)
clear(ios::failbit | rdstate());
return EOF;
}
setg(eback(), eback(), eback() + rlen);
return (unsigned char)*gptr();
}
void URLStream::setProxy(const char *host, tpport_t port)
{
switch(family) {
#ifdef CCXX_IPV6
case IPV6:
v6proxyHost = host;
break;
#endif
case IPV4:
proxyHost = host;
break;
default:
proxyPort = 0;
return;
}
proxyPort = port;
}
URLStream::Error URLStream::submit(const char *path, const char **vars, size_t buf)
{
Error status = errInvalid, saved;
if(!strnicmp(path, "http:", 5)) {
urlmethod = methodHttpGet;
path = strchr(path + 5, '/');
status = sendHTTPHeader(path, vars, buf);
}
if((status == errInvalid || status == errTimeout)) {
if(Socket::state != AVAILABLE)
close();
return status;
}
else {
saved = status;
status = getHTTPHeaders();
if(status == errSuccess)
return saved;
else if(status == errTimeout) {
if(Socket::state != AVAILABLE)
close();
}
return status;
}
}
URLStream::Error URLStream::post(const char *path, MIMEMultipartForm &form, size_t buf)
{
Error status = errInvalid, saved;
if(!strnicmp(path, "http:", 5)) {
urlmethod = methodHttpPostMultipart;
path = strchr(path + 5, '/');
status = sendHTTPHeader(path, (const char **)form.getHeaders(), buf);
}
if(status == errInvalid || status == errTimeout) {
if(Socket::state != AVAILABLE)
close();
return status;
}
saved = status;
status = getHTTPHeaders();
if(status == errSuccess) {
form.body(dynamic_cast<std::ostream *>(this));
return saved;
}
if(status == errTimeout) {
if(Socket::state != AVAILABLE)
close();
}
return status;
}
URLStream::Error URLStream::post(const char *path, const char **vars, size_t buf)
{
Error status = errInvalid, saved;
if(!strnicmp(path, "http:", 5)) {
urlmethod = methodHttpPost;
path = strchr(path + 5, '/');
status = sendHTTPHeader(path, vars, buf);
}
if((status == errInvalid || status == errTimeout)) {
if(Socket::state != AVAILABLE)
close();
return status;
}
saved = status;
status = getHTTPHeaders();
if(status == errSuccess)
return saved;
if(status == errTimeout) {
if(Socket::state != AVAILABLE)
close();
}
return status;
}
URLStream::Error URLStream::head(const char *path, size_t buf)
{
Error status = errInvalid, saved;
if(!strnicmp(path, "http:", 5)) {
urlmethod = methodHttpGet;
path = strchr(path + 5, '/');
status = sendHTTPHeader(path, NULL, buf);
}
if((status == errInvalid || status == errTimeout)) {
if(Socket::state != AVAILABLE)
close();
return status;
}
else {
saved = status;
status = getHTTPHeaders();
if(status == errSuccess)
return saved;
else if(status == errTimeout) {
if(Socket::state != AVAILABLE)
close();
}
return status;
}
}
URLStream &URLStream::getline(char *buffer, size_t size)
{
size_t len;
*buffer = 0;
// TODO: check, we mix use of streambuf with Socket::readLine...
iostream::getline(buffer, (unsigned long)size);
len = strlen(buffer);
while(len) {
if(buffer[len - 1] == '\r' || buffer[len - 1] == '\n')
buffer[len - 1] = 0;
else
break;
--len;
}
return *this;
}
URLStream::Error URLStream::get(size_t buffer)
{
String path = String("http://") + m_host;
if ( m_address.operator[](0) != '/' )
path += "/";
path += m_address;
return get(path.c_str(), buffer);
}
URLStream::Error URLStream::get(const char *urlpath, size_t buf)
{
const char *path = urlpath;
Error status = errInvalid, saved;
urlmethod = methodFileGet;
if(Socket::state != AVAILABLE)
close();
if(!strnicmp(path, "file:", 5)) {
urlmethod = methodFileGet;
path += 5;
}
else if(!strnicmp(path, "http:", 5)) {
urlmethod = methodHttpGet;
path = strchr(path + 5, '/');
}
switch(urlmethod) {
case methodHttpGet:
status = sendHTTPHeader(path, NULL, buf);
break;
case methodFileGet:
if(so != INVALID_SOCKET)
::close((int)so);
so = ::open(path, O_RDWR);
if(so == INVALID_SOCKET)
so = ::open(path, O_RDONLY);
// FIXME: open return the same handle type as socket call ??
if(so == INVALID_SOCKET)
return errInvalid;
Socket::state = STREAM;
allocate(buf);
return errSuccess;
default:
break;
}
if((status == errInvalid || status == errTimeout)) {
if(Socket::state != AVAILABLE)
close();
return status;
}
else {
saved = status;
status = getHTTPHeaders();
if(status == errSuccess)
return saved;
else if(status == errTimeout) {
if(Socket::state != AVAILABLE)
close();
}
return status;
}
}
URLStream::Error URLStream::getHTTPHeaders()
{
char buffer[512];
size_t buf = sizeof(buffer);
Error status = errSuccess;
char *cp, *ep;
ssize_t len = 1;
char nc = 0;
chunk = ((unsigned)-1) / 2;
encoding = encodingBinary;
while(len > 0) {
len = readLine(buffer, buf, timeout);
if(len < 1)
return errTimeout;
// FIXME: for multiline syntax ??
if(buffer[0] == ' ' || buffer[0] == '\r' || buffer[0] == '\n')
break;
cp = strchr(buffer, ':');
if(!cp)
continue;
*(cp++) = 0;
while(*cp == ' ' || *cp == '\t')
++cp;
ep = strchr(cp, '\n');
if(!ep)
ep = &nc;
while(*ep == '\n' || *ep == '\r' || *ep == ' ') {
*ep = 0;
if((--ep) < cp)
break;
}
if(!stricmp(buffer, "Transfer-Encoding")) {
if(!stricmp(cp, "chunked")) {
chunk = 0;
encoding = encodingChunked;
}
}
httpHeader(buffer, cp);
}
return status;
}
void URLStream::close(void)
{
if(Socket::state == AVAILABLE)
return;
endStream();
so = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(so != INVALID_SOCKET)
Socket::state = AVAILABLE;
}
URLStream::Error URLStream::sendHTTPHeader(const char *url, const char **vars, size_t buf)
{
// TODO: implement authentication
char reloc[4096];
// "//" host ":" port == max 2 + 128 + 1 + 5 + 1(\0) = 137, rounded 140
char host[140];
// TODO: add support for //user:pass@host:port/ syntax
#ifdef HAVE_SSTREAM
ostringstream str;
#else
char buffer[4096];
strstream str(buffer, sizeof(buffer), ios::out);
#endif
char *ref, *cp, *ep;
char *hp;
const char *uri = "/";
int count = 0;
size_t len = 0;
tpport_t port = 80;
const char **args = vars;
const char *var;
bool lasteq = true;
struct servent *svc;
retry:
#ifdef HAVE_SSTREAM
str.str("");
#else
buffer[0] = 0;
str.seekp(0);
#endif
setString(host, sizeof(host), url);
reformat:
hp = strchr(host, '/');
if(!hp) {
host[0] = '/';
setString(host + 1, sizeof(host) - 1, url);
goto reformat;
}
while(*hp == '/')
++hp;
cp = strchr(hp, '/');
if (cp) *cp = 0;
ep = strrchr(hp, ':');
if(ep) {
*ep = 0;
++ep;
if(isdigit(*ep))
port = atoi(ep);
else {
Socket::mutex.enter();
svc = getservbyname(ep, "tcp");
if(svc)
port = ntohs(svc->s_port);
Socket::mutex.leave();
}
}
if(!proxyPort) {
const char* ep1 = url;
while(*ep1 == '/')
++ep1;
ep1 = strchr(ep1, '/');
if(ep1)
uri = ep1;
}
switch(urlmethod) {
case methodHttpGet:
str << "GET ";
if(proxyPort) {
str << "http:" << url;
if(!cp) str << '/';
}
else
str << uri;
break;
case methodHttpPost:
case methodHttpPostMultipart:
str << "POST ";
if(proxyPort) {
str << "http:" << url;
if(!cp) str << '/';
}
else
str << uri;
break;
default:
return errInvalid;
}
if(vars && urlmethod == methodHttpGet) {
str << "?";
while(*vars) {
if(count++ && lasteq)
str << "&";
str << *vars;
if(!lasteq)
lasteq = true;
else if(strchr(*vars, '='))
lasteq = true;
else {
lasteq = false;
str << "=";
}
++vars;
}
}
switch(protocol) {
case protocolHttp1_1:
str << " HTTP/1.1" << "\r\n";
break;
case protocolHttp1_0:
str << " HTTP/1.0" << "\r\n";
break;
}
if ( m_host.empty() )
m_host = hp;
str << "Host: " << hp << "\r\n";
if(agent)
str << "User-Agent: " << agent << "\r\n";
if(cookie)
str << "Cookie: " << cookie << "\r\n";
if(pragma)
str << "Pragma: " << pragma << "\r\n";
if(referer)
str << "Referer: " << referer << "\r\n";
switch(auth) {
case authBasic:
str << "Authorization: Basic ";
snprintf(reloc, 64, "%s:%s", user, password);
b64Encode(reloc, reloc + 64, 128);
str << reloc + 64 << "\r\n";
case authAnonymous:
break;
}
switch(proxyAuth) {
case authBasic:
str << "Proxy-Authorization: Basic ";
snprintf(reloc, 64, "%s:%s", proxyUser, proxyPasswd);
b64Encode(reloc, reloc + 64, 128);
str << reloc + 64 << "\r\n";
str << "Proxy-Connection: close" << "\r\n";
case authAnonymous:
break;
}
str << "Connection: close\r\n";
char **add = extraHeader();
if(add) {
while(*add) {
str << *(add++) << ": ";
str << *(add++) << "\r\n";
}
}
if(vars)
switch(urlmethod) {
case methodHttpPost:
while(*args) {
var = *args;
if(count++ || !strchr(var, '='))
len += strlen(var) + 1;
else
len = strlen(var);
++args;
}
count = 0;
len += 2;
str << "Content-Type: application/x-www-form-urlencoded" << "\r\n";
str << "Content-Length: " << (unsigned)len << "\r\n";
break;
case methodHttpPostMultipart:
while(*args)
str << *(args++) << "\r\n";
default:
break;
}
str << "\r\n";
#ifdef HAVE_SSTREAM
// sstream does not want ends
#else
str << ends;
#endif
if(Socket::state != AVAILABLE)
close();
#ifndef WIN32
#ifdef SOICGIFINDEX
if (localif != NULL) {
struct ifreq ifr;
switch(family) {
#ifdef CCXX_IPV6
case IPV6:
sockaddr_in6 source;
int alen = sizeof(source);
memset(&ifr, 0, sizeof(ifr));
setString(ifr.ifr_name, sizeof(ifr.ifr_name), localif);
if (ioctl(so, SIOCGIFINDEX, &ifr) < 0)
return errInterface;
else {
if (setsockopt(so, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) == -1)
return errInterface;
else if(getsockname(so, (struct sockaddr*)&source,(socklen_t *) &alen) == -1)
return errInterface;
else if (bind(so, (struct sockaddr*)&source, sizeof(source)) == -1)
return errInterface;
else
source.sin6_port = 0;
}
break;
#endif
case IPV4:
sockaddr_in source;
int alen = sizeof(source);
memset(&ifr, 0, sizeof(ifr));
setString(ifr.ifr_name, sizeof(ifr.ifr_name), localif);
if (ioctl(so, SIOCGIFINDEX, &ifr) < 0)
return errInterface;
else {
if (setsockopt(so, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) == -1)
return errInterface;
else if(getsockname(so, (struct sockaddr*)&source,(socklen_t *) &alen) == -1)
return errInterface;
else if (bind(so, (struct sockaddr*)&source, sizeof(source)) == -1)
return errInterface;
else
source.sin_port = 0;
}
}
}
#endif
#endif
if(proxyPort) {
switch(family) {
#ifdef CCXX_IPV6
case IPV6:
connect(v6proxyHost, proxyPort, (unsigned)buf);
break;
#endif
case IPV4:
connect(proxyHost, proxyPort, (unsigned)buf);
break;
}
}
else {
switch(family) {
#ifdef CCXX_IPV6
case IPV6:
connect(IPV6Host(hp), port, (unsigned)buf);
break;
#endif
case IPV4:
connect(IPV4Host(hp), port, (unsigned)buf);
}
}
if(!isConnected())
return errUnreachable;
// FIXME: send (or write) can send less than len bytes
// use stream funcion ??
#ifdef HAVE_SSTREAM
writeData(str.str().c_str(), _IOLEN64 str.str().length());
#else
writeData(str.str().c_str(), _IOLEN64 str.str().length());
#endif
if(urlmethod == methodHttpPost && vars) {
#ifdef HAVE_SSTREAM
str.str() = "";
#else
str.seekp(0);
#endif
bool sep = false;
while(*vars) {
if(sep)
writeData("&", 1);
else
sep = true;
var = *vars;
if(!strchr(var, '=')) {
snprintf(reloc, sizeof(reloc), "%s=%s", var, *(++vars));
writeData(reloc, strlen(reloc));
}
else
writeData(var, strlen(var));
++vars;
}
writeData("\r\n", 2);
}
cont:
#ifdef HAVE_SSTREAM
char buffer[4096];
#else
// nothing here
#endif
len = readLine(buffer, sizeof(buffer) - 1, timeout);
if(len < 1)
return errTimeout;
if(strnicmp(buffer, "HTTP/", 5))
return errInvalid;
ref = strchr(buffer, ' ');
while(*ref == ' ')
++ref;
switch(atoi(ref)) {
default:
return errInvalid;
case 100:
goto cont;
case 200:
return errSuccess;
case 401:
return errUnauthorized;
case 403:
return errForbidden;
case 404:
return errMissing;
case 405:
return errDenied;
case 500:
case 501:
case 502:
case 503:
case 504:
case 505:
return errFailure;
case 300:
case 301:
case 302:
break;
}
if(!follow)
return errRelocated;
for(;;) {
len = readLine(reloc, sizeof(reloc), timeout);
if(len < 1)
return errTimeout;
if(!strnicmp(reloc, "Location: ", 10))
break;
}
if(!strnicmp(reloc + 10, "http:", 5)) {
url = strchr(reloc + 15, '/');
ep = (char *)(url + strlen(url) - 1);
while(*ep == '\r' || *ep == '\n')
*(ep--) = 0;
}
else
url = reloc + 10;
close();
goto retry;
}
void URLStream::setAuthentication(Authentication a, const char *value)
{
auth = a;
if (auth != authAnonymous) {
if(!user)
user = "anonymous";
if(!password)
password = "";
}
}
void URLStream::setProxyAuthentication(Authentication a, const char *value)
{
proxyAuth = a;
if (proxyAuth != authAnonymous) {
if(!proxyUser)
proxyUser = "anonymous";
if(!proxyPasswd)
proxyPasswd = "";
}
}
void URLStream::setReferer(const char *str)
{
if(!str)
return;
referer = str;
}
#ifdef CCXX_NAMESPACES
}
#endif
/** EMACS **
* Local variables:
* mode: c++
* c-basic-offset: 4
* End:
*/