/// \file /// \brief Contains HTTPConnection, used to communicate with web servers /// /// This file is part of RakNet Copyright 2008 Kevin Jenkins. /// /// Usage of RakNet is subject to the appropriate license agreement. /// Creative Commons Licensees are subject to the /// license found at /// http://creativecommons.org/licenses/by-nc/2.5/ /// Single application licensees are subject to the license found at /// http://www.jenkinssoftware.com/SingleApplicationLicense.html /// Custom license users are subject to the terms therein. /// GPL license users are subject to the GNU General Public /// License as published by the Free /// Software Foundation; either version 2 of the License, or (at your /// option) any later version. #include "TCPInterface.h" #include "HTTPConnection.h" #include "RakSleep.h" #include "RakString.h" #include "RakAssert.h" #include #include #include #include using namespace RakNet; HTTPConnection::HTTPConnection(TCPInterface& _tcp, const char *_host, unsigned short _port) : tcp(_tcp), host(_host), port(_port), state(RAK_HTTP_INITIAL) {} void HTTPConnection::Post(const char *remote_path, const char *data, const char *_contentType) { if(state == RAK_HTTP_IDLE) state = RAK_HTTP_ESTABLISHED; else if(state == RAK_HTTP_INITIAL) state = RAK_HTTP_STARTING; else return; outgoing = data; path = remote_path; contentType=_contentType; incoming.Clear(); } bool HTTPConnection::HasBadResponse(int *code, RakNet::RakString *data) { if(badResponses.IsEmpty()) return false; if (code) *code = badResponses.Peek().code; if (data) *data = badResponses.Pop().data; return true; } bool HTTPConnection::InList(StatusCheckFunction func) { SystemAddress address = (tcp.*func)(); if(address == UNASSIGNED_SYSTEM_ADDRESS) return false; server = address; return true; } void HTTPConnection::Update(void) { if(InList(&TCPInterface::HasCompletedConnectionAttempt)) state = RAK_HTTP_ESTABLISHED; if(InList(&TCPInterface::HasFailedConnectionAttempt)) state = RAK_HTTP_STARTING; // retry // normally, HTTP servers close the stream after sending data if(InList(&TCPInterface::HasLostConnection)) state = RAK_HTTP_INITIAL; if(state == RAK_HTTP_STARTING) { server = tcp.Connect(host, port, false); state = RAK_HTTP_CONNECTING; } if(state == RAK_HTTP_ESTABLISHED) { RakString request("POST %s HTTP/1.0\r\n" "Host: %s\r\n" "Content-Type: %s\r\n" "Content-Length: %u\r\n" "\r\n" "%s", path.C_String(), host.C_String(), contentType.C_String(), (unsigned) outgoing.GetLength(), outgoing.C_String()); tcp.Send(request, (unsigned int) strlen(request), server); state = RAK_HTTP_REQUEST_SENT; } } RakString HTTPConnection::Read(void) { const char *start_of_body = strstr(incoming, "\r\n\r\n"); if(! start_of_body) { badResponses.Push(BadResponse(incoming, HTTPConnection::NoBody)); return RakString(); } return RakString(start_of_body + 4); } SystemAddress HTTPConnection::GetServerAddress(void) const { return server; } bool HTTPConnection::ProcessFinalTCPPacket(Packet *packet) { RakAssert(packet); // read all the packets possible if(packet->systemAddress == server) { if(incoming.GetLength() == 0) { int response_code = atoi((char *)packet->data + strlen("HTTP/1.0 ")); if(response_code > 299) badResponses.Push(BadResponse(packet->data, response_code)); } incoming += (char *)packet->data; // safe because TCPInterface Null-terminates assert(strlen((char *)packet->data) == packet->length); // otherwise it contains Null bytes const char *start_of_body = strstr(incoming, "\r\n\r\n"); // besides having the server close the connection, they may // provide a length header and supply that many bytes if(start_of_body && state == RAK_HTTP_REQUEST_SENT) { if (strstr((const char*) packet->data, "\r\nConnection: close\r\n")) { state = RAK_HTTP_IDLE; } else { long length_of_headers = (long)(start_of_body + 4 - incoming.C_String()); const char *length_header = strstr(incoming, "\r\nLength: "); if(length_header) { long length = atol(length_header + 10) + length_of_headers; if((long) incoming.GetLength() >= length) state = RAK_HTTP_IDLE; } } } } return IsBusy()==false; } bool HTTPConnection::IsBusy(void) const { return state != RAK_HTTP_IDLE && state != RAK_HTTP_INITIAL; } int HTTPConnection::GetState(void) const { return state; } HTTPConnection::~HTTPConnection(void) { tcp.CloseConnection(server); }