1
// http.cpp: HyperText Transport Protocol handler for Cygnal, for Gnash.
3
// Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
6
// This program is free software; you can redistribute it and/or modify
7
// it under the terms of the GNU General Public License as published by
8
// the Free Software Foundation; either version 3 of the License, or
9
// (at your option) any later version.
11
// This program is distributed in the hope that it will be useful,
12
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
// GNU General Public License for more details.
16
// You should have received a copy of the GNU General Public License
17
// along with this program; if not, write to the Free Software
18
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21
#include <boost/thread/mutex.hpp>
22
#include <boost/shared_ptr.hpp>
23
#include <boost/shared_array.hpp>
24
#include <boost/scoped_array.hpp>
25
#include <boost/tokenizer.hpp>
26
#include <boost/date_time/posix_time/posix_time.hpp>
27
#include <boost/date_time/gregorian/gregorian.hpp>
28
#include <sys/types.h>
35
#include <sys/types.h>
39
#include "GnashSystemIOHeaders.h" // read()
46
// #include "handler.h"
49
#include "diskstream.h"
52
// Not POSIX, so best not rely on it if possible.
54
# define PATH_MAX 1024
57
#if defined(_WIN32) || defined(WIN32)
58
# define __PRETTY_FUNCTION__ __FUNCDNAME__
59
# include <winsock2.h>
63
# include <sys/param.h>
68
static boost::mutex stl_mutex;
73
// extern map<int, Handler *> handlers;
75
// FIXME, this seems too small to me. --gnu
76
static const int readsize = 1024;
78
static Cache& cache = Cache::getDefaultInstance();
81
: _filetype(DiskStream::FILETYPE_HTML),
90
// GNASH_REPORT_FUNCTION;
91
// struct status_codes *status = new struct status_codes;
95
// _status_codes(CONTINUE, status);
99
HTTP::HTTP(Handler *hand)
100
: _filetype(DiskStream::FILETYPE_HTML),
109
// GNASH_REPORT_FUNCTION;
118
// GNASH_REPORT_FUNCTION;
133
HTTP::operator = (HTTP& /*obj*/)
135
GNASH_REPORT_FUNCTION;
143
HTTP::processHeaderFields(cygnal::Buffer *buf)
145
// GNASH_REPORT_FUNCTION;
146
string head(reinterpret_cast<const char *>(buf->reference()), buf->size());
148
// The end of the header block is always followed by a blank line
149
string::size_type end = head.find("\r\n\r\n", 0);
150
// head.erase(end, buf.size()-end);
151
Tok t(head, Sep("\r\n"));
152
for (Tok::iterator i = t.begin(); i != t.end(); ++i) {
153
string::size_type pos = i->find(":", 0);
154
if (pos != string::npos) {
155
string name = i->substr(0, pos);
156
string value = i->substr(pos+2, i->size());
157
std::transform(name.begin(), name.end(), name.begin(),
158
(int(*)(int)) tolower);
159
std::transform(value.begin(), value.end(), value.begin(),
160
(int(*)(int)) tolower);
161
_fields[name] = value;
162
if (name == "keep-alive") {
164
if ((value != "on") && (value != "off")) {
165
_max_requests = strtol(value.c_str(), NULL, 0);
166
// log_debug("Setting Max Requests for Keep-Alive to %d", _max_requests);
169
if (name == "connection") {
170
if (value.find("keep-alive", 0) != string::npos) {
174
if (name == "content-length") {
175
_filesize = strtol(value.c_str(), NULL, 0);
176
log_debug("Setting Content Length to %d", _filesize);
178
if (name == "content-type") {
179
// This is the type used by flash when sending a AMF data via POST
180
if (value == "application/x-amf") {
181
// log_debug("Got AMF data in the POST request!");
182
_filetype = DiskStream::FILETYPE_AMF;
184
// This is the type used by wget when sending a file via POST
185
if (value == "application/x-www-form-urlencoded") {
186
// log_debug("Got file data in the POST request");
187
_filetype = DiskStream::FILETYPE_ENCODED;
189
log_debug("Setting Content Type to %d", _filetype);
192
// cerr << "FIXME: " << (void *)i << " : " << dec << end << endl;
194
const boost::uint8_t *cmd = reinterpret_cast<const boost::uint8_t *>(i->c_str());
195
if (extractCommand(const_cast<boost::uint8_t *>(cmd)) == HTTP::HTTP_NONE) {
199
log_debug("Got a request, parsing \"%s\"", *i);
200
string::size_type start = i->find(" ");
201
string::size_type params = i->find("?");
202
string::size_type pos = i->find("HTTP/");
203
if (pos != string::npos) {
204
// The version is the last field and is the protocol name
205
// followed by a slash, and the version number. Note that
206
// the version is not a double, even though it has a dot
207
// in it. It's actually two separate integers.
208
_version.major = i->at(pos+5) - '0';
209
_version.minor = i->at(pos+7) - '0';
210
// log_debug (_("Version: %d.%d"), _version.major, _version.minor);
211
// the filespec in the request is the middle field, deliminated
212
// by a space on each end.
213
if (params != string::npos) {
214
_params = i->substr(params+1, end);
215
_filespec = i->substr(start+1, params);
216
log_debug("Parameters for file: \"%s\"", _params);
218
_filespec = i->substr(start+1, pos-start-2);
220
log_debug("Requesting file: \"%s\"", _filespec);
222
// HTTP 1.1 enables persistant network connections
224
if (_version.minor > 0) {
225
log_debug("Enabling Keep Alive by default for HTTP > 1.0");
234
return buf->reference() + end + 4;
237
// // Parse an Echo Request message coming from the Red5 echo_test. This
238
// // method should only be used for testing purposes.
239
// vector<boost::shared_ptr<cygnal::Element > >
240
// HTTP::parseEchoRequest(boost::uint8_t *data, size_t size)
242
// // GNASH_REPORT_FUNCTION;
244
// vector<boost::shared_ptr<cygnal::Element > > headers;
246
// // skip past the header bytes, we don't care about them.
247
// boost::uint8_t *tmpptr = data + 6;
249
// boost::uint16_t length;
250
// length = ntohs((*(boost::uint16_t *)tmpptr) & 0xffff);
251
// tmpptr += sizeof(boost::uint16_t);
253
// // Get the first name, which is a raw string, and not preceded by
255
// boost::shared_ptr<cygnal::Element > el1(new cygnal::Element);
257
// // If the length of the name field is corrupted, then we get out of
258
// // range quick, and corrupt memory. This is a bit of a hack, but
259
// // reduces memory errors caused by some of the corrupted tes cases.
260
// boost::uint8_t *endstr = std::find(tmpptr, tmpptr+length, '\0');
261
// if (endstr != tmpptr+length) {
262
// log_debug("Caught corrupted string! length was %d, null at %d",
263
// length, endstr-tmpptr);
264
// length = endstr-tmpptr;
266
// el1->setName(tmpptr, length);
268
// headers.push_back(el1);
270
// // Get the second name, which is a raw string, and not preceded by
272
// length = ntohs((*(boost::uint16_t *)tmpptr) & 0xffff);
273
// tmpptr += sizeof(boost::uint16_t);
274
// boost::shared_ptr<cygnal::Element > el2(new cygnal::Element);
276
// // std::string name2(reinterpret_cast<const char *>(tmpptr), length);
277
// // el2->setName(name2.c_str(), name2.size());
278
// // If the length of the name field is corrupted, then we get out of
279
// // range quick, and corrupt memory. This is a bit of a hack, but
280
// // reduces memory errors caused by some of the corrupted tes cases.
281
// endstr = std::find(tmpptr, tmpptr+length, '\0');
282
// if (endstr != tmpptr+length) {
283
// log_debug("Caught corrupted string! length was %d, null at %d",
284
// length, endstr-tmpptr);
285
// length = endstr-tmpptr;
287
// el2->setName(tmpptr, length);
288
// headers.push_back(el2);
291
// // Get the last two pieces of data, which are both AMF encoded
292
// // with a type byte.
294
// boost::shared_ptr<cygnal::Element> el3 = amf.extractAMF(tmpptr, tmpptr + size);
295
// headers.push_back(el3);
296
// tmpptr += amf.totalsize();
298
// boost::shared_ptr<cygnal::Element> el4 = amf.extractAMF(tmpptr, tmpptr + size);
299
// headers.push_back(el4);
304
// // format a response to the 'echo' test used for testing Gnash. This
305
// // is only used for testing by developers. The format appears to be
306
// // two strings, followed by a double, followed by the "onResult".
308
// HTTP::formatEchoResponse(const std::string &num, cygnal::Element &el)
310
// // GNASH_REPORT_FUNCTION;
311
// boost::shared_ptr<cygnal::Buffer> data;
313
// cygnal::Element nel;
314
// if (el.getType() == cygnal::Element::TYPED_OBJECT_AMF0) {
315
// nel.makeTypedObject();
316
// string name = el.getName();
317
// nel.setName(name);
318
// if (el.propertySize()) {
319
// // FIXME: see about using std::reverse() instead.
320
// for (int i=el.propertySize()-1; i>=0; i--) {
321
// // for (int i=0 ; i<el.propertySize(); i++) {
322
// boost::shared_ptr<cygnal::Element> child = el.getProperty(i);
323
// nel.addProperty(child);
325
// data = nel.encode();
327
// data = el.encode();
330
// data = el.encode();
333
// return formatEchoResponse(num, data->reference(), data->allocated());
337
// Client side parsing of response message codes
338
boost::shared_ptr<HTTP::http_response_t>
339
HTTP::parseStatus(const std::string &line)
341
// GNASH_REPORT_FUNCTION;
343
boost::shared_ptr<http_response_t> status;
344
// The respnse is a number followed by the error message.
345
string::size_type pos = line.find(" ", 0);
346
if (pos != string::npos) {
347
status.reset(new http_response_t);
348
status->code = static_cast<HTTP::http_status_e>(strtol(line.substr(0, pos).c_str(), NULL, 0));
349
status->msg = line.substr(pos+1, line.size());
356
HTTP::processClientRequest(int fd)
358
// GNASH_REPORT_FUNCTION;
361
boost::shared_ptr<cygnal::Buffer> buf(_que.peek());
363
_cmd = extractCommand(buf->reference());
366
result = processGetRequest(fd);
368
case HTTP::HTTP_POST:
369
result = processPostRequest(fd);
371
case HTTP::HTTP_HEAD:
372
result = processHeadRequest(fd);
374
case HTTP::HTTP_CONNECT:
375
result = processConnectRequest(fd);
377
case HTTP::HTTP_TRACE:
378
result = processTraceRequest(fd);
380
case HTTP::HTTP_OPTIONS:
381
result = processOptionsRequest(fd);
384
result = processPutRequest(fd);
386
case HTTP::HTTP_DELETE:
387
result = processDeleteRequest(fd);
397
// A GET request asks the server to send a file to the client
399
HTTP::processGetRequest(int fd)
401
GNASH_REPORT_FUNCTION;
403
// boost::uint8_t buffer[readsize+1];
404
// const char *ptr = reinterpret_cast<const char *>(buffer);
405
// memset(buffer, 0, readsize+1);
410
cerr << "QUE = " << _que.size() << endl;
412
if (_que.size() == 0) {
416
boost::shared_ptr<cygnal::Buffer> buf(_que.pop());
417
// cerr << "YYYYYYY: " << (char *)buf->reference() << endl;
418
// cerr << hexify(buf->reference(), buf->allocated(), false) << endl;
421
// log_debug("Que empty, net connection dropped for fd #%d", getFileFd());
422
log_debug("Que empty, net connection dropped for fd #%d", fd);
427
processHeaderFields(*buf);
429
string url = _docroot + _filespec;
430
// See if the file is in the cache and already opened.
431
boost::shared_ptr<DiskStream> filestream(cache.findFile(url));
433
log_network("FIXME: found file in cache!");
435
filestream.reset(new DiskStream);
436
// cerr << "New Filestream at 0x" << hex << filestream.get() << endl;
438
// cache.addFile(url, filestream); FIXME: always reload from disk for now.
440
// Oopen the file and read the first chunk into memory
441
if (filestream->open(url)) {
442
formatErrorResponse(HTTP::NOT_FOUND);
444
// Get the file size for the HTTP header
445
if (filestream->getFileType() == DiskStream::FILETYPE_NONE) {
446
formatErrorResponse(HTTP::NOT_FOUND);
448
cache.addPath(_filespec, filestream->getFilespec());
454
cygnal::Buffer &reply = formatHeader(filestream->getFileType(),
455
filestream->getFileSize(),
459
size_t filesize = filestream->getFileSize();
460
size_t bytes_read = 0;
464
#ifdef USE_STATS_CACHE
465
struct timespec start;
466
clock_gettime (CLOCK_REALTIME, &start);
469
if (filesize <= filestream->getPagesize()) {
472
getbytes = filestream->getPagesize();
474
if (filesize >= CACHE_LIMIT) {
476
filestream->loadToMem(page);
477
ret = writeNet(fd, filestream->get(), getbytes);
482
page += filestream->getPagesize();
483
} while (bytes_read <= filesize);
485
filestream->loadToMem(filesize, 0);
486
ret = writeNet(fd, filestream->get(), filesize);
489
#ifdef USE_STATS_CACHE
491
clock_gettime (CLOCK_REALTIME, &end);
492
double time = (end.tv_sec - start.tv_sec) + ((end.tv_nsec - start.tv_nsec)/1e9);
493
cerr << "File " << _filespec
494
<< " transferred " << filesize << " bytes in: " << fixed
495
<< time << " seconds for net fd #" << fd << endl;
499
log_debug("http_handler all done transferring requested file \"%s\".", _filespec);
504
// A POST request asks sends a data from the client to the server. After processing
505
// the header like we normally do, we then read the amount of bytes specified by
506
// the "content-length" field, and then write that data to disk, or decode the amf.
508
HTTP::processPostRequest(int fd)
510
GNASH_REPORT_FUNCTION;
512
// cerr << "QUE1 = " << _que.size() << endl;
514
if (_que.size() == 0) {
518
boost::shared_ptr<cygnal::Buffer> buf(_que.pop());
520
log_debug("Que empty, net connection dropped for fd #%d", getFileFd());
523
// cerr << __FUNCTION__ << buf->allocated() << " : " << hexify(buf->reference(), buf->allocated(), true) << endl;
526
boost::uint8_t *data = processHeaderFields(*buf);
527
size_t length = strtol(getField("content-length").c_str(), NULL, 0);
528
boost::shared_ptr<cygnal::Buffer> content(new cygnal::Buffer(length));
530
if (buf->allocated() - (data - buf->reference()) ) {
531
// cerr << "Don't need to read more data: have " << buf->allocated() << " bytes" << endl;
532
content->copy(data, length);
535
// cerr << "Need to read more data, only have " << buf->allocated() << " bytes" << endl;
536
ret = readNet(fd, *content, 2);
537
data = content->reference();
540
if (getField("content-type") == "application/x-www-form-urlencoded") {
541
log_debug("Got file data in POST");
542
string url = _docroot + _filespec;
543
DiskStream ds(url, *content);
546
// oh boy, we got ourselves some encoded AMF objects instead of a boring file.
547
} else if (getField("content-type") == "application/x-amf") {
548
log_debug("Got AMF data in POST");
551
boost::shared_ptr<cygnal::Element> el = amf.extractAMF(content.reference(), content.end());
552
el->dump(); // FIXME: do something intelligent
559
// NOTE: this is a "special" path we trap until we have real CGI support
560
if ((_filespec == "/echo/gateway")
561
&& (getField("content-type") == "application/x-amf")) {
562
// const char *num = (const char *)buf->at(10);
563
log_debug("Got CGI echo request in POST");
564
// cerr << "FIXME 2: " << hexify(content->reference(), content->allocated(), true) << endl;
566
vector<boost::shared_ptr<cygnal::Element> > headers = parseEchoRequest(*content);
567
//boost::shared_ptr<cygnal::Element> &el0 = headers[0];
568
//boost::shared_ptr<cygnal::Element> &el1 = headers[1];
569
//boost::shared_ptr<cygnal::Element> &el3 = headers[3];
571
if (headers.size() >= 4) {
573
cygnal::Buffer &reply = formatEchoResponse(headers[1]->getName(), *headers[3]);
574
// cerr << "FIXME 3: " << hexify(reply.reference(), reply.allocated(), true) << endl;
575
// cerr << "FIXME 3: " << hexify(reply.reference(), reply.allocated(), false) << endl;
580
cygnal::Buffer &reply = formatHeader(_filetype, _filesize, HTTP::OK);
588
HTTP::processPutRequest(int /* fd */)
590
// GNASH_REPORT_FUNCTION;
591
log_unimpl("PUT request");
597
HTTP::processDeleteRequest(int /* fd */)
599
// GNASH_REPORT_FUNCTION;
600
log_unimpl("DELETE request");
605
HTTP::processConnectRequest(int /* fd */)
607
// GNASH_REPORT_FUNCTION;
608
log_unimpl("CONNECT request");
613
HTTP::processOptionsRequest(int /* fd */)
615
// GNASH_REPORT_FUNCTION;
616
log_unimpl("OPTIONS request");
621
HTTP::processHeadRequest(int /* fd */)
623
// GNASH_REPORT_FUNCTION;
624
log_unimpl("HEAD request");
629
HTTP::processTraceRequest(int /* fd */)
631
// GNASH_REPORT_FUNCTION;
632
log_unimpl("TRACE request");
637
// Convert the Content-Length field to a number we can use
639
HTTP::getContentLength()
641
// GNASH_REPORT_FUNCTION;
642
std::string length = getField("content-length");
643
if (length.size() > 0) {
644
return static_cast<size_t>(strtol(length.c_str(), NULL, 0));
651
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5 (5.3 Request Header Fields)
653
HTTP::checkRequestFields(cygnal::Buffer & /* buf */)
655
// GNASH_REPORT_FUNCTION;
658
const char *foo[] = {
671
"If-Unmodified-Since",
673
"Proxy-Authorization",
684
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec7 (7.1 Entity Header Fields)
686
HTTP::checkEntityFields(cygnal::Buffer & /* buf */)
688
// GNASH_REPORT_FUNCTION;
691
const char *foo[] = {
696
"Content-Length", // Must be used when sending a Response
700
"Content-Type", // Must be used when sending non text/html files
711
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec4 (4.5 General Header Fields)
713
HTTP::checkGeneralFields(cygnal::Buffer & /* buf */)
715
// GNASH_REPORT_FUNCTION;
718
const char *foo[] = {
720
"Connection", // Must look for Keep-Alive and close
724
"Transfer-Encoding", // Must look for Chunked-Body too
734
boost::shared_ptr<std::vector<std::string> >
735
HTTP::getFieldItem(const std::string &name)
737
// GNASH_REPORT_FUNCTION;
738
boost::shared_ptr<std::vector<std::string> > ptr(new std::vector<std::string>);
739
Tok t(_fields[name], Sep(", "));
740
for (Tok::iterator i = t.begin(), e = t.end(); i != e; ++i) {
750
// GNASH_REPORT_FUNCTION;
758
HTTP::formatHeader(http_status_e type)
760
// GNASH_REPORT_FUNCTION;
762
return formatHeader(_filesize, type);
766
HTTP::formatCommon(const string &data)
768
// GNASH_REPORT_FUNCTION;
776
HTTP::formatHeader(size_t size, http_status_e code)
778
// GNASH_REPORT_FUNCTION;
779
return formatHeader(_filetype, size, code);
783
HTTP::formatHeader(DiskStream::filetype_e type, size_t size, http_status_e code)
785
// GNASH_REPORT_FUNCTION;
792
sprintf(num, "%d.%d", _version.major, _version.minor);
794
sprintf(num, " %d ", static_cast<int>(code));
798
_buffer += "Continue";
800
case SWITCHPROTOCOLS:
801
_buffer += "Switch Protocols";
803
// 2xx: Success - The action was successfully received,
804
// understood, and accepted
810
_buffer += "Created";
813
_buffer += "Accepted";
815
case NON_AUTHORITATIVE:
816
_buffer += "Non Authoritive";
819
_buffer += "No Content";
822
_buffer += "Reset Content";
824
case PARTIAL_CONTENT:
825
_buffer += "Partial Content";
827
// 3xx: Redirection - Further action must be taken in order to
828
// complete the request
829
case MULTIPLE_CHOICES:
830
_buffer += "Multiple Choices";
832
case MOVED_PERMANENTLY:
833
_buffer += "Moved Permanently";
839
_buffer += "See Other";
842
_buffer += "Not Modified";
845
_buffer += "Use Proxy";
847
case TEMPORARY_REDIRECT:
848
_buffer += "Temporary Redirect";
850
// 4xx: Client Error - The request contains bad syntax or
851
// cannot be fulfilled
853
_buffer += "Bad Request";
856
_buffer += "Unauthorized";
858
case PAYMENT_REQUIRED:
859
_buffer += "Payment Required";
862
_buffer += "Forbidden";
865
_buffer += "Not Found";
867
case METHOD_NOT_ALLOWED:
868
_buffer += "Method Not Allowed";
871
_buffer += "Not Acceptable";
873
case PROXY_AUTHENTICATION_REQUIRED:
874
_buffer += "Proxy Authentication Required";
876
case REQUEST_TIMEOUT:
877
_buffer += "Request Timeout";
880
_buffer += "Conflict";
885
case LENGTH_REQUIRED:
886
_buffer += "Length Required";
888
case PRECONDITION_FAILED:
889
_buffer += "Precondition Failed";
891
case REQUEST_ENTITY_TOO_LARGE:
892
_buffer += "Request Entity Too Large";
894
case REQUEST_URI_TOO_LARGE:
895
_buffer += "Request URI Too Large";
897
case UNSUPPORTED_MEDIA_TYPE:
898
_buffer += "Unsupported Media Type";
900
case REQUESTED_RANGE_NOT_SATISFIABLE:
901
_buffer += "Request Range Not Satisfiable";
903
case EXPECTATION_FAILED:
904
_buffer += "Expectation Failed";
906
// 5xx: Server Error - The server failed to fulfill an apparently valid request
907
case INTERNAL_SERVER_ERROR:
908
_buffer += "Internal Server Error";
910
case NOT_IMPLEMENTED:
911
_buffer += "Method Not Implemented";
914
_buffer += "Bad Gateway";
916
case SERVICE_UNAVAILABLE:
917
_buffer += "Service Unavailable";
919
case GATEWAY_TIMEOUT:
920
_buffer += "Gateway Timeout";
922
case HTTP_VERSION_NOT_SUPPORTED:
923
_buffer += "HTTP Version Not Supported";
925
// Gnash/Cygnal extensions for internal use
929
_buffer += "Close Pipe";
940
formatLastModified();
941
formatAcceptRanges("bytes");
942
formatContentLength(size);
944
// Apache closes the connection on GET requests, so we do the same.
945
// This is a bit silly, because if we close after every GET request,
946
// we're not really handling the persistance of HTTP 1.1 at all.
948
formatConnection("close");
951
formatContentType(type);
953
// All HTTP messages are followed by a blank line.
962
// GNASH_REPORT_FUNCTION;
963
boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
965
// cout << now.time_of_day() << "\r\n";
967
boost::gregorian::date d(now.date());
971
boost::gregorian::greg_weekday wd = d.day_of_week();
973
_buffer += wd.as_long_string();
976
sprintf(num, "%d", static_cast<int>(d.day()));
980
_buffer += boost::gregorian::greg_month(d.month()).as_short_string();
983
sprintf(num, "%d", static_cast<int>(d.year()));
987
_buffer += boost::posix_time::to_simple_string(now.time_of_day());
989
_buffer += " GMT\r\n";
997
// GNASH_REPORT_FUNCTION;
998
_buffer += "Server: Cygnal (GNU/Linux)\r\n";
1004
HTTP::formatServer(const string &data)
1006
// GNASH_REPORT_FUNCTION;
1007
_buffer += "Server: ";
1015
HTTP::formatContentLength()
1017
// GNASH_REPORT_FUNCTION;
1019
return formatContentLength(_filesize);
1023
HTTP::formatContentLength(boost::uint32_t filesize)
1025
// GNASH_REPORT_FUNCTION;
1026
// _header << "Content-Length: " << filesize << "\r\n";
1028
_buffer += "Content-Length: ";
1030
sprintf(num, "%d", filesize);
1038
HTTP::formatContentType()
1040
// GNASH_REPORT_FUNCTION;
1041
return formatContentType(_filetype);
1045
HTTP::formatContentType(DiskStream::filetype_e filetype)
1047
// GNASH_REPORT_FUNCTION;
1050
// default to HTML if the type isn't known
1051
case DiskStream::FILETYPE_NONE:
1052
_buffer += "Content-Type: text/html\r\n";
1054
case DiskStream::FILETYPE_AMF:
1055
_buffer += "Content-Type: application/x-amf\r\n";
1057
case DiskStream::FILETYPE_SWF:
1058
_buffer += "Content-Type: application/x-shockwave-flash\r\n";
1060
case DiskStream::FILETYPE_HTML:
1061
_buffer += "Content-Type: text/html\r\n";
1063
case DiskStream::FILETYPE_PNG:
1064
_buffer += "Content-Type: image/png\r\n";
1066
case DiskStream::FILETYPE_JPEG:
1067
_buffer += "Content-Type: image/jpeg\r\n";
1069
case DiskStream::FILETYPE_GIF:
1070
_buffer += "Content-Type: image/gif\r\n";
1072
case DiskStream::FILETYPE_MP3:
1073
_buffer += "Content-Type: audio/mpeg\r\n";
1075
case DiskStream::FILETYPE_MP4:
1076
_buffer += "Content-Type: video/mp4\r\n";
1078
case DiskStream::FILETYPE_OGG:
1079
_buffer += "Content-Type: audio/ogg\r\n";
1081
case DiskStream::FILETYPE_VORBIS:
1082
_buffer += "Content-Type: audio/ogg\r\n";
1084
case DiskStream::FILETYPE_THEORA:
1085
_buffer += "Content-Type: video/ogg\r\n";
1087
case DiskStream::FILETYPE_DIRAC:
1088
_buffer += "Content-Type: video/dirac\r\n";
1090
case DiskStream::FILETYPE_TEXT:
1091
_buffer += "Content-Type: text/plain\r\n";
1093
case DiskStream::FILETYPE_FLV:
1094
_buffer += "Content-Type: video/x-flv\r\n";
1096
case DiskStream::FILETYPE_VP6:
1097
_buffer += "Content-Type: video/vp6\r\n";
1099
case DiskStream::FILETYPE_XML:
1100
_buffer += "Content-Type: application/xml\r\n";
1102
case DiskStream::FILETYPE_FLAC:
1103
_buffer += "Content-Type: audio/flac\r\n";
1105
case DiskStream::FILETYPE_PHP:
1106
_buffer += "Content-Type: application/x-httpd-php\r\n";
1109
_buffer += "Content-Type: text/html\r\n";
1116
HTTP::formatLastModified()
1118
// GNASH_REPORT_FUNCTION;
1119
boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
1120
std::stringstream date;
1122
boost::gregorian::date d(now.date());
1124
date << d.day_of_week();
1125
date << ", " << d.day();
1126
date << " " << d.month();
1127
date << " " << d.year();
1128
date << " " << now.time_of_day();
1131
return formatLastModified(date.str());
1135
HTTP::formatEchoResponse(const std::string &num, cygnal::Buffer &data)
1137
// GNASH_REPORT_FUNCTION;
1138
return formatEchoResponse(num, data.reference(), data.allocated());
1142
HTTP::formatEchoResponse(const std::string &num, boost::uint8_t *data, size_t size)
1144
// GNASH_REPORT_FUNCTION;
1146
//boost::uint8_t *tmpptr = data;
1148
// FIXME: temporary hacks while debugging
1149
cygnal::Buffer fixme("00 00 00 00 00 01");
1150
cygnal::Buffer fixme2("ff ff ff ff");
1152
_buffer = "HTTP/1.1 200 OK\r\n";
1153
formatContentType(DiskStream::FILETYPE_AMF);
1154
// formatContentLength(size);
1155
// FIXME: this is a hack ! Calculate a real size!
1156
formatContentLength(size+29);
1158
// Don't pretend to be the Red5 server
1159
formatServer("Cygnal (0.8.6)");
1161
// All HTTP messages are followed by a blank line.
1164
// Add the binary blob for the header
1167
// Make the result response, which is the 2nd data item passed in
1168
// the request, a slash followed by a number like "/2".
1169
string result = num;
1170
result += "/onResult";
1171
boost::shared_ptr<cygnal::Buffer> res = cygnal::AMF::encodeString(result);
1172
_buffer.append(res->begin()+1, res->size()-1);
1174
// Add the null data item
1175
boost::shared_ptr<cygnal::Buffer> null = cygnal::AMF::encodeString("null");
1176
_buffer.append(null->begin()+1, null->size()-1);
1178
// Add the other binary blob
1181
cygnal::Element::amf0_type_e type = static_cast<cygnal::Element::amf0_type_e>(*data);
1182
if ((type == cygnal::Element::UNSUPPORTED_AMF0)
1183
|| (type == cygnal::Element::NULL_AMF0)) {
1185
// Red5 returns a NULL object when it's recieved an undefined one in the echo_test
1186
} else if (type == cygnal::Element::UNDEFINED_AMF0) {
1187
_buffer += cygnal::Element::NULL_AMF0;
1189
// Add the AMF data we're echoing back
1191
_buffer.append(data, size);
1199
HTTP::formatRequest(const string &url, http_method_e cmd)
1201
// GNASH_REPORT_FUNCTION;
1206
case HTTP::HTTP_GET:
1209
case HTTP::HTTP_POST:
1212
case HTTP::HTTP_HEAD:
1215
case HTTP::HTTP_CONNECT:
1216
_buffer = "CONNECT ";
1218
case HTTP::HTTP_TRACE:
1221
case HTTP::HTTP_OPTIONS:
1222
_buffer = "OPTIONS ";
1229
_buffer += " HTTP/1.1";
1230
// sprintf(num, "%d.%d", _version.major, _version.minor);
1235
formatHost("localhost");
1236
formatAgent("Gnash");
1238
// Post messages want a bunch more fields than a GET request
1239
if (cmd == HTTP::HTTP_POST) {
1240
formatContentType(DiskStream::FILETYPE_AMF);
1241
formatEncoding("deflate, gzip, x-gzip, identity, *;q=0");
1242
formatConnection("Keep-Alive");
1245
// // All HTTP messages are followed by a blank line.
1246
// terminateHeader();
1253
HTTP::extractCommand(boost::uint8_t *data)
1255
// GNASH_REPORT_FUNCTION;
1257
// string body = reinterpret_cast<const char *>(data);
1258
HTTP::http_method_e cmd = HTTP::HTTP_NONE;
1260
// force the case to make comparisons easier
1261
// std::transform(body.begin(), body.end(), body.begin(),
1262
// (int(*)(int)) toupper);
1264
// Extract the command
1265
if (memcmp(data, "GET", 3) == 0) {
1266
cmd = HTTP::HTTP_GET;
1267
} else if (memcmp(data, "POST", 4) == 0) {
1268
cmd = HTTP::HTTP_POST;
1269
} else if (memcmp(data, "HEAD", 4) == 0) {
1270
cmd = HTTP::HTTP_HEAD;
1271
} else if (memcmp(data, "CONNECT", 7) == 0) {
1272
cmd = HTTP::HTTP_CONNECT;
1273
} else if (memcmp(data, "TRACE", 5) == 0) {
1274
cmd = HTTP::HTTP_TRACE;
1275
} else if (memcmp(data, "PUT", 3) == 0) {
1276
cmd = HTTP::HTTP_PUT;
1277
} else if (memcmp(data, "OPTIONS", 4) == 0) {
1278
cmd = HTTP::HTTP_OPTIONS;
1279
} else if (memcmp(data, "DELETE", 4) == 0) {
1280
cmd = HTTP::HTTP_DELETE;
1281
} else if (memcmp(data, "HTTP", 4) == 0) {
1282
cmd = HTTP::HTTP_RESPONSE;
1285
// For valid requests, the second argument, delimited by spaces
1286
// is the filespec of the file being requested or transmitted.
1287
if (cmd != HTTP::HTTP_NONE) {
1288
boost::uint8_t *start = std::find(data, data+7, ' ') + 1;
1289
boost::uint8_t *end = std::find(start + 2, data+PATH_MAX, ' ');
1290
boost::uint8_t *params = std::find(start, end, '?');
1291
if (params != end) {
1292
_params = std::string(params+1, end);
1293
_filespec = std::string(start, params);
1294
log_debug("Parameters for file: \"%s\"", _params);
1296
// This is fine as long as end is within the buffer.
1297
_filespec = std::string(start, end);
1299
// log_debug("Requesting file: \"%s\"", _filespec);
1301
// The third field is always the HTTP version
1302
// The version is the last field and is the protocol name
1303
// followed by a slash, and the version number. Note that
1304
// the version is not a double, even though it has a dot
1305
// in it. It's actually two separate integers.
1306
_version.major = *(end+6) - '0';
1307
_version.minor = *(end+8) - '0';
1308
// log_debug (_("Version: %d.%d"), _version.major, _version.minor);
1314
/// \brief Send a message to the other end of the network connection.
1315
///` Sends the contents of the _header and _body private data to
1316
/// the already opened network connection.
1318
/// @return The number of bytes sent
1322
GNASH_REPORT_FUNCTION;
1327
/// \brief Send a message to the other end of the network connection.
1328
///` Sends the contents of the _header and _body private data to
1329
/// the already opened network connection.
1331
/// @param fd The file descriptor to use for writing to the network.
1333
/// @return The number of bytes sent
1335
HTTP::sendMsg(int /* fd */)
1337
GNASH_REPORT_FUNCTION;
1342
/// \brief Send a message to the other end of the network connection.
1343
///` Sends the contents of the _header and _body private data to
1344
/// the already opened network connection.
1346
/// @param data A real pointer to the data.
1347
/// @param size The number of bytes of data stored.
1349
/// @return The number of bytes sent
1351
HTTP::sendMsg(const boost::uint8_t *data, size_t size)
1353
GNASH_REPORT_FUNCTION;
1356
return Network::writeNet(data, size);
1360
HTTP::recvChunked(boost::uint8_t *data, size_t size)
1362
// GNASH_REPORT_FUNCTION;
1372
// A chunked transfer sends a count of bytes in ASCII hex first,
1373
// and that line is terminated with the usual \r\n HTTP header field
1374
// line number. There is supposed to be a ';' before the \r\n, as this
1375
// field can have other attributes, but the OpenStreetMap server doesn't
1376
// use the semi-colon, as it's optional, and rarely used anyway.
1377
boost::shared_ptr<cygnal::Buffer> buf;
1378
boost::uint8_t *start = std::find(data, data+size, '\r') + 2;
1379
if (start != data+size) {
1380
// extract the total size of the chunk
1381
std::string bytes(data, start-2);
1382
size_t sizesize = start-data;
1383
total = static_cast<size_t>(strtol(bytes.c_str(), NULL, 16));
1384
log_debug("%s: Total size for first chunk is: %d, data size %d (%d)",
1385
__PRETTY_FUNCTION__, total, size, sizesize);
1386
buf.reset(new cygnal::Buffer(total+2));
1387
// Add the existing data from the previous packet
1388
buf->copy(data+sizesize, size-sizesize);
1391
// The size of the packet we need to read has a 2 byte terminator "\r\n",
1392
// like any other HTTP header field, so we have to read those bytes too
1393
// so we can stay sychronized with the start of each chunk.
1394
pktsize = total - buf->allocated() + 2;
1395
// log_debug("%s: Total Packet size for first chunk is: %d", __PRETTY_FUNCTION__,
1401
// Keep reading chunks as long as they arrive
1406
// Only read a few bytes, as we have todo a resize, so the less
1407
// data to copy the better. We only do this to get the total size
1408
// of the chunk, which we need to know when it's done. As we can't
1409
// tell we're done processing chunks till one has a length of
1410
// "0\r\n", this is important.
1412
buf.reset(new cygnal::Buffer(pktsize+2));
1414
ret = readNet(buf->reference() + buf->allocated(), pktsize, 60);
1418
log_debug("no data yet for fd #%d, continuing...", getFileFd());
1422
// manually set the seek pointer in the buffer, as we read
1423
// the data into the raw memory allocated to the buffer. We
1424
// only want to do this if we got data of course.
1425
buf->setSeekPointer(buf->end() + ret);
1427
start = std::find(buf->reference(), buf->reference()+ret, '\r') + 2;
1428
if (start != buf->reference()+ret) {
1429
// extract the total size of the chunk
1430
std::string bytes(buf->reference(), start-2);
1431
total = static_cast<size_t>(strtol(bytes.c_str(), NULL, 16));
1432
// The total size of the last chunk is always "0"
1434
log_debug("%s: end of chunks!", __PRETTY_FUNCTION__);
1439
pktsize = total+8; // FIXME: why do we need an 8 here ?
1440
// log_debug("%s: Total size for chunk is: %d (%s), adding %d bytes",
1441
// __PRETTY_FUNCTION__, total, bytes, (start - buf->reference()));
1442
cygnal::Buffer tmpbuf(start - buf->reference());
1443
// don't forget the two bytes for the "\r\n"
1444
tmpbuf.copy(buf->reference() + bytes.size() + 2, (start - buf->reference()));
1445
buf->clear(); // FIXME: debug only
1447
buf->copy(tmpbuf.reference(), tmpbuf.size());
1452
// If we got less data than specified, we need to keep reading data
1453
if (ret < buf->size()) {
1458
// log_debug("Didn't get a full packet, reading more data");
1464
// log_debug("%s: finished packet, ret is %d, pktsize is %d\r\n%s", __PRETTY_FUNCTION__, ret, pktsize,
1465
// hexify(buf->reference(), buf->allocated(), true));
1467
// The last two bytes of the chunk are always "\r\n", which we remove so it
1468
// doesn't become part of the binary data being sent in the chunk.
1469
if ((*(buf->end()-2) == '\r') && (*(buf->end()-1) == '\n')) {
1470
*(buf->end()-2) = 0; // '\r'
1471
*(buf->end()-1) = 0; // '\n'
1472
buf->setSeekPointer(buf->end() - 2);
1476
done = false; // reset
1477
} // end of while chunks
1483
HTTP::recvMsg(int fd)
1485
// GNASH_REPORT_FUNCTION;
1486
return recvMsg(fd, 0);
1490
HTTP::recvMsg(int fd, size_t size)
1492
// GNASH_REPORT_FUNCTION;
1497
size = cygnal::NETBUFSIZE;
1500
log_debug("Starting to wait for data in net for fd #%d", fd);
1504
boost::shared_ptr<cygnal::Buffer> buf(new cygnal::Buffer(size));
1505
ret = net.readNet(fd, *buf, 5);
1506
// cerr << __PRETTY_FUNCTION__ << ret << " : " << (char *)buf->reference() << endl;
1508
// the read timed out as there was no data, but the socket is still open.
1510
log_debug("no data yet for fd #%d, continuing...", fd);
1513
// ret is "no position" when the socket is closed from the other end of the connection,
1515
if ((ret == static_cast<size_t>(string::npos)) || (static_cast<int>(ret) == -1)) {
1516
log_debug("socket for fd #%d was closed...", fd);
1519
// We got data. Resize the buffer if necessary.
1521
buf->setSeekPointer(buf->reference() + ret);
1522
// cerr << "XXXXX: " << (char *)buf->reference() << endl;
1523
if (ret < static_cast<int>(cygnal::NETBUFSIZE)) {
1524
// buf->resize(ret); FIXME: why does this corrupt
1531
// ret must be more than 0 here
1532
if (ret == buf->size()) {
1536
log_debug("no more data for fd #%d, exiting...", fd);
1539
if (static_cast<int>(ret) == -1) {
1540
log_debug("Handler done for fd #%d, can't read any data...", fd);
1545
// We're done. Notify the other threads the socket is closed, and tell them to die.
1546
log_debug("Done receiving data for fd #%d...", fd);
1554
// GNASH_REPORT_FUNCTION;
1556
boost::mutex::scoped_lock lock(stl_mutex);
1558
log_debug (_("==== The HTTP header breaks down as follows: ===="));
1559
log_debug (_("Filespec: %s"), _filespec.c_str());
1560
log_debug (_("Version: %d.%d"), _version.major, _version.minor);
1562
std::map<string, string>::const_iterator it;
1563
for (it = _fields.begin(); it != _fields.end(); ++it) {
1564
log_debug("Field: \"%s\" = \"%s\"", it->first, it->second);
1567
// Dump the RTMPT fields
1568
log_debug("RTMPT optional index is: ", _index);
1569
log_debug("RTMPT optional client ID is: ", _clientid);
1570
log_debug (_("==== ==== ===="));
1573
} // end of gnash namespace
1578
// indent-tabs-mode: t