#include "http-protocol.h"
#include "netparameters.h"
namespace Security {
namespace Network {
HTTPProtocol::HTTPProtocol(Manager &mgr, const char *scheme) : Protocol(mgr, scheme)
{
}
HTTPProtocol::HTTPTransfer *HTTPProtocol::makeTransfer(const Target &target, Operation operation)
{
return new HTTPTransfer(*this, target, operation, defaultHttpPort);
}
HTTPProtocol::HTTPConnection::HTTPConnection(Protocol &proto,
const HostTarget &hostTarget)
: TCPConnection(proto, hostTarget),
subVersion(defaultSubVersion),
state(errorState), deferSendRequest(false)
{
const HostTarget &host = proxyHostTarget();
connect(host.host(), host.port());
state = connecting;
}
void HTTPProtocol::HTTPConnection::request(const char *operation)
{
mOperation = operation;
if (state == idle) sendRequest();
}
void HTTPProtocol::HTTPConnection::sendRequest()
{
assert(state == idle);
subVersion = getv<int>(kNetworkHttpUseVersion, defaultSubVersion);
flushOutput(false); const Target &target = this->target();
if (transfer().useProxyHeaders()) {
printfe("%s %s HTTP/1.%d", mOperation.c_str(), target.urlForm().c_str(), subVersion);
authorizationHeader("Proxy-Authorization", hostTarget,
kNetworkGenericProxyUsername, kNetworkGenericProxyPassword);
} else {
printfe("%s %s HTTP/1.%d", mOperation.c_str(), target.path.c_str(), subVersion);
}
hostHeader();
authorizationHeader("Authorization", target,
kNetworkGenericUsername, kNetworkGenericPassword);
printfe("User-Agent: %s",
getv<string>(kNetworkHttpUserAgent, "MacNetwork/1.0 (Macintosh)").c_str());
if (int restartOffset = getv<int>(kNetworkRestartPosition, 0)) {
printfe("Range: bytes=%d-", restartOffset);
}
{
string otherHeaders;
if (get(kNetworkHttpMoreHeaders, otherHeaders)) {
static const char lineEndings[] = "\r\n";
const char *p = otherHeaders.c_str();
while (const char *q = strpbrk(p, lineEndings)) {
if (q > p)
printfe("%.*s", q - p, p);
p = q + strspn(q, lineEndings);
}
if (*p)
printfe("%s", p);
}
}
if (transfer().hasSource()) {
Source &source = transfer().source();
size_t size = source.getSize();
if (size == Source::unknownSize) {
} else {
printfe("Content-length: %ld", size);
}
printfe("Content-Type: %s", getv<string>(kNetworkHttpPostContentType, "text/plain").c_str());
printfe(""); mode(source); } else {
printfe(""); }
flushOutput(); mode(lineInput); state = primaryResponse; }
void HTTPProtocol::HTTPConnection::hostHeader()
{
const HostTarget &host = target().host;
if (host.port())
printfe("Host: %s:%d", host.host().name().c_str(), host.port());
else
printfe("Host: %s", host.host().name().c_str());
}
void HTTPProtocol::HTTPConnection::authorizationHeader(const char *headerName,
const HostTarget &host,
ParameterSource::Key userKey, ParameterSource::Key passKey)
{
string username = host.haveUserPass() ? host.username() : getv<string>(userKey);
string password = host.haveUserPass() ? host.password() : getv<string>(passKey);
if (!username.empty()) {
static const char alphabet[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
string token = username + ":" + password;
char *buffer = new char[4 * token.length() / 3 + 2]; const char *src = token.c_str(), *end = src + token.length();
char *outp = buffer;
while (src < end) {
uint32 binary = src[0] << 16;
if (src+1 < end)
binary |= src[1] << 8 | src[2];
*outp++ = alphabet[(binary >> 18) & 0x3F];
*outp++ = alphabet[(binary >> 12) & 0x3F];
*outp++ = (src+1 < end) ? alphabet[(binary >> 6) & 0x3F] : '=';
*outp++ = (src+2 < end) ? alphabet[binary & 0x3F] : '=';
src += 3;
}
*outp = '\0';
printfe("%s: Basic %s", headerName, buffer);
delete[] buffer;
}
}
void HTTPProtocol::HTTPConnection::transit(Event event, char *input, size_t length)
{
switch (event) {
case autoWriteDone: return;
case endOfInput: switch (state) {
case idle:
case readWholeBody: break;
case primaryResponse: return restart();
default: UnixError::throwMe(ECONNRESET); }
break;
case connectionDone: {
assert(state == connecting);
int error = length;
observe(Observer::connectEvent, &error);
if (error) { connect();
} else { state = idle;
if (!deferSendRequest) { mode(lineInput);
sendRequest();
}
}
}
return;
default:
break;
}
switch (state) {
case primaryResponse:
{
assert(mode() == lineInput);
observe(Observer::protocolReceive, input);
transfer().httpResponse() = input; int reasonPos;
if (sscanf(input, "HTTP/%d.%d %u %n",
&httpVersionMajor, &httpVersionMinor,
&transfer().httpResponseCode(), &reasonPos) != 3) {
fail(Transfer::remoteFailure);
}
if (httpVersionMajor != 1) fail(Transfer::remoteFailure);
if (httpVersionMinor < 0 || httpVersionMinor > 1)
fail(Transfer::remoteFailure);
observe (Observer::resultCodeReady);
state = readHeaders;
restarting(false);
break;
}
case readHeaders:
{
assert(mode() == lineInput);
if (length) { headers().add(input);
observe(Observer::protocolReceive, input);
} else { observe(Observer::protocolReceive, "** END OF HEADER **");
observe(Observer::downloading, input);
if (const char *encoding = headers().find("Transfer-Encoding")) {
if (!strcasecmp(encoding, "chunked")) {
state = chunkHeader;
break;
} else if (!strcasecmp(encoding, "identity")) {
} else {
fail(Transfer::remoteFailure);
}
}
state = readWholeBody;
if (const char *lengthArg = headers().find("Content-Length")) {
size_t length = strtol(lengthArg, NULL, 10);
sink().setSize(length);
if (length > 0)
mode(sink(), length);
else finish();
} else { mode(sink());
}
}
break;
}
case chunkHeader:
{
assert(mode() == lineInput);
char *endOfMatch;
size_t chunkLength = strtol(input, &endOfMatch, 0x10);
if (length == 0 || endOfMatch == input) fail(Transfer::remoteFailure);
if (chunkLength) {
secdebug("http", "reading chunk of %ld bytes", chunkLength);
mode(sink(), chunkLength);
state = chunkDownload;
} else {
secdebug("http", "final chunk marker");
state = chunkTrailer;
observe(Observer::protocolReceive, "** END OF DATA **");
}
break;
}
case chunkGap:
{
assert(mode() == lineInput);
state = chunkHeader;
break;
}
case chunkTrailer:
{
assert(mode() == lineInput);
if (input[0] == '\0') { finish();
} else {
headers().add(input);
observe(Observer::protocolReceive, input);
}
break;
}
case chunkDownload:
{
assert(event == autoReadDone);
state = chunkGap;
mode(lineInput);
break;
}
case readWholeBody:
{
assert(event == autoReadDone || event == endOfInput);
finish();
break;
}
case idle:
{
secdebug("http",
"%p event %d while idle; destroying connection", this, event);
abort();
state = dead;
}
break;
default:
assert(false);
}
}
void HTTPProtocol::HTTPConnection::transitError(const CssmCommonError &error)
{
fail(true); }
void HTTPProtocol::HTTPConnection::finish()
{
flushInput(); chooseRetain(); mode(lineInput); state = idle; Connection::finish(); }
void HTTPProtocol::HTTPConnection::fail(bool forceDrop)
{
if (forceDrop)
retain(false); else
chooseRetain(); Connection::fail(); }
bool HTTPProtocol::HTTPConnection::validate()
{
assert(state == idle);
tickle(); return state == idle;
}
void HTTPProtocol::HTTPConnection::chooseRetain()
{
retain(strcasecmp(headers().find("Connection", "Keep"), "Close"));
}
HTTPProtocol::HTTPTransfer::HTTPTransfer(Protocol &proto,
const Target &tgt, Operation operation, IPPort defaultPort)
: Transfer(proto, tgt, operation, defaultPort),
mResultClass(unclassifiedFailure)
{
}
void HTTPProtocol::HTTPTransfer::start()
{
const HostTarget &host = proxyHostTarget();
HTTPConnection *connection = protocol.manager.findConnection<HTTPConnection>(host);
if (connection == NULL)
connection = new HTTPConnection(protocol, host);
connection->dock(this);
startRequest();
}
void HTTPProtocol::HTTPTransfer::abort()
{
observe(Observer::aborting);
setError("aborted");
connectionAs<HTTPConnection>().abort();
}
void HTTPProtocol::HTTPConnection::abort()
{
close();
fail(true);
}
void HTTPProtocol::HTTPTransfer::startRequest()
{
const char *defaultForm;
switch (operation()) {
case Protocol::upload: defaultForm = "PUT"; break;
case Protocol::transaction: defaultForm = "POST"; break;
default: defaultForm = "GET"; break;
}
connectionAs<HTTPConnection>().request(getv<string>(kNetworkHttpCommand, defaultForm).c_str());
}
bool HTTPProtocol::HTTPTransfer::useProxyHeaders() const
{
return protocol.isProxy();
}
Transfer::ResultClass HTTPProtocol::HTTPTransfer::resultClass() const
{
switch (state()) {
case failed:
return mResultClass;
case finished:
{
if (mResultClass != unclassifiedFailure)
return mResultClass; unsigned int code = httpResponseCode();
if (code == 401 || code == 407 || code == 305) return authorizationFailure;
else if (code / 100 == 3) return success;
else if (code / 100 == 2) return success;
else return remoteFailure;
}
default:
assert(false);
return localFailure;
}
}
void HTTPProtocol::HTTPTransfer::fail(ResultClass why, OSStatus how)
{
mResultClass = why;
Error::throwMe(how);
}
void HTTPProtocol::HTTPHeaderMap::merge(string key, string &old, string newValue)
{
old = old + ", " + newValue;
}
} }