]> git.saurik.com Git - apple/security.git/blobdiff - Network/http-protocol.cpp
Security-222.tar.gz
[apple/security.git] / Network / http-protocol.cpp
diff --git a/Network/http-protocol.cpp b/Network/http-protocol.cpp
deleted file mode 100644 (file)
index 34450cf..0000000
+++ /dev/null
@@ -1,523 +0,0 @@
-/*
- * 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