http-protocol.cpp   [plain text]


/*
 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
 * 
 * The contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License. Please obtain
 * a copy of the License at http://www.apple.com/publicsource and read it before
 * using this file.
 * 
 * This Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
 * specific language governing rights and limitations under the License.
 */


//
// http-protocol - HTTP protocol objects
//
// HTTP Transfers succeed (state() == finished) if the HTTP protocol was successfully
// observed. This means that even 300/400/500 type results are "successful" as far
// as state() is concerned. ResultClass() will attempt to classify both successful and
// unsuccessful outcomes, and errorDescription() is the primary HTTP response line
// (HTTP/1.n ccc some-string). HTTP Transfers fail (state() == failed) only if they can't
// talk to the server, or a protocol violation (or unimplemented feature) is detected.
// Deal with it.
//
// Note that the protected flag deferSendRequest allows the state sequencer to be
// interrupted at the idle stage (before an HTTP request is sent over the virtual wire).
// This is used by the https protocol driver to "wedge in" the SSL negotiation. Not very
// elegant, but it works.
//
// This implementation of the http protocol includes http proxy operation. As a result,
// it is very important to distinguish the various HostTargets and Targets involved:
//	Connection::hostTarget is the host we're talking to - it could be a proxy.
//	Transfer::target.host is the host we're trying to reach.
// From the HTTPConnection's point of view:
//	hostTarget may be a proxy or the destination
//	target().host is always the host we're trying to reach
// If we're not in proxy mode, these two are usually the same (caveat tester).
//
#include "http-protocol.h"
#include "netparameters.h"


namespace Security {
namespace Network {


//
// Construct the protocol object
//
HTTPProtocol::HTTPProtocol(Manager &mgr, const char *scheme) : Protocol(mgr, scheme)
{
}


//
// Create a Transfer object for our protocol
//
HTTPProtocol::HTTPTransfer *HTTPProtocol::makeTransfer(const Target &target, Operation operation)
{
    return new HTTPTransfer(*this, target, operation, defaultHttpPort);
}


//
// Construct an HTTPConnection object
//
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;
}


//
// Start a request/response transaction on this Connection. This puts out all the
// HTTP request headers in one fell swoop (but not any request body).
// The Connection must be in idle state.
//
void HTTPProtocol::HTTPConnection::request(const char *operation)
{
    mOperation = operation;
    if (state == idle)	// already waiting for request
        sendRequest();
}

void HTTPProtocol::HTTPConnection::sendRequest()
{
    assert(state == idle);

    // what version of HTTP/1 shall we use?
    subVersion = getv<int>(kNetworkHttpUseVersion, defaultSubVersion);

    flushOutput(false);	// hold output until we're done
    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 restarting, add a Range header
    if (int restartOffset = getv<int>(kNetworkRestartPosition, 0)) {
        printfe("Range: bytes=%d-", restartOffset);
    }
    
    // add other headers set by caller, if any
    {
        string otherHeaders;
        if (get(kNetworkHttpMoreHeaders, otherHeaders)) {
            // launder and rinse - don't let the caller screw up the HTTP header structure
            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);
            }
            // now send any last (unterminated) line
            if (*p)
                printfe("%s", p);
        }
    }

    // add fields used for upstream transfer, if any, and initiate
    if (transfer().hasSource()) {
        Source &source = transfer().source();
        size_t size = source.getSize();
        if (size == Source::unknownSize) {
            //@@@ try to use Transfer-encoding: chunked -- for now, just use EOF delimiting
        } else {
            printfe("Content-length: %ld", size);
        }
        printfe("Content-Type: %s", getv<string>(kNetworkHttpPostContentType, "text/plain").c_str());
        printfe("");					// end of headers
        mode(source);					// initiate autoWrite mode
    } else {
        printfe("");					// end of headers, no data
    }

    flushOutput();						// release pent-up output
    mode(lineInput);					// line input mode
    state = primaryResponse;			// prime the state machine
}

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);
    //@@@ only "Basic" authentication supported for now
    if (!username.empty()) {
        //@@@ ad-hoc Base64 encoding. Replace with suitable stream encoder when available
        static const char alphabet[] = 
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
        string token = username + ":" + password;
        char *buffer = new char[4 * token.length() / 3 + 2];	// just enough
        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;
    }
}


//
// This is the master state transit machine for HTTP.
//
void HTTPProtocol::HTTPConnection::transit(Event event, char *input, size_t length)
{
    switch (event) {
    case autoWriteDone:	// ingore: it's asynchronous to our state machine
        return;
    case endOfInput:	// most of the time, this is a protocol error, so filter it out now
        switch (state) {
        case idle:
        case readWholeBody:	// expected
            break;
        case primaryResponse: // Connection failed; restart it
            return restart();
        default:		// unexpected; fail
            UnixError::throwMe(ECONNRESET);	// @@@ diagnostic?
        }
        break;
    case connectionDone: // TCP connection complete or failed
        {
            assert(state == connecting);
            int error = length;
            observe(Observer::connectEvent, &error);
            if (error) {	// retry
                connect();
            } else {		// connection good
                state = idle;
                if (!deferSendRequest) {	// (subclass wants to wedge in)
                    mode(lineInput);
                    sendRequest();
                }
            }
        }
        return;
    default:
        break;
    }

    switch (state) {
    case primaryResponse:
        {
            assert(mode() == lineInput);
            observe(Observer::protocolReceive, input);
            transfer().httpResponse() = input;	// remember response for caller
            // --> HTTP/major.minor status reason-phrase
            int reasonPos;
            if (sscanf(input, "HTTP/%d.%d %u %n",
                    &httpVersionMajor, &httpVersionMinor,
                    &transfer().httpResponseCode(), &reasonPos) != 3) {
                // malformed response header
                fail(Transfer::remoteFailure);
            }
            
            if (httpVersionMajor != 1)	// wrong major protocol Version
                fail(Transfer::remoteFailure);
            if (httpVersionMinor < 0 || httpVersionMinor > 1)
                fail(Transfer::remoteFailure);
 
            // notify the URLAccess emulation that we have the result code
            observe (Observer::resultCodeReady);
            
           // okay, we grok the version. We'll proceed for now reading headers etc.
            state = readHeaders;
            
            // we got input from the server, so this Connection is now confirmed good
            restarting(false);
            break;
        }
    case readHeaders:
        {
            assert(mode() == lineInput);
            if (length) {		// another header
                headers().add(input);
                observe(Observer::protocolReceive, input);
            } else {			// end of headers
                // we are now handling the transition from response headers to response body
                observe(Observer::protocolReceive, "** END OF HEADER **");
                observe(Observer::downloading, input);
                
                // Transfer-Encoding overrides Content-Length as per RFC2616 p.34
                if (const char *encoding = headers().find("Transfer-Encoding")) {
                    if (!strcasecmp(encoding, "chunked")) {
                        // eat input in chunks
                        state = chunkHeader;
                        // mode remains lineInput
                        break;
                    } else if (!strcasecmp(encoding, "identity")) {
                        // allowed and ignored
                    } else {
                        // unrecognized transfer-encoding
                        fail(Transfer::remoteFailure);
                    }
                }
                // no transfer-encoding (or transfer-encoding: identity): big gulp mode
                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	// null body, already done
                        finish();
                } else {	// read until EOI
                    mode(sink());
                }
            }
            break;
        }
    case chunkHeader:
        {
            assert(mode() == lineInput);
            // line should be (just) a hex number, sans "0x" prefix or spaces. Be strict
            char *endOfMatch;
            size_t chunkLength = strtol(input, &endOfMatch, 0x10);
            if (length == 0 || endOfMatch == input) // no valid number
                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') {	// end of trailer
                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:
        {
            // the only asynchronous event in idle mode is a connection drop
            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)
{
    // note that fail(const char * [, OSStatus]) has already called setError
    fail(true);		// fail transfer and throw out connection
}


void HTTPProtocol::HTTPConnection::finish()
{
    flushInput();			// clear excess garbage input (resynchronize)
    chooseRetain();			// shall we keep the Connection?
    mode(lineInput);		// ensure valid input mode
    state = idle;			// idle state
    Connection::finish();	// finish this transfer
}


void HTTPProtocol::HTTPConnection::fail(bool forceDrop)
{
    if (forceDrop)
        retain(false);		// drop the Connection
    else
        chooseRetain();		// perhaps keep it
    Connection::fail();		// fail this transfer
}


bool HTTPProtocol::HTTPConnection::validate()
{
    assert(state == idle);
    tickle();	// may change state
    return state == idle;
}


void HTTPProtocol::HTTPConnection::chooseRetain()
{
    // figure out whether to stay alive
    retain(strcasecmp(headers().find("Connection", "Keep"), "Close"));
    //@@@ need to handle the HTTP/1.0 case    
}


//
// Transfer objects
//
HTTPProtocol::HTTPTransfer::HTTPTransfer(Protocol &proto,
    const Target &tgt, Operation operation, IPPort defaultPort)
    : Transfer(proto, tgt, operation, defaultPort),
      mResultClass(unclassifiedFailure)
{
}

void HTTPProtocol::HTTPTransfer::start()
{
    // HTTP servers can serve both proxy requests and direct requests,
    // and can be pooled based on that fact. Use proxy==target here.
    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);
}


//
// This lower-level request startup function can be called directly by children.
//
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());
}


//
// Determine whether we should use the proxy form of HTTP headers.
// By default, this is true iff we are used by a proxy Protocol.
// However, children may override this determination.
//
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;	// preclassified
            unsigned int code = httpResponseCode();
            if (code == 401 || code == 407 || code == 305)	// auth or proxy auth required
                return authorizationFailure;
            else if (code / 100 == 3)			// redirect (interpreted as success)
                return success;
            else if (code / 100 == 2)			// success codes
                return success;
            else	// when in doubt, blame the remote end :-)
                return remoteFailure;
        }
    default:
        assert(false);
        return localFailure;
    }
}


void HTTPProtocol::HTTPTransfer::fail(ResultClass why, OSStatus how)
{
    mResultClass = why;
    Error::throwMe(how);
}


//
// Manage the HTTP version of a HeaderMap
//
void HTTPProtocol::HTTPHeaderMap::merge(string key, string &old, string newValue)
{
    // duplicates must be CSV type; concatenate (RFC 2616; section 4.2)
    old = old + ", " + newValue;
}


}	// end namespace Network
}	// end namespace Security