/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * $Id: BinFileInputStream.cpp 553903 2007-07-06 14:43:42Z amassari $
 */


// ---------------------------------------------------------------------------
//  Includes
// ---------------------------------------------------------------------------

#if HAVE_CONFIG_H
#  include <config.h>
#endif

#include <stdlib.h>
#include <string.h>

#include <xercesc/util/NetAccessors/BinHTTPInputStreamCommon.hpp>

#include <xercesc/util/XMLString.hpp>
#include <xercesc/util/XMLExceptMsgs.hpp>
#include <xercesc/util/Janitor.hpp>
#include <xercesc/util/TransService.hpp>
#include <xercesc/util/PlatformUtils.hpp>
#include <xercesc/util/Base64.hpp>

XERCES_CPP_NAMESPACE_BEGIN

BinHTTPInputStreamCommon::BinHTTPInputStreamCommon(MemoryManager *manager)
      : fBytesProcessed(0)
      , fBuffer(1023, manager)
	  , fBufferPos(0)
      , fContentType(0)
	  , fEncoding(0)
      , fMemoryManager(manager)
{
}


BinHTTPInputStreamCommon::~BinHTTPInputStreamCommon()
{
    if(fContentType) fMemoryManager->deallocate(fContentType);
    if(fEncoding) fMemoryManager->deallocate(fEncoding);
}

static const char *CRLF = "\r\n";

void BinHTTPInputStreamCommon::createHTTPRequest(const XMLURL &urlSource, const XMLNetHTTPInfo *httpInfo, CharBuffer &buffer)
{
    static const char *GET = "GET ";
    static const char *PUT = "PUT ";
    static const char *POST = "POST ";
    static const char *HTTP10 = " HTTP/1.0\r\n";
    static const char *HOST = "Host: ";
    static const char *AUTHORIZATION = "Authorization: Basic ";
    static const char *COLON = ":";

    XMLTransService::Codes failReason;
    const XMLSize_t blockSize = 2048;

    XMLTranscoder* trans = XMLPlatformUtils::fgTransService->makeNewTranscoderFor("ISO8859-1", failReason, blockSize, fMemoryManager);
    Janitor<XMLTranscoder> janTrans(trans);

    TranscodeToStr hostName(urlSource.getHost(), trans, fMemoryManager);
    TranscodeToStr path(urlSource.getPath(), trans, fMemoryManager);
    TranscodeToStr fragment(urlSource.getFragment(), trans, fMemoryManager);
    TranscodeToStr query(urlSource.getQuery(), trans, fMemoryManager);

    // Build up the http GET command to send to the server.
    // To do:  We should really support http 1.1.  This implementation
    //         is weak.
    if(httpInfo) {
        switch(httpInfo->fHTTPMethod) {
        case XMLNetHTTPInfo::GET:   buffer.append(GET); break;
        case XMLNetHTTPInfo::PUT:   buffer.append(PUT); break;
        case XMLNetHTTPInfo::POST:  buffer.append(POST); break;
        }
    }
    else {
        buffer.append(GET);
    }

    if(path.str() != 0) {
        buffer.append((char*)path.str());
    }
    else {
        buffer.append("/");
    }

    if(query.str() != 0) {
        buffer.append("?");
        buffer.append((char*)query.str());
    }

    if(fragment.str() != 0) {
        buffer.append((char*)fragment.str());
    }
    buffer.append(HTTP10);

    buffer.append(HOST);
    buffer.append((char*)hostName.str());
    if(urlSource.getPortNum() != 80)
    {
        buffer.append(COLON);
        buffer.appendDecimalNumber(urlSource.getPortNum());
    }
    buffer.append(CRLF);

    const XMLCh *username = urlSource.getUser();
    const XMLCh *password = urlSource.getPassword();
    if(username && password) {
        XMLBuffer userPassBuf(256, fMemoryManager);
        userPassBuf.append(username);
        userPassBuf.append(chColon);
        userPassBuf.append(password);

        TranscodeToStr userPass(userPassBuf.getRawBuffer(), trans, fMemoryManager);

        XMLSize_t len;
        XMLByte* encodedData = Base64::encode(userPass.str(), userPass.length(), &len, fMemoryManager);
        ArrayJanitor<XMLByte> janBuf2(encodedData, fMemoryManager);

        if(encodedData) {
            // HTTP doesn't want the 0x0A separating the data in chunks of 76 chars per line
            XMLByte* authData = (XMLByte*)fMemoryManager->allocate((len+1)*sizeof(XMLByte));
            ArrayJanitor<XMLByte> janBuf(authData, fMemoryManager);
            XMLByte *cursor = authData;
            for(XMLSize_t i = 0; i < len; ++i)
                if(encodedData[i] != chLF)
                    *cursor++ = encodedData[i];
            *cursor++ = 0;
            buffer.append(AUTHORIZATION);
            buffer.append((char*)authData);
            buffer.append(CRLF);
        }
    }

    if(httpInfo && httpInfo->fHeaders)
        buffer.append(httpInfo->fHeaders, httpInfo->fHeadersLen);

    buffer.append(CRLF);
}

XMLCh *BinHTTPInputStreamCommon::findHeader(const char *name)
{
    XMLSize_t len = strlen(name);

    char *p = strstr(fBuffer.getRawBuffer(), name);
    while(p != 0) {
        if(*(p - 1) == '\n' &&
            *(p + len) == ':' &&
            *(p + len + 1) == ' ') {

            p += len + 2;

            char *endP = strstr(p, CRLF);
            if(endP == 0) {
                for(endP = p; *endP != 0; ++endP) ;
            }

            // Transcode from iso-8859-1
            TranscodeFromStr value((XMLByte*)p, endP - p, "ISO8859-1", fMemoryManager);
            return value.adopt();
        }

        p = strstr(p + 1, name);
    }

    return 0;
}

int BinHTTPInputStreamCommon::sendRequest(const XMLURL &url, const XMLNetHTTPInfo *httpInfo)
{
    //
    //  Constants in ASCII to send/check in the HTTP request/response
    //

    static const char *CRLF2X = "\r\n\r\n";
    static const char *LF2X = "\n\n";

    // The port is open and ready to go.
    // Build up the http GET command to send to the server.
    CharBuffer requestBuffer(1023, fMemoryManager);
    createHTTPRequest(url, httpInfo, requestBuffer);

    // Send the http request
    if(!send(requestBuffer.getRawBuffer(), requestBuffer.getLen())) {
        ThrowXMLwithMemMgr1(NetAccessorException,
                            XMLExcepts::NetAcc_WriteSocket, url.getURLText(), fMemoryManager);
    }

    if(httpInfo && httpInfo->fPayload) {
        if(!send(httpInfo->fPayload, httpInfo->fPayloadLen)) {
            ThrowXMLwithMemMgr1(NetAccessorException,
                                XMLExcepts::NetAcc_WriteSocket, url.getURLText(), fMemoryManager);
        }
    }

    //
    // get the response, check the http header for errors from the server.
    //
    char tmpBuf[1024];
    int ret;

    fBuffer.reset();
    while(true) {
        ret = receive(tmpBuf, sizeof(tmpBuf));
        if(ret == -1) {
            ThrowXMLwithMemMgr1(NetAccessorException, XMLExcepts::NetAcc_ReadSocket, url.getURLText(), fMemoryManager);
        }

        // connection closed
        if(ret == 0)
            break;

        fBuffer.append(tmpBuf, ret);

        fBufferPos = strstr(fBuffer.getRawBuffer(), CRLF2X);
        if(fBufferPos != 0) {
            fBufferPos += 4;
            *(fBufferPos - 2) = 0;
            break;
        }

        fBufferPos = strstr(fBuffer.getRawBuffer(), LF2X);
        if(fBufferPos != 0) {
            fBufferPos += 2;
            *(fBufferPos - 1) = 0;
            break;
        }
    }

    // Parse the response status
    char *p = strstr(fBuffer.getRawBuffer(), "HTTP");
    if(p == 0) {
        ThrowXMLwithMemMgr1(NetAccessorException, XMLExcepts::NetAcc_ReadSocket, url.getURLText(), fMemoryManager);
    }

    p = strchr(p, chSpace);
    if(p == 0) {
        ThrowXMLwithMemMgr1(NetAccessorException, XMLExcepts::NetAcc_ReadSocket, url.getURLText(), fMemoryManager);
    }

    return atoi(p);
}

const XMLCh *BinHTTPInputStreamCommon::getContentType() const
{
    if(fContentType == 0) {
        // mutable
        const_cast<BinHTTPInputStreamCommon*>(this)->fContentType =
        const_cast<BinHTTPInputStreamCommon*>(this)->findHeader("Content-Type");
    }
    return fContentType;
}

const XMLCh *BinHTTPInputStreamCommon::getEncoding() const
{
	if(fEncoding == 0) {
		const XMLCh* contentTypeHeader = getContentType();
		if(contentTypeHeader)
		{
			const XMLCh szCharsetEquals[] = {chLatin_c, chLatin_h, chLatin_a, chLatin_r, chLatin_s, chLatin_e, chLatin_t, chEqual, chNull };

			BaseRefVectorOf<XMLCh>* tokens=XMLString::tokenizeString(contentTypeHeader, chSemiColon, fMemoryManager);
			for(XMLSize_t i=0;i<tokens->size();i++)
			{
				XMLString::removeWS(tokens->elementAt(i), fMemoryManager);
				if(XMLString::startsWithI(tokens->elementAt(i), szCharsetEquals))
				{
					// mutable
					const XMLCh* encodingName=tokens->elementAt(i)+XMLString::stringLen(szCharsetEquals);
					const_cast<BinHTTPInputStreamCommon*>(this)->fEncoding = XMLString::replicate(encodingName, fMemoryManager);
					break;
				}
			}
			// if the charset=value entry was not present, check if we should use a default value
			if(fEncoding==0 && tokens->size()>0)
			{
				const XMLCh szTextSlash[] = { chLatin_t, chLatin_e, chLatin_x, chLatin_t, chForwardSlash, chNull };
				const XMLCh szXml[] = {chLatin_x, chLatin_m, chLatin_l, chNull };
				const XMLCh szXmlDash[] = {chLatin_x, chLatin_m, chLatin_l, chDash, chNull };

				XMLBuffer contentType(XMLString::stringLen(contentTypeHeader), fMemoryManager);
				contentType.set(tokens->elementAt(0));

				XMLCh* strType = contentType.getRawBuffer();
				XMLString::removeWS(strType, fMemoryManager);
				if(XMLString::startsWithI(strType, szTextSlash))
				{
					// text/* has a default encoding of iso-8859-1
					
					// text/xml, text/xml-external-parsed-entity, or a subtype like text/AnythingAtAll+xml 
					// has a default encoding of us-ascii
					XMLCh* subType = strType+XMLString::stringLen(szTextSlash);

					BaseRefVectorOf<XMLCh>* tokens=XMLString::tokenizeString(subType, chPlus, fMemoryManager);
					for(XMLSize_t i=0;i<tokens->size();i++)
					{
						XMLCh* part=tokens->elementAt(i);
						if(XMLString::compareIStringASCII(part, szXml)==0 || XMLString::startsWithI(part, szXmlDash))
						{
							const_cast<BinHTTPInputStreamCommon*>(this)->fEncoding = XMLString::replicate(XMLUni::fgUSASCIIEncodingString, fMemoryManager);
							break;
						}
					}
					if(fEncoding==0)
						const_cast<BinHTTPInputStreamCommon*>(this)->fEncoding = XMLString::replicate(XMLUni::fgISO88591EncodingString, fMemoryManager);
					delete tokens;
				}
			}
			delete tokens;
		}
	}
    return fEncoding;
}

XMLSize_t BinHTTPInputStreamCommon::readBytes(XMLByte* const    toFill,
                                              const XMLSize_t    maxToRead)
{
    XMLSize_t len = fBuffer.getRawBuffer() + fBuffer.getLen() - fBufferPos;
    if(len > 0)
    {
        // If there's any data left over in the buffer into which we first
        //   read from the server (to get the http header), return that.
        if (len > maxToRead)
            len = maxToRead;
        memcpy(toFill, fBufferPos, len);
        fBufferPos += len;
    }
    else
    {
        // There was no data in the local buffer.
        // Read some from the socket, straight into our caller's buffer.
        //
        int cbRead = receive((char *)toFill, maxToRead);
        if (cbRead == -1)
        {
            ThrowXMLwithMemMgr(NetAccessorException, XMLExcepts::NetAcc_ReadSocket, fMemoryManager);
        }
        len = cbRead;
    }

    fBytesProcessed += len;
    return len;
}

XERCES_CPP_NAMESPACE_END
