blob: 82c37bbb8f37b87bc65f6dfe16e88b93480df2b7 [file] [log] [blame]
/**
* 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.
*/
/*
* XSEC
*
* XSECBinHTTPURIInputStream := Re-implementation of the Xerces
* BinHTTPURLInputStream. Allows us to make
* some small changes to support the requirements
* of XMLDSIG (notably re-directs)
*
* NOTE: Much code taken from Xerces, and the cross platform interfacing is
* no-where near as nice.
*
* $Id$
*
*/
#include <xsec/utils/winutils/XSECBinHTTPURIInputStream.hpp>
#define _WINSOCKAPI_
#define INCL_WINSOCK_API_TYPEDEFS 1
#include <winsock2.h>
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <xercesc/util/PlatformUtils.hpp>
#include <xercesc/util/XMLNetAccessor.hpp>
#include <xercesc/util/XMLString.hpp>
#include <xercesc/util/XMLExceptMsgs.hpp>
#include <xercesc/util/XMLUniDefs.hpp>
XERCES_CPP_NAMESPACE_USE
#include <xsec/utils/winutils/XSECBinHTTPURIInputStream.hpp>
#include <xsec/framework/XSECError.hpp>
#include "../../utils/XSECAutoPtr.hpp"
static HMODULE gWinsockLib = NULL;
static LPFN_GETHOSTBYNAME gWSgethostbyname = NULL;
static LPFN_INET_ADDR gWSinet_addr = NULL;
static LPFN_GETHOSTBYADDR gWSgethostbyaddr = NULL;
static LPFN_HTONS gWShtons = NULL;
static LPFN_SOCKET gWSsocket = NULL;
static LPFN_CONNECT gWSconnect = NULL;
static LPFN_SEND gWSsend = NULL;
static LPFN_RECV gWSrecv = NULL;
static LPFN_SHUTDOWN gWSshutdown = NULL;
static LPFN_CLOSESOCKET gWSclosesocket = NULL;
static LPFN_WSACLEANUP gWSACleanup = NULL;
bool XSECBinHTTPURIInputStream::fInitialized = false;
XMLMutex* XSECBinHTTPURIInputStream::fInitMutex = 0;
// Ported from Xerces 2.x, now missing on 3.x, so I'm inlining it.
static void* compareAndSwap(void** toFill, const void* const newValue, const void* const toCompare) {
#if defined _WIN64
return ::InterlockedCompareExchangePointer(toFill, (void*)newValue, (void*)toCompare);
#else
void* result;
__asm
{
mov eax, toCompare;
mov ebx, newValue;
mov ecx, toFill
lock cmpxchg [ecx], ebx;
mov result, eax;
}
return result;
#endif
}
void XSECBinHTTPURIInputStream::Initialize() {
//
// Initialize the WinSock library here.
//
WORD wVersionRequested;
WSADATA wsaData;
LPFN_WSASTARTUP startup = NULL;
if(gWinsockLib == NULL) {
gWinsockLib = LoadLibrary(_T("WSOCK32"));
if(gWinsockLib == NULL) {
ThrowXML(NetAccessorException, XMLExcepts::NetAcc_InitFailed);
}
else {
startup = (LPFN_WSASTARTUP) GetProcAddress(gWinsockLib,"WSAStartup");
gWSACleanup = (LPFN_WSACLEANUP) GetProcAddress(gWinsockLib,"WSACleanup");
gWSgethostbyname = (LPFN_GETHOSTBYNAME) GetProcAddress(gWinsockLib,"gethostbyname");
gWSinet_addr = (LPFN_INET_ADDR) GetProcAddress(gWinsockLib,"inet_addr");
gWSgethostbyaddr = (LPFN_GETHOSTBYADDR) GetProcAddress(gWinsockLib,"gethostbyaddr");
gWShtons = (LPFN_HTONS) GetProcAddress(gWinsockLib,"htons");
gWSsocket = (LPFN_SOCKET) GetProcAddress(gWinsockLib,"socket");
gWSconnect = (LPFN_CONNECT) GetProcAddress(gWinsockLib,"connect");
gWSsend = (LPFN_SEND) GetProcAddress(gWinsockLib,"send");
gWSrecv = (LPFN_RECV) GetProcAddress(gWinsockLib,"recv");
gWSshutdown = (LPFN_SHUTDOWN) GetProcAddress(gWinsockLib,"shutdown");
gWSclosesocket = (LPFN_CLOSESOCKET) GetProcAddress(gWinsockLib,"closesocket");
if(startup == NULL ||
gWSACleanup == NULL ||
gWSgethostbyname == NULL ||
gWSinet_addr == NULL ||
gWSgethostbyaddr == NULL ||
gWShtons == NULL ||
gWSsocket == NULL ||
gWSconnect == NULL ||
gWSsend == NULL ||
gWSrecv == NULL ||
gWSshutdown == NULL ||
gWSclosesocket == NULL)
{
gWSACleanup = NULL;
Cleanup();
ThrowXML(NetAccessorException, XMLExcepts::NetAcc_InitFailed);
}
}
}
wVersionRequested = MAKEWORD( 2, 2 );
int err = (*startup)(wVersionRequested, &wsaData);
if (err != 0)
{
// Call WSAGetLastError() to get the last error.
ThrowXML(NetAccessorException, XMLExcepts::NetAcc_InitFailed);
}
fInitialized = true;
}
void XSECBinHTTPURIInputStream::Cleanup() {
if(fInitialized)
{
if(gWSACleanup) (*gWSACleanup)();
gWSACleanup = NULL;
FreeLibrary(gWinsockLib);
gWinsockLib = NULL;
gWSgethostbyname = NULL;
gWSinet_addr = NULL;
gWSgethostbyaddr = NULL;
gWShtons = NULL;
gWSsocket = NULL;
gWSconnect = NULL;
gWSsend = NULL;
gWSrecv = NULL;
gWSshutdown = NULL;
gWSclosesocket = NULL;
fInitialized = false;
delete fInitMutex;
fInitMutex = 0;
}
}
hostent* XSECBinHTTPURIInputStream::gethostbyname(const char* name)
{
return (*gWSgethostbyname)(name);
}
unsigned long XSECBinHTTPURIInputStream::inet_addr(const char* cp)
{
return (*gWSinet_addr)(cp);
}
hostent* XSECBinHTTPURIInputStream::gethostbyaddr(const char* addr,int len,int type)
{
return (*gWSgethostbyaddr)(addr,len,type);
}
unsigned short XSECBinHTTPURIInputStream::htons(unsigned short hostshort)
{
return (*gWShtons)(hostshort);
}
unsigned short XSECBinHTTPURIInputStream::socket(int af,int type,int protocol)
{
return (unsigned short) (*gWSsocket)(af,type,protocol);
}
int XSECBinHTTPURIInputStream::connect(unsigned short s,const sockaddr* name,int namelen)
{
return (*gWSconnect)(s,name,namelen);
}
int XSECBinHTTPURIInputStream::send(unsigned short s,const char* buf,int len,int flags)
{
return (*gWSsend)(s,buf,len,flags);
}
int XSECBinHTTPURIInputStream::recv(unsigned short s,char* buf,int len,int flags)
{
return (*gWSrecv)(s,buf,len,flags);
}
int XSECBinHTTPURIInputStream::shutdown(unsigned int s,int how)
{
return (*gWSshutdown)(s,how);
}
int XSECBinHTTPURIInputStream::closesocket(unsigned int socket)
{
return (*gWSclosesocket)(socket);
}
unsigned int XSECBinHTTPURIInputStream::getSocketHandle(const XMLUri& urlSource) {
//
// Pull all of the parts of the URL out of th urlSource object, and transcode them
// and transcode them back to ASCII.
//
const XMLCh* hostName = urlSource.getHost();
XSECAutoPtrChar hostNameAsCharStar(hostName);
const XMLCh* path = urlSource.getPath();
XSECAutoPtrChar pathAsCharStar(path);
const XMLCh* fragment = urlSource.getFragment();
XSECAutoPtrChar fragmentAsCharStar(fragment);
const XMLCh* query = urlSource.getQueryString();
XSECAutoPtrChar queryAsCharStar(query);
unsigned short portNumber = (unsigned short) urlSource.getPort();
// If no number is set, go with port 80
if (portNumber == USHRT_MAX)
portNumber = 80;
//
// Set up a socket.
//
struct hostent* hostEntPtr = 0;
struct sockaddr_in sa;
if ((hostEntPtr = gethostbyname(hostNameAsCharStar.get())) == NULL)
{
unsigned long numAddress = inet_addr(hostNameAsCharStar.get());
if (numAddress == INADDR_NONE)
{
// Call WSAGetLastError() to get the error number.
throw XSECException(XSECException::HTTPURIInputStreamError,
"Error reported resolving IP address");
}
if ((hostEntPtr =
gethostbyaddr((const char *) &numAddress,
sizeof(unsigned long), AF_INET)) == NULL)
{
// Call WSAGetLastError() to get the error number.
throw XSECException(XSECException::HTTPURIInputStreamError,
"Error reported resolving IP address");
}
}
memcpy((void *) &sa.sin_addr,
(const void *) hostEntPtr->h_addr, hostEntPtr->h_length);
sa.sin_family = hostEntPtr->h_addrtype;
sa.sin_port = htons(portNumber);
SOCKET s = socket(hostEntPtr->h_addrtype, SOCK_STREAM, 0);
if (s == INVALID_SOCKET)
{
// Call WSAGetLastError() to get the error number.
throw XSECException(XSECException::HTTPURIInputStreamError,
"Error reported creating socket");
}
if (connect((unsigned short) s, (struct sockaddr *) &sa, sizeof(sa)) == SOCKET_ERROR)
{
// Call WSAGetLastError() to get the error number.
throw XSECException(XSECException::HTTPURIInputStreamError,
"Error reported connecting to socket");
}
// Set a flag so we know that the headers have not been read yet.
bool fHeaderRead = false;
// The port is open and ready to go.
// Build up the http GET command to send to the server.
// To do: We should really support http 1.1. This implementation
// is weak.
memset(fBuffer, 0, sizeof(fBuffer));
strcpy(fBuffer, "GET ");
strcat(fBuffer, pathAsCharStar.get());
if (queryAsCharStar.get() != 0)
{
// Tack on a ? before the fragment
strcat(fBuffer,"?");
strcat(fBuffer, queryAsCharStar.get());
}
if (fragmentAsCharStar.get() != 0)
{
strcat(fBuffer, fragmentAsCharStar.get());
}
strcat(fBuffer, " HTTP/1.0\r\n");
strcat(fBuffer, "Host: ");
strcat(fBuffer, hostNameAsCharStar.get());
if (portNumber != 80)
{
strcat(fBuffer, ":");
int i = (int) strlen(fBuffer);
_itoa(portNumber, fBuffer+i, 10);
}
strcat(fBuffer, "\r\n\r\n");
// Send the http request
int lent = (int) strlen(fBuffer);
int aLent = 0;
if ((aLent = send((unsigned short) s, fBuffer, lent, 0)) != lent)
{
// Call WSAGetLastError() to get the error number.
throw XSECException(XSECException::HTTPURIInputStreamError,
"Error reported writing to socket");
}
//
// get the response, check the http header for errors from the server.
//
memset(fBuffer, 0, sizeof(fBuffer));
aLent = recv((unsigned short) s, fBuffer, sizeof(fBuffer)-1, 0);
if (aLent == SOCKET_ERROR || aLent == 0)
{
// Call WSAGetLastError() to get the error number.
throw XSECException(XSECException::HTTPURIInputStreamError,
"Error reported reading socket");
}
fBufferEnd = fBuffer+aLent;
*fBufferEnd = 0;
do {
// Find the break between the returned http header and any data.
// (Delimited by a blank line)
// Hang on to any data for use by the first read from this XSECBinHTTPURIInputStream.
//
fBufferPos = strstr(fBuffer, "\r\n\r\n");
if (fBufferPos != 0)
{
fBufferPos += 4;
*(fBufferPos-2) = 0;
fHeaderRead = true;
}
else
{
fBufferPos = strstr(fBuffer, "\n\n");
if (fBufferPos != 0)
{
fBufferPos += 2;
*(fBufferPos-1) = 0;
fHeaderRead = true;
}
else
{
//
// Header is not yet read, do another recv() to get more data...
aLent = (unsigned int) recv((unsigned short) s, fBufferEnd, ((int) sizeof(fBuffer) - 1) - (int) (fBufferEnd - fBuffer), 0);
if (aLent == SOCKET_ERROR || aLent == 0)
{
// Call WSAGetLastError() to get the error number.
throw XSECException(XSECException::HTTPURIInputStreamError,
"Error reported reading socket");
}
fBufferEnd = fBufferEnd + aLent;
*fBufferEnd = 0;
}
}
} while(fHeaderRead == false);
// Make sure the header includes an HTTP 200 OK response.
//
char *p = strstr(fBuffer, "HTTP");
if (p == 0)
{
throw XSECException(XSECException::HTTPURIInputStreamError,
"Error reported reading socket");
}
p = strchr(p, ' ');
if (p == 0)
{
throw XSECException(XSECException::HTTPURIInputStreamError,
"Error reported reading socket");
}
int httpResponse = atoi(p);
// Check for redirect or permanently moved
if (httpResponse == 302 || httpResponse == 301)
{
//Once grows, should use a switch
char redirectBuf[256];
int q;
// Find the "Location:" string
p = strstr(p, "Location:");
if (p == 0)
{
throw XSECException(XSECException::HTTPURIInputStreamError,
"Error reported reading socket");
}
p = strchr(p, ' ');
if (p == 0)
{
throw XSECException(XSECException::HTTPURIInputStreamError,
"Error reported reading socket");
}
// Now read
p++;
for (q=0; q < 255 && p[q] != '\r' && p[q] !='\n'; ++q)
redirectBuf[q] = p[q];
redirectBuf[q] = '\0';
// Try to find this location
XSECAutoPtrXMLCh redirectBufTrans(redirectBuf);
return getSocketHandle(XMLUri(redirectBufTrans.get()));
}
else if (httpResponse != 200)
{
// Most likely a 404 Not Found error.
// Should recognize and handle the forwarding responses.
//
throw XSECException(XSECException::HTTPURIInputStreamError,
"Unknown HTTP response received");
}
return (unsigned int) s;
}
XSECBinHTTPURIInputStream::XSECBinHTTPURIInputStream(const XMLUri& urlSource)
: fSocketHandle(0)
, fBytesProcessed(0)
{
if(!fInitialized)
{
if (!fInitMutex)
{
XMLMutex* tmpMutex = new XMLMutex;
if (compareAndSwap((void**)&fInitMutex, tmpMutex, 0))
{
// Someone beat us to it, so let's clean up ours
delete tmpMutex;
}
}
XMLMutexLock lock(fInitMutex);
if (!fInitialized)
{
Initialize();
}
}
fSocketHandle = getSocketHandle(urlSource);
}
void XSECBinHTTPURIInputStream::ExternalInitialize(void) {
if(!fInitialized)
{
if (!fInitMutex)
{
XMLMutex* tmpMutex = new XMLMutex;
if (compareAndSwap((void**)&fInitMutex, tmpMutex, 0))
{
// Someone beat us to it, so let's clean up ours
delete tmpMutex;
}
}
XMLMutexLock lock(fInitMutex);
if (!fInitialized)
{
Initialize();
}
}
}
XSECBinHTTPURIInputStream::~XSECBinHTTPURIInputStream()
{
shutdown(fSocketHandle, SD_BOTH);
closesocket(fSocketHandle);
}
//
// readBytes
//
xsecsize_t XSECBinHTTPURIInputStream::readBytes(XMLByte* const toFill
, const xsecsize_t maxToRead)
{
xsecsize_t len = (xsecsize_t) (fBufferEnd - 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.
//
len = recv(fSocketHandle, (char *) toFill, maxToRead, 0);
if (len == SOCKET_ERROR)
{
// Call WSAGetLastError() to get the error number.
throw XSECException(XSECException::HTTPURIInputStreamError,
"Error reported reading socket");
}
}
fBytesProcessed += len;
return len;
}
#ifdef XSEC_XERCES_INPUTSTREAM_HAS_CONTENTTYPE
const XMLCh* XSECBinHTTPURIInputStream::getContentType() const {
return NULL;
}
#endif