/* [<][>][^][v][top][bottom][index][help] */
DEFINITIONS
This source file includes following definitions.
- _close
- _close
- clearHeader
- processHeaderFields
- parseStatus
- processClientRequest
- processGetRequest
- processPostRequest
- processPutRequest
- processDeleteRequest
- processConnectRequest
- processOptionsRequest
- processHeadRequest
- processTraceRequest
- getContentLength
- checkRequestFields
- checkEntityFields
- checkGeneralFields
- getFieldItem
- startHeader
- formatHeader
- formatCommon
- formatHeader
- formatHeader
- formatDate
- formatServer
- formatServer
- formatContentLength
- formatContentLength
- formatContentType
- formatContentType
- formatLastModified
- formatEchoResponse
- formatEchoResponse
- formatRequest
- extractCommand
- sendMsg
- sendMsg
- sendMsg
- recvChunked
- recvMsg
- recvMsg
- dump
// http.cpp: HyperText Transport Protocol handler for Cygnal, for Gnash.
//
// Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010,
// 2011 Free Software Foundation, Inc
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
//
#include <boost/thread/mutex.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/shared_array.hpp>
#include <boost/scoped_array.hpp>
#include <boost/tokenizer.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/date_time/gregorian/gregorian.hpp>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string>
#include <iostream>
#include <cstring>
#include <sstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <algorithm>
#include "GnashSystemIOHeaders.h" // read()
#include "http.h"
#include "amf.h"
#include "element.h"
#include "cque.h"
#include "log.h"
#include "network.h"
// #include "handler.h"
#include "utility.h"
#include "buffer.h"
#include "diskstream.h"
#include "cache.h"
// Not POSIX, so best not rely on it if possible.
#ifndef PATH_MAX
# define PATH_MAX 1024
#endif
#if defined(_WIN32) || defined(WIN32)
# define __PRETTY_FUNCTION__ __FUNCDNAME__
# include <winsock2.h>
# include <direct.h>
#else
# include <unistd.h>
# include <sys/param.h>
#endif
using std::string;
static boost::mutex stl_mutex;
namespace gnash
{
// extern map<int, Handler *> handlers;
// FIXME, this seems too small to me. --gnu
static const int readsize = 1024;
static Cache& cache = Cache::getDefaultInstance();
HTTP::HTTP()
: _filetype(DiskStream::FILETYPE_HTML),
_filesize(0),
_keepalive(false),
// _handler(0),
_clientid(0),
_index(0),
_max_requests(0),
_close(false)
{
// GNASH_REPORT_FUNCTION;
// struct status_codes *status = new struct status_codes;
_version.major = 0;
_version.minor = 0;
// _status_codes(CONTINUE, status);
}
#if 0
HTTP::HTTP(Handler *hand)
: _filetype(DiskStream::FILETYPE_HTML),
_filesize(0),
_keepalive(false),
_clientid(0),
_index(0),
_max_requests(0),
_close(false)
{
// GNASH_REPORT_FUNCTION;
// _handler = hand;
_version.major = 0;
_version.minor = 0;
}
#endif
HTTP::~HTTP()
{
// GNASH_REPORT_FUNCTION;
}
bool
HTTP::clearHeader()
{
// _header.str("");
_buffer.clear();
_filesize = 0;
_max_requests = 0;
return true;
}
HTTP &
HTTP::operator = (HTTP& /*obj*/)
{
GNASH_REPORT_FUNCTION;
// this = obj;
// TODO: FIXME !
return *this;
}
boost::uint8_t *
HTTP::processHeaderFields(cygnal::Buffer *buf)
{
// GNASH_REPORT_FUNCTION;
string head(reinterpret_cast<const char *>(buf->reference()), buf->size());
// The end of the header block is always followed by a blank line
string::size_type end = head.find("\r\n\r\n", 0);
// head.erase(end, buf.size()-end);
Tok t(head, Sep("\r\n"));
for (Tok::iterator i = t.begin(); i != t.end(); ++i) {
string::size_type pos = i->find(":", 0);
if (pos != string::npos) {
string name = i->substr(0, pos);
string value = i->substr(pos+2, i->size());
std::transform(name.begin(), name.end(), name.begin(),
(int(*)(int)) tolower);
std::transform(value.begin(), value.end(), value.begin(),
(int(*)(int)) tolower);
_fields[name] = value;
if (name == "keep-alive") {
_keepalive = true;
if ((value != "on") && (value != "off")) {
_max_requests = strtol(value.c_str(), NULL, 0);
// log_debug("Setting Max Requests for Keep-Alive to %d", _max_requests);
}
}
if (name == "connection") {
if (value.find("keep-alive", 0) != string::npos) {
_keepalive = true;
}
}
if (name == "content-length") {
_filesize = strtol(value.c_str(), NULL, 0);
log_debug("Setting Content Length to %d", _filesize);
}
if (name == "content-type") {
// This is the type used by flash when sending a AMF data via POST
if (value == "application/x-amf") {
// log_debug("Got AMF data in the POST request!");
_filetype = DiskStream::FILETYPE_AMF;
}
// This is the type used by wget when sending a file via POST
if (value == "application/x-www-form-urlencoded") {
// log_debug("Got file data in the POST request");
_filetype = DiskStream::FILETYPE_ENCODED;
}
log_debug("Setting Content Type to %d", _filetype);
}
// cerr << "FIXME: " << (void *)i << " : " << dec << end << endl;
} else {
const boost::uint8_t *cmd = reinterpret_cast<const boost::uint8_t *>(i->c_str());
if (extractCommand(const_cast<boost::uint8_t *>(cmd)) == HTTP::HTTP_NONE) {
break;
#if 1
} else {
log_debug("Got a request, parsing \"%s\"", *i);
string::size_type start = i->find(" ");
string::size_type params = i->find("?");
string::size_type pos = i->find("HTTP/");
if (pos != string::npos) {
// The version is the last field and is the protocol name
// followed by a slash, and the version number. Note that
// the version is not a double, even though it has a dot
// in it. It's actually two separate integers.
_version.major = i->at(pos+5) - '0';
_version.minor = i->at(pos+7) - '0';
// log_debug (_("Version: %d.%d"), _version.major, _version.minor);
// the filespec in the request is the middle field, deliminated
// by a space on each end.
if (params != string::npos) {
_params = i->substr(params+1, end);
_filespec = i->substr(start+1, params);
log_debug("Parameters for file: \"%s\"", _params);
} else {
_filespec = i->substr(start+1, pos-start-2);
}
log_debug("Requesting file: \"%s\"", _filespec);
// HTTP 1.1 enables persistent network connections
// by default.
if (_version.minor > 0) {
log_debug("Enabling Keep Alive by default for HTTP > 1.0");
_keepalive = true;
}
}
}
#endif
}
}
return buf->reference() + end + 4;
}
// // Parse an Echo Request message coming from the Red5 echo_test. This
// // method should only be used for testing purposes.
// vector<boost::shared_ptr<cygnal::Element > >
// HTTP::parseEchoRequest(boost::uint8_t *data, size_t size)
// {
// // GNASH_REPORT_FUNCTION;
// vector<boost::shared_ptr<cygnal::Element > > headers;
// // skip past the header bytes, we don't care about them.
// boost::uint8_t *tmpptr = data + 6;
// boost::uint16_t length;
// length = ntohs((*(boost::uint16_t *)tmpptr) & 0xffff);
// tmpptr += sizeof(boost::uint16_t);
// // Get the first name, which is a raw string, and not preceded by
// // a type byte.
// boost::shared_ptr<cygnal::Element > el1(new cygnal::Element);
// // If the length of the name field is corrupted, then we get out of
// // range quick, and corrupt memory. This is a bit of a hack, but
// // reduces memory errors caused by some of the corrupted tes cases.
// boost::uint8_t *endstr = std::find(tmpptr, tmpptr+length, '\0');
// if (endstr != tmpptr+length) {
// log_debug("Caught corrupted string! length was %d, null at %d",
// length, endstr-tmpptr);
// length = endstr-tmpptr;
// }
// el1->setName(tmpptr, length);
// tmpptr += length;
// headers.push_back(el1);
// // Get the second name, which is a raw string, and not preceded by
// // a type byte.
// length = ntohs((*(boost::uint16_t *)tmpptr) & 0xffff);
// tmpptr += sizeof(boost::uint16_t);
// boost::shared_ptr<cygnal::Element > el2(new cygnal::Element);
// // std::string name2(reinterpret_cast<const char *>(tmpptr), length);
// // el2->setName(name2.c_str(), name2.size());
// // If the length of the name field is corrupted, then we get out of
// // range quick, and corrupt memory. This is a bit of a hack, but
// // reduces memory errors caused by some of the corrupted tes cases.
// endstr = std::find(tmpptr, tmpptr+length, '\0');
// if (endstr != tmpptr+length) {
// log_debug("Caught corrupted string! length was %d, null at %d",
// length, endstr-tmpptr);
// length = endstr-tmpptr;
// }
// el2->setName(tmpptr, length);
// headers.push_back(el2);
// tmpptr += length;
// // Get the last two pieces of data, which are both AMF encoded
// // with a type byte.
// amf::AMF amf;
// boost::shared_ptr<cygnal::Element> el3 = amf.extractAMF(tmpptr, tmpptr + size);
// headers.push_back(el3);
// tmpptr += amf.totalsize();
// boost::shared_ptr<cygnal::Element> el4 = amf.extractAMF(tmpptr, tmpptr + size);
// headers.push_back(el4);
// return headers;
// }
// // format a response to the 'echo' test used for testing Gnash. This
// // is only used for testing by developers. The format appears to be
// // two strings, followed by a double, followed by the "onResult".
// cygnal::Buffer &
// HTTP::formatEchoResponse(const std::string &num, cygnal::Element &el)
// {
// // GNASH_REPORT_FUNCTION;
// boost::shared_ptr<cygnal::Buffer> data;
// cygnal::Element nel;
// if (el.getType() == cygnal::Element::TYPED_OBJECT_AMF0) {
// nel.makeTypedObject();
// string name = el.getName();
// nel.setName(name);
// if (el.propertySize()) {
// // FIXME: see about using std::reverse() instead.
// for (int i=el.propertySize()-1; i>=0; i--) {
// // for (int i=0 ; i<el.propertySize(); i++) {
// boost::shared_ptr<cygnal::Element> child = el.getProperty(i);
// nel.addProperty(child);
// }
// data = nel.encode();
// } else {
// data = el.encode();
// }
// } else {
// data = el.encode();
// }
// return formatEchoResponse(num, data->reference(), data->allocated());
// }
#if 0 // FIXME:
// Client side parsing of response message codes
boost::shared_ptr<HTTP::http_response_t>
HTTP::parseStatus(const std::string &line)
{
// GNASH_REPORT_FUNCTION;
boost::shared_ptr<http_response_t> status;
// The respnse is a number followed by the error message.
string::size_type pos = line.find(" ", 0);
if (pos != string::npos) {
status.reset(new http_response_t);
status->code = static_cast<HTTP::http_status_e>(strtol(line.substr(0, pos).c_str(), NULL, 0));
status->msg = line.substr(pos+1, line.size());
}
return status;
}
HTTP::http_method_e
HTTP::processClientRequest(int fd)
{
// GNASH_REPORT_FUNCTION;
bool result = false;
boost::shared_ptr<cygnal::Buffer> buf(_que.peek());
if (buf) {
_cmd = extractCommand(buf->reference());
switch (_cmd) {
case HTTP::HTTP_GET:
result = processGetRequest(fd);
break;
case HTTP::HTTP_POST:
result = processPostRequest(fd);
break;
case HTTP::HTTP_HEAD:
result = processHeadRequest(fd);
break;
case HTTP::HTTP_CONNECT:
result = processConnectRequest(fd);
break;
case HTTP::HTTP_TRACE:
result = processTraceRequest(fd);
break;
case HTTP::HTTP_OPTIONS:
result = processOptionsRequest(fd);
break;
case HTTP::HTTP_PUT:
result = processPutRequest(fd);
break;
case HTTP::HTTP_DELETE:
result = processDeleteRequest(fd);
break;
default:
break;
}
}
return _cmd;
}
// A GET request asks the server to send a file to the client
bool
HTTP::processGetRequest(int fd)
{
GNASH_REPORT_FUNCTION;
// boost::uint8_t buffer[readsize+1];
// const char *ptr = reinterpret_cast<const char *>(buffer);
// memset(buffer, 0, readsize+1);
// _handler->wait();
// _handler->dump();
cerr << "QUE = " << _que.size() << endl;
if (_que.size() == 0) {
return false;
}
boost::shared_ptr<cygnal::Buffer> buf(_que.pop());
// cerr << "YYYYYYY: " << (char *)buf->reference() << endl;
// cerr << hexify(buf->reference(), buf->allocated(), false) << endl;
if (buf == 0) {
// log_debug("Que empty, net connection dropped for fd #%d", getFileFd());
log_debug("Que empty, net connection dropped for fd #%d", fd);
return false;
}
clearHeader();
processHeaderFields(*buf);
string url = _docroot + _filespec;
// See if the file is in the cache and already opened.
boost::shared_ptr<DiskStream> filestream(cache.findFile(url));
if (filestream) {
log_network("FIXME: found file in cache!");
} else {
filestream.reset(new DiskStream);
// cerr << "New Filestream at 0x" << hex << filestream.get() << endl;
// cache.addFile(url, filestream); FIXME: always reload from disk for now.
// Oopen the file and read the first chunk into memory
if (filestream->open(url)) {
formatErrorResponse(HTTP::NOT_FOUND);
} else {
// Get the file size for the HTTP header
if (filestream->getFileType() == DiskStream::FILETYPE_NONE) {
formatErrorResponse(HTTP::NOT_FOUND);
} else {
cache.addPath(_filespec, filestream->getFilespec());
}
}
}
// Send the reply
cygnal::Buffer &reply = formatHeader(filestream->getFileType(),
filestream->getFileSize(),
HTTP::OK);
writeNet(fd, reply);
size_t filesize = filestream->getFileSize();
size_t bytes_read = 0;
int ret;
size_t page = 0;
if (filesize) {
#ifdef USE_STATS_CACHE
struct timespec start;
clock_gettime (CLOCK_REALTIME, &start);
#endif
size_t getbytes = 0;
if (filesize <= filestream->getPagesize()) {
getbytes = filesize;
} else {
getbytes = filestream->getPagesize();
}
if (filesize >= CACHE_LIMIT) {
do {
filestream->loadToMem(page);
ret = writeNet(fd, filestream->get(), getbytes);
if (ret <= 0) {
break;
}
bytes_read += ret;
page += filestream->getPagesize();
} while (bytes_read <= filesize);
} else {
filestream->loadToMem(filesize, 0);
ret = writeNet(fd, filestream->get(), filesize);
}
filestream->close();
#ifdef USE_STATS_CACHE
struct timespec end;
clock_gettime (CLOCK_REALTIME, &end);
double time = (end.tv_sec - start.tv_sec) + ((end.tv_nsec - start.tv_nsec)/1e9);
cerr << "File " << _filespec
<< " transferred " << filesize << " bytes in: " << fixed
<< time << " seconds for net fd #" << fd << endl;
#endif
}
log_debug("http_handler all done transferring requested file \"%s\".", _filespec);
return true;
}
// A POST request asks sends a data from the client to the server. After processing
// the header like we normally do, we then read the amount of bytes specified by
// the "content-length" field, and then write that data to disk, or decode the amf.
bool
HTTP::processPostRequest(int fd)
{
GNASH_REPORT_FUNCTION;
// cerr << "QUE1 = " << _que.size() << endl;
if (_que.size() == 0) {
return false;
}
boost::shared_ptr<cygnal::Buffer> buf(_que.pop());
if (buf == 0) {
log_debug("Que empty, net connection dropped for fd #%d", getFileFd());
return false;
}
// cerr << __FUNCTION__ << buf->allocated() << " : " << hexify(buf->reference(), buf->allocated(), true) << endl;
clearHeader();
boost::uint8_t *data = processHeaderFields(*buf);
size_t length = strtol(getField("content-length").c_str(), NULL, 0);
boost::shared_ptr<cygnal::Buffer> content(new cygnal::Buffer(length));
int ret = 0;
if (buf->allocated() - (data - buf->reference()) ) {
// cerr << "Don't need to read more data: have " << buf->allocated() << " bytes" << endl;
content->copy(data, length);
ret = length;
} else {
// cerr << "Need to read more data, only have " << buf->allocated() << " bytes" << endl;
ret = readNet(fd, *content, 2);
data = content->reference();
}
if (getField("content-type") == "application/x-www-form-urlencoded") {
log_debug("Got file data in POST");
string url = _docroot + _filespec;
DiskStream ds(url, *content);
ds.writeToDisk();
// ds.close();
// oh boy, we got ourselves some encoded AMF objects instead of a boring file.
} else if (getField("content-type") == "application/x-amf") {
log_debug("Got AMF data in POST");
#if 0
amf::AMF amf;
boost::shared_ptr<cygnal::Element> el = amf.extractAMF(content.reference(), content.end());
el->dump(); // FIXME: do something intelligent
// with this Element
#endif
}
// Send the reply
// NOTE: this is a "special" path we trap until we have real CGI support
if ((_filespec == "/echo/gateway")
&& (getField("content-type") == "application/x-amf")) {
// const char *num = (const char *)buf->at(10);
log_debug("Got CGI echo request in POST");
// cerr << "FIXME 2: " << hexify(content->reference(), content->allocated(), true) << endl;
vector<boost::shared_ptr<cygnal::Element> > headers = parseEchoRequest(*content);
//boost::shared_ptr<cygnal::Element> &el0 = headers[0];
//boost::shared_ptr<cygnal::Element> &el1 = headers[1];
//boost::shared_ptr<cygnal::Element> &el3 = headers[3];
if (headers.size() >= 4) {
if (headers[3]) {
cygnal::Buffer &reply = formatEchoResponse(headers[1]->getName(), *headers[3]);
// cerr << "FIXME 3: " << hexify(reply.reference(), reply.allocated(), true) << endl;
// cerr << "FIXME 3: " << hexify(reply.reference(), reply.allocated(), false) << endl;
writeNet(fd, reply);
}
}
} else {
cygnal::Buffer &reply = formatHeader(_filetype, _filesize, HTTP::OK);
writeNet(fd, reply);
}
return true;
}
bool
HTTP::processPutRequest(int /* fd */)
{
// GNASH_REPORT_FUNCTION;
log_unimpl("PUT request");
return false;
}
bool
HTTP::processDeleteRequest(int /* fd */)
{
// GNASH_REPORT_FUNCTION;
log_unimpl("DELETE request");
return false;
}
bool
HTTP::processConnectRequest(int /* fd */)
{
// GNASH_REPORT_FUNCTION;
log_unimpl("CONNECT request");
return false;
}
bool
HTTP::processOptionsRequest(int /* fd */)
{
// GNASH_REPORT_FUNCTION;
log_unimpl("OPTIONS request");
return false;
}
bool
HTTP::processHeadRequest(int /* fd */)
{
// GNASH_REPORT_FUNCTION;
log_unimpl("HEAD request");
return false;
}
bool
HTTP::processTraceRequest(int /* fd */)
{
// GNASH_REPORT_FUNCTION;
log_unimpl("TRACE request");
return false;
}
#endif
// Convert the Content-Length field to a number we can use
size_t
HTTP::getContentLength()
{
// GNASH_REPORT_FUNCTION;
std::string length = getField("content-length");
if (length.size() > 0) {
return static_cast<size_t>(strtol(length.c_str(), NULL, 0));
}
return 0;
}
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5 (5.3 Request Header Fields)
bool
HTTP::checkRequestFields(cygnal::Buffer & /* buf */)
{
// GNASH_REPORT_FUNCTION;
#if 0
const char *foo[] = {
"Accept",
"Accept-Charset",
"Accept-Encoding",
"Accept-Language",
"Authorization",
"Expect",
"From",
"Host",
"If-Match",
"If-Modified-Since",
"If-None-Match",
"If-Range",
"If-Unmodified-Since",
"Max-Forwards",
"Proxy-Authorization",
"Range",
"Referer",
"TE",
"User-Agent",
0
};
#endif
return false;
}
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec7 (7.1 Entity Header Fields)
bool
HTTP::checkEntityFields(cygnal::Buffer & /* buf */)
{
// GNASH_REPORT_FUNCTION;
#if 0
const char *foo[] = {
"Accept",
"Allow",
"Content-Encoding",
"Content-Language",
"Content-Length", // Must be used when sending a Response
"Content-Location",
"Content-MD5",
"Content-Range",
"Content-Type", // Must be used when sending non text/html files
"Expires",
"Last-Modified",
0
};
return true;
#endif
return false;
}
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec4 (4.5 General Header Fields)
bool
HTTP::checkGeneralFields(cygnal::Buffer & /* buf */)
{
// GNASH_REPORT_FUNCTION;
#if 0
const char *foo[] = {
"Cache-Control"
"Connection", // Must look for Keep-Alive and close
"Date",
"Pragma",
"Trailer",
"Transfer-Encoding", // Must look for Chunked-Body too
"Upgrade",
"Via",
"Warning",
0
};
#endif
return false;
}
boost::shared_ptr<std::vector<std::string> >
HTTP::getFieldItem(const std::string &name)
{
// GNASH_REPORT_FUNCTION;
boost::shared_ptr<std::vector<std::string> > ptr(new std::vector<std::string>);
Tok t(_fields[name], Sep(", "));
for (Tok::iterator i = t.begin(), e = t.end(); i != e; ++i) {
ptr->push_back(*i);
}
return ptr;
}
bool
HTTP::startHeader()
{
// GNASH_REPORT_FUNCTION;
clearHeader();
return true;
}
cygnal::Buffer &
HTTP::formatHeader(http_status_e type)
{
// GNASH_REPORT_FUNCTION;
return formatHeader(_filesize, type);
}
cygnal::Buffer &
HTTP::formatCommon(const string &data)
{
// GNASH_REPORT_FUNCTION;
_buffer += data;
_buffer += "\r\n";
return _buffer;
}
cygnal::Buffer &
HTTP::formatHeader(size_t size, http_status_e code)
{
// GNASH_REPORT_FUNCTION;
return formatHeader(_filetype, size, code);
}
cygnal::Buffer &
HTTP::formatHeader(DiskStream::filetype_e type, size_t size, http_status_e code)
{
// GNASH_REPORT_FUNCTION;
clearHeader();
char num[12];
_buffer = "HTTP/";
sprintf(num, "%d.%d", _version.major, _version.minor);
_buffer += num;
sprintf(num, " %d ", static_cast<int>(code));
_buffer += num;
switch (code) {
case CONTINUE:
_buffer += "Continue";
break;
case SWITCHPROTOCOLS:
_buffer += "Switch Protocols";
break;
// 2xx: Success - The action was successfully received,
// understood, and accepted
break;
case OK:
_buffer += "OK";
break;
case CREATED:
_buffer += "Created";
break;
case ACCEPTED:
_buffer += "Accepted";
break;
case NON_AUTHORITATIVE:
_buffer += "Non Authoritive";
break;
case NO_CONTENT:
_buffer += "No Content";
break;
case RESET_CONTENT:
_buffer += "Reset Content";
break;
case PARTIAL_CONTENT:
_buffer += "Partial Content";
break;
// 3xx: Redirection - Further action must be taken in order to
// complete the request
case MULTIPLE_CHOICES:
_buffer += "Multiple Choices";
break;
case MOVED_PERMANENTLY:
_buffer += "Moved Permanently";
break;
case FOUND:
_buffer += "Found";
break;
case SEE_OTHER:
_buffer += "See Other";
break;
case NOT_MODIFIED:
_buffer += "Not Modified";
break;
case USE_PROXY:
_buffer += "Use Proxy";
break;
case TEMPORARY_REDIRECT:
_buffer += "Temporary Redirect";
break;
// 4xx: Client Error - The request contains bad syntax or
// cannot be fulfilled
case BAD_REQUEST:
_buffer += "Bad Request";
break;
case UNAUTHORIZED:
_buffer += "Unauthorized";
break;
case PAYMENT_REQUIRED:
_buffer += "Payment Required";
break;
case FORBIDDEN:
_buffer += "Forbidden";
break;
case NOT_FOUND:
_buffer += "Not Found";
break;
case METHOD_NOT_ALLOWED:
_buffer += "Method Not Allowed";
break;
case NOT_ACCEPTABLE:
_buffer += "Not Acceptable";
break;
case PROXY_AUTHENTICATION_REQUIRED:
_buffer += "Proxy Authentication Required";
break;
case REQUEST_TIMEOUT:
_buffer += "Request Timeout";
break;
case CONFLICT:
_buffer += "Conflict";
break;
case GONE:
_buffer += "Gone";
break;
case LENGTH_REQUIRED:
_buffer += "Length Required";
break;
case PRECONDITION_FAILED:
_buffer += "Precondition Failed";
break;
case REQUEST_ENTITY_TOO_LARGE:
_buffer += "Request Entity Too Large";
break;
case REQUEST_URI_TOO_LARGE:
_buffer += "Request URI Too Large";
break;
case UNSUPPORTED_MEDIA_TYPE:
_buffer += "Unsupported Media Type";
break;
case REQUESTED_RANGE_NOT_SATISFIABLE:
_buffer += "Request Range Not Satisfiable";
break;
case EXPECTATION_FAILED:
_buffer += "Expectation Failed";
break;
// 5xx: Server Error - The server failed to fulfill an apparently valid request
case INTERNAL_SERVER_ERROR:
_buffer += "Internal Server Error";
break;
case NOT_IMPLEMENTED:
_buffer += "Method Not Implemented";
break;
case BAD_GATEWAY:
_buffer += "Bad Gateway";
break;
case SERVICE_UNAVAILABLE:
_buffer += "Service Unavailable";
break;
case GATEWAY_TIMEOUT:
_buffer += "Gateway Timeout";
break;
case HTTP_VERSION_NOT_SUPPORTED:
_buffer += "HTTP Version Not Supported";
break;
// Gnash/Cygnal extensions for internal use
case LIFE_IS_GOOD:
break;
case CLOSEPIPE:
_buffer += "Close Pipe";
break;
default:
break;
}
// end the line
_buffer += "\r\n";
formatDate();
formatServer();
formatLastModified();
formatAcceptRanges("bytes");
formatContentLength(size);
// Apache closes the connection on GET requests, so we do the same.
// This is a bit silly, because if we close after every GET request,
// we're not really handling the persistance of HTTP 1.1 at all.
if (_close) {
formatConnection("close");
_keepalive = false;
}
formatContentType(type);
// All HTTP messages are followed by a blank line.
terminateHeader();
return _buffer;
}
cygnal::Buffer &
HTTP::formatDate()
{
// GNASH_REPORT_FUNCTION;
boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
// cout << now.time_of_day() << "\r\n";
boost::gregorian::date d(now.date());
char num[12];
boost::gregorian::greg_weekday wd = d.day_of_week();
_buffer += "Date: ";
_buffer += wd.as_long_string();
_buffer += ", ";
sprintf(num, "%d", static_cast<int>(d.day()));
_buffer += num;
_buffer += " ";
_buffer += boost::gregorian::greg_month(d.month()).as_short_string();
_buffer += " ";
sprintf(num, "%d", static_cast<int>(d.year()));
_buffer += num;
_buffer += " ";
_buffer += boost::posix_time::to_simple_string(now.time_of_day());
_buffer += " GMT\r\n";
return _buffer;
}
cygnal::Buffer &
HTTP::formatServer()
{
// GNASH_REPORT_FUNCTION;
_buffer += "Server: Cygnal (GNU/Linux)\r\n";
return _buffer;
}
cygnal::Buffer &
HTTP::formatServer(const string &data)
{
// GNASH_REPORT_FUNCTION;
_buffer += "Server: ";
_buffer += data;
_buffer += "\r\n";
return _buffer;
}
cygnal::Buffer &
HTTP::formatContentLength()
{
// GNASH_REPORT_FUNCTION;
return formatContentLength(_filesize);
}
cygnal::Buffer &
HTTP::formatContentLength(boost::uint32_t filesize)
{
// GNASH_REPORT_FUNCTION;
// _header << "Content-Length: " << filesize << "\r\n";
_buffer += "Content-Length: ";
char num[12];
sprintf(num, "%d", filesize);
_buffer += num;
_buffer += "\r\n";
return _buffer;
}
cygnal::Buffer &
HTTP::formatContentType()
{
// GNASH_REPORT_FUNCTION;
return formatContentType(_filetype);
}
cygnal::Buffer &
HTTP::formatContentType(DiskStream::filetype_e filetype)
{
// GNASH_REPORT_FUNCTION;
switch (filetype) {
// default to HTML if the type isn't known
case DiskStream::FILETYPE_NONE:
_buffer += "Content-Type: text/html\r\n";
break;
case DiskStream::FILETYPE_AMF:
_buffer += "Content-Type: application/x-amf\r\n";
break;
case DiskStream::FILETYPE_SWF:
_buffer += "Content-Type: application/x-shockwave-flash\r\n";
break;
case DiskStream::FILETYPE_HTML:
_buffer += "Content-Type: text/html\r\n";
break;
case DiskStream::FILETYPE_PNG:
_buffer += "Content-Type: image/png\r\n";
break;
case DiskStream::FILETYPE_JPEG:
_buffer += "Content-Type: image/jpeg\r\n";
break;
case DiskStream::FILETYPE_GIF:
_buffer += "Content-Type: image/gif\r\n";
break;
case DiskStream::FILETYPE_MP3:
_buffer += "Content-Type: audio/mpeg\r\n";
break;
case DiskStream::FILETYPE_MP4:
_buffer += "Content-Type: video/mp4\r\n";
break;
case DiskStream::FILETYPE_OGG:
_buffer += "Content-Type: audio/ogg\r\n";
break;
case DiskStream::FILETYPE_VORBIS:
_buffer += "Content-Type: audio/ogg\r\n";
break;
case DiskStream::FILETYPE_THEORA:
_buffer += "Content-Type: video/ogg\r\n";
break;
case DiskStream::FILETYPE_DIRAC:
_buffer += "Content-Type: video/dirac\r\n";
break;
case DiskStream::FILETYPE_TEXT:
_buffer += "Content-Type: text/plain\r\n";
break;
case DiskStream::FILETYPE_FLV:
_buffer += "Content-Type: video/x-flv\r\n";
break;
case DiskStream::FILETYPE_VP6:
_buffer += "Content-Type: video/vp6\r\n";
break;
case DiskStream::FILETYPE_XML:
_buffer += "Content-Type: application/xml\r\n";
break;
case DiskStream::FILETYPE_FLAC:
_buffer += "Content-Type: audio/flac\r\n";
break;
case DiskStream::FILETYPE_PHP:
_buffer += "Content-Type: application/x-httpd-php\r\n";
break;
default:
_buffer += "Content-Type: text/html\r\n";
}
return _buffer;
}
cygnal::Buffer &
HTTP::formatLastModified()
{
// GNASH_REPORT_FUNCTION;
boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
std::stringstream date;
boost::gregorian::date d(now.date());
date << d.day_of_week();
date << ", " << d.day();
date << " " << d.month();
date << " " << d.year();
date << " " << now.time_of_day();
date << " GMT";
return formatLastModified(date.str());
}
cygnal::Buffer &
HTTP::formatEchoResponse(const std::string &num, cygnal::Buffer &data)
{
// GNASH_REPORT_FUNCTION;
return formatEchoResponse(num, data.reference(), data.allocated());
}
cygnal::Buffer &
HTTP::formatEchoResponse(const std::string &num, boost::uint8_t *data, size_t size)
{
// GNASH_REPORT_FUNCTION;
//boost::uint8_t *tmpptr = data;
// FIXME: temporary hacks while debugging
cygnal::Buffer fixme("00 00 00 00 00 01");
cygnal::Buffer fixme2("ff ff ff ff");
_buffer = "HTTP/1.1 200 OK\r\n";
formatContentType(DiskStream::FILETYPE_AMF);
// formatContentLength(size);
// FIXME: this is a hack ! Calculate a real size!
formatContentLength(size+29);
// Don't pretend to be the Red5 server
formatServer("Cygnal (0.8.6)");
// All HTTP messages are followed by a blank line.
terminateHeader();
// Add the binary blob for the header
_buffer += fixme;
// Make the result response, which is the 2nd data item passed in
// the request, a slash followed by a number like "/2".
string result = num;
result += "/onResult";
boost::shared_ptr<cygnal::Buffer> res = cygnal::AMF::encodeString(result);
_buffer.append(res->begin()+1, res->size()-1);
// Add the null data item
boost::shared_ptr<cygnal::Buffer> null = cygnal::AMF::encodeString("null");
_buffer.append(null->begin()+1, null->size()-1);
// Add the other binary blob
_buffer += fixme2;
cygnal::Element::amf0_type_e type = static_cast<cygnal::Element::amf0_type_e>(*data);
if ((type == cygnal::Element::UNSUPPORTED_AMF0)
|| (type == cygnal::Element::NULL_AMF0)) {
_buffer += type;
// Red5 returns a NULL object when it's recieved an undefined one in the echo_test
} else if (type == cygnal::Element::UNDEFINED_AMF0) {
_buffer += cygnal::Element::NULL_AMF0;
} else {
// Add the AMF data we're echoing back
if (size) {
_buffer.append(data, size);
}
}
return _buffer;
}
cygnal::Buffer &
HTTP::formatRequest(const string &url, http_method_e cmd)
{
// GNASH_REPORT_FUNCTION;
clearHeader();
switch (cmd) {
case HTTP::HTTP_GET:
_buffer = "GET ";
break;
case HTTP::HTTP_POST:
_buffer = "POST ";
break;
case HTTP::HTTP_HEAD:
_buffer = "HEAD ";
break;
case HTTP::HTTP_CONNECT:
_buffer = "CONNECT ";
break;
case HTTP::HTTP_TRACE:
_buffer = "TRACE ";
break;
case HTTP::HTTP_OPTIONS:
_buffer = "OPTIONS ";
break;
default:
break;
}
_buffer += url;
_buffer += " HTTP/1.1";
// sprintf(num, "%d.%d", _version.major, _version.minor);
// _buffer += num;
// end the line
_buffer += "\r\n";
formatHost("localhost");
formatAgent("Gnash");
// Post messages want a bunch more fields than a GET request
if (cmd == HTTP::HTTP_POST) {
formatContentType(DiskStream::FILETYPE_AMF);
formatEncoding("deflate, gzip, x-gzip, identity, *;q=0");
formatConnection("Keep-Alive");
}
// // All HTTP messages are followed by a blank line.
// terminateHeader();
return _buffer;
}
HTTP::http_method_e
HTTP::extractCommand(boost::uint8_t *data)
{
// GNASH_REPORT_FUNCTION;
// string body = reinterpret_cast<const char *>(data);
HTTP::http_method_e cmd = HTTP::HTTP_NONE;
// force the case to make comparisons easier
// std::transform(body.begin(), body.end(), body.begin(),
// (int(*)(int)) toupper);
// Extract the command
if (memcmp(data, "GET", 3) == 0) {
cmd = HTTP::HTTP_GET;
} else if (memcmp(data, "POST", 4) == 0) {
cmd = HTTP::HTTP_POST;
} else if (memcmp(data, "HEAD", 4) == 0) {
cmd = HTTP::HTTP_HEAD;
} else if (memcmp(data, "CONNECT", 7) == 0) {
cmd = HTTP::HTTP_CONNECT;
} else if (memcmp(data, "TRACE", 5) == 0) {
cmd = HTTP::HTTP_TRACE;
} else if (memcmp(data, "PUT", 3) == 0) {
cmd = HTTP::HTTP_PUT;
} else if (memcmp(data, "OPTIONS", 4) == 0) {
cmd = HTTP::HTTP_OPTIONS;
} else if (memcmp(data, "DELETE", 4) == 0) {
cmd = HTTP::HTTP_DELETE;
} else if (memcmp(data, "HTTP", 4) == 0) {
cmd = HTTP::HTTP_RESPONSE;
}
// For valid requests, the second argument, delimited by spaces
// is the filespec of the file being requested or transmitted.
if (cmd != HTTP::HTTP_NONE) {
boost::uint8_t *start = std::find(data, data+7, ' ') + 1;
boost::uint8_t *end = std::find(start + 2, data+PATH_MAX, ' ');
boost::uint8_t *params = std::find(start, end, '?');
if (params != end) {
_params = std::string(params+1, end);
_filespec = std::string(start, params);
log_debug("Parameters for file: \"%s\"", _params);
} else {
// This is fine as long as end is within the buffer.
_filespec = std::string(start, end);
}
// log_debug("Requesting file: \"%s\"", _filespec);
// The third field is always the HTTP version
// The version is the last field and is the protocol name
// followed by a slash, and the version number. Note that
// the version is not a double, even though it has a dot
// in it. It's actually two separate integers.
_version.major = *(end+6) - '0';
_version.minor = *(end+8) - '0';
// log_debug (_("Version: %d.%d"), _version.major, _version.minor);
}
return cmd;
}
/// \brief Send a message to the other end of the network connection.
///` Sends the contents of the _header and _body private data to
/// the already opened network connection.
///
/// @return The number of bytes sent
int DSOEXPORT
HTTP::sendMsg()
{
GNASH_REPORT_FUNCTION;
return 0; // FIXME
}
/// \brief Send a message to the other end of the network connection.
///` Sends the contents of the _header and _body private data to
/// the already opened network connection.
///
/// @param fd The file descriptor to use for writing to the network.
///
/// @return The number of bytes sent
int DSOEXPORT
HTTP::sendMsg(int /* fd */)
{
GNASH_REPORT_FUNCTION;
return 0; // FIXME
}
/// \brief Send a message to the other end of the network connection.
///` Sends the contents of the _header and _body private data to
/// the already opened network connection.
///
/// @param data A real pointer to the data.
/// @param size The number of bytes of data stored.
///
/// @return The number of bytes sent
int DSOEXPORT
HTTP::sendMsg(const boost::uint8_t *data, size_t size)
{
GNASH_REPORT_FUNCTION;
// _header
return Network::writeNet(data, size);
}
size_t
HTTP::recvChunked(boost::uint8_t *data, size_t size)
{
// GNASH_REPORT_FUNCTION;
bool done = false;
bool chunks = true;
size_t total = 0;
size_t pktsize = 0;
if (size == 0) {
return 0;
}
// A chunked transfer sends a count of bytes in ASCII hex first,
// and that line is terminated with the usual \r\n HTTP header field
// line number. There is supposed to be a ';' before the \r\n, as this
// field can have other attributes, but the OpenStreetMap server doesn't
// use the semi-colon, as it's optional, and rarely used anyway.
boost::shared_ptr<cygnal::Buffer> buf;
boost::uint8_t *start = std::find(data, data+size, '\r') + 2;
if (start != data+size) {
// extract the total size of the chunk
std::string bytes(data, start-2);
size_t sizesize = start-data;
total = static_cast<size_t>(strtol(bytes.c_str(), NULL, 16));
log_debug("%s: Total size for first chunk is: %d, data size %d (%d)",
__PRETTY_FUNCTION__, total, size, sizesize);
buf.reset(new cygnal::Buffer(total+2));
// Add the existing data from the previous packet
buf->copy(data+sizesize, size-sizesize);
}
// The size of the packet we need to read has a 2 byte terminator "\r\n",
// like any other HTTP header field, so we have to read those bytes too
// so we can stay sychronized with the start of each chunk.
pktsize = total - buf->allocated() + 2;
// log_debug("%s: Total Packet size for first chunk is: %d", __PRETTY_FUNCTION__,
// pktsize);
done = false;
size_t ret = 0;
// Keep reading chunks as long as they arrive
while (chunks) {
do {
if (!pktsize) {
total = 0;
// Only read a few bytes, as we have todo a resize, so the less
// data to copy the better. We only do this to get the total size
// of the chunk, which we need to know when it's done. As we can't
// tell we're done processing chunks till one has a length of
// "0\r\n", this is important.
pktsize = 12;
buf.reset(new cygnal::Buffer(pktsize+2));
}
ret = readNet(buf->reference() + buf->allocated(), pktsize, 60);
//buf->dump();
// We got data.
if (ret == 0) {
log_debug("no data yet for fd #%d, continuing...", getFileFd());
done = true;
}
if (ret) {
// manually set the seek pointer in the buffer, as we read
// the data into the raw memory allocated to the buffer. We
// only want to do this if we got data of course.
buf->setSeekPointer(buf->end() + ret);
if (!total) {
start = std::find(buf->reference(), buf->reference()+ret, '\r') + 2;
if (start != buf->reference()+ret) {
// extract the total size of the chunk
std::string bytes(buf->reference(), start-2);
total = static_cast<size_t>(strtol(bytes.c_str(), NULL, 16));
// The total size of the last chunk is always "0"
if (total == 0) {
log_debug("%s: end of chunks!", __PRETTY_FUNCTION__);
pktsize = 0;
done = true;
chunks = false;
} else {
pktsize = total+8; // FIXME: why do we need an 8 here ?
// log_debug("%s: Total size for chunk is: %d (%s), adding %d bytes",
// __PRETTY_FUNCTION__, total, bytes, (start - buf->reference()));
cygnal::Buffer tmpbuf(start - buf->reference());
// don't forget the two bytes for the "\r\n"
tmpbuf.copy(buf->reference() + bytes.size() + 2, (start - buf->reference()));
buf->clear(); // FIXME: debug only
buf->resize(total);
buf->copy(tmpbuf.reference(), tmpbuf.size());
}
}
}
// If we got less data than specified, we need to keep reading data
if (ret < buf->size()) {
pktsize -= ret;
if (pktsize == 0) {
done = true;
} else {
// log_debug("Didn't get a full packet, reading more data");
continue;
}
}
}
} while (!done);
// log_debug("%s: finished packet, ret is %d, pktsize is %d\r\n%s", __PRETTY_FUNCTION__, ret, pktsize,
// hexify(buf->reference(), buf->allocated(), true));
if (pktsize == 0) {
// The last two bytes of the chunk are always "\r\n", which we remove so it
// doesn't become part of the binary data being sent in the chunk.
if ((*(buf->end()-2) == '\r') && (*(buf->end()-1) == '\n')) {
*(buf->end()-2) = 0; // '\r'
*(buf->end()-1) = 0; // '\n'
buf->setSeekPointer(buf->end() - 2);
}
_que.push(buf);
}
done = false; // reset
} // end of while chunks
return _que.size();
}
int
HTTP::recvMsg(int fd)
{
// GNASH_REPORT_FUNCTION;
return recvMsg(fd, 0);
}
int
HTTP::recvMsg(int fd, size_t size)
{
// GNASH_REPORT_FUNCTION;
size_t ret = 0;
if (size == 0) {
size = cygnal::NETBUFSIZE;
}
log_debug("Starting to wait for data in net for fd #%d", fd);
Network net;
do {
boost::shared_ptr<cygnal::Buffer> buf(new cygnal::Buffer(size));
ret = net.readNet(fd, *buf, 5);
// cerr << __PRETTY_FUNCTION__ << ret << " : " << (char *)buf->reference() << endl;
// the read timed out as there was no data, but the socket is still open.
if (ret == 0) {
log_debug("no data yet for fd #%d, continuing...", fd);
continue;
}
// ret is "no position" when the socket is closed from the other end of the connection,
// so we're done.
if ((ret == static_cast<size_t>(string::npos)) || (static_cast<int>(ret) == -1)) {
log_debug("socket for fd #%d was closed...", fd);
return 0;
}
// We got data. Resize the buffer if necessary.
if (ret > 0) {
buf->setSeekPointer(buf->reference() + ret);
// cerr << "XXXXX: " << (char *)buf->reference() << endl;
if (ret < static_cast<int>(cygnal::NETBUFSIZE)) {
// buf->resize(ret); FIXME: why does this corrupt
// the buffer ?
_que.push(buf);
break;
} else {
_que.push(buf);
}
// ret must be more than 0 here
if (ret == buf->size()) {
continue;
}
} else {
log_debug("no more data for fd #%d, exiting...", fd);
return 0;
}
if (static_cast<int>(ret) == -1) {
log_debug("Handler done for fd #%d, can't read any data...", fd);
return -1;
}
} while (ret);
// We're done. Notify the other threads the socket is closed, and tell them to die.
log_debug("Done receiving data for fd #%d...", fd);
return ret;
}
void
HTTP::dump() {
// GNASH_REPORT_FUNCTION;
boost::mutex::scoped_lock lock(stl_mutex);
log_debug (_("==== The HTTP header breaks down as follows: ===="));
log_debug (_("Filespec: %s"), _filespec.c_str());
log_debug (_("Version: %d.%d"), _version.major, _version.minor);
std::map<string, string>::const_iterator it;
for (it = _fields.begin(); it != _fields.end(); ++it) {
log_debug("Field: \"%s\" = \"%s\"", it->first, it->second);
}
// Dump the RTMPT fields
log_debug("RTMPT optional index is: ", _index);
log_debug("RTMPT optional client ID is: ", _clientid);
log_debug (_("==== ==== ===="));
}
} // end of gnash namespace
// local Variables:
// mode: C++
// indent-tabs-mode: t
// End: