don't build the Framebuffer GUI for BSD when --enable-gui=all is used
[gnash.git] / cygnal / http_server.cpp
blob026490f34dd5432a6b4775e36f318ecf634dafa6
1 // http.cpp: HyperText Transport Protocol handler for Cygnal, for Gnash.
2 //
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
4 // Foundation, Inc
5 //
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.
10 //
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 #ifdef HAVE_CONFIG_H
22 #include "gnashconfig.h"
23 #endif
25 #include <boost/thread/mutex.hpp>
26 #include <boost/shared_ptr.hpp>
27 #include <boost/shared_array.hpp>
28 #include <boost/scoped_array.hpp>
29 #include <boost/tokenizer.hpp>
30 #include <boost/date_time/posix_time/posix_time.hpp>
31 #include <boost/date_time/gregorian/gregorian.hpp>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <fcntl.h>
35 #include <string>
36 #include <iostream>
37 #include <cstring>
38 #include <sstream>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <algorithm>
42 #include "GnashSystemIOHeaders.h" // read()
44 #include "amf.h"
45 #include "element.h"
46 #include "cque.h"
47 #include "log.h"
48 #include "crc.h"
49 #include "network.h"
50 //#include "handler.h"
51 #include "utility.h"
52 #include "buffer.h"
53 #include "http.h"
54 #include "diskstream.h"
56 // Cygnal specific headers
57 #include "http_server.h"
58 #include "proc.h"
59 #include "cache.h"
61 // Not POSIX, so best not rely on it if possible.
62 #ifndef PATH_MAX
63 # define PATH_MAX 1024
64 #endif
66 using namespace gnash;
67 using namespace std;
69 static boost::mutex stl_mutex;
71 namespace cygnal
74 // The rcfile is loaded and parsed here:
75 static CRcInitFile& crcfile = CRcInitFile::getDefaultInstance();
76 static Cache& cache = Cache::getDefaultInstance();
77 // static Proc& cgis = Proc::getDefaultInstance();
79 HTTPServer::HTTPServer()
81 // GNASH_REPORT_FUNCTION;
84 HTTPServer::~HTTPServer()
86 // GNASH_REPORT_FUNCTION;
89 HTTP::http_method_e
90 HTTPServer::processClientRequest(int fd)
92 GNASH_REPORT_FUNCTION;
94 cygnal::Buffer *buf = new cygnal::Buffer;
96 // return processClientRequest(fd, buf);
97 return HTTP::HTTP_NONE;
100 HTTP::http_method_e
101 HTTPServer::processClientRequest(Handler *hand, int fd, cygnal::Buffer *buf)
103 GNASH_REPORT_FUNCTION;
105 cygnal::Buffer result;
107 if (buf) {
108 _cmd = extractCommand(buf->reference());
109 switch (_cmd) {
110 case HTTP::HTTP_GET:
111 result = processGetRequest(hand, fd, buf);
112 break;
113 case HTTP::HTTP_POST:
114 result = processPostRequest(fd, buf);
115 break;
116 case HTTP::HTTP_HEAD:
117 result = processHeadRequest(fd, buf);
118 break;
119 case HTTP::HTTP_CONNECT:
120 result = processConnectRequest(fd, buf);
121 break;
122 case HTTP::HTTP_TRACE:
123 result = processTraceRequest(fd, buf);
124 break;
125 case HTTP::HTTP_OPTIONS:
126 result = processOptionsRequest(fd, buf);
127 break;
128 case HTTP::HTTP_PUT:
129 result = processPutRequest(fd, buf);
130 break;
131 case HTTP::HTTP_DELETE:
132 result = processDeleteRequest(fd, buf);
133 break;
134 default:
135 break;
139 #if 0
141 writeNet(fd, result);
143 _docroot = crcfile.getDocumentRoot();
145 string url = _docroot + _filespec;
147 // See if the file is in the cache and already opened.
148 boost::shared_ptr<DiskStream> filestream(cache.findFile(_filespec));
149 if (filestream) {
150 log_debug("FIXME: found filestream %s in cache!", _filespec);
151 filestream->dump();
152 } else {
153 filestream.reset(new DiskStream);
154 log_network("New filestream %s", _filespec);
155 // cache.addFile(url, filestream); FIXME: always reload from disk for now.
157 // Oopen the file and read the first chunk into memory
158 if (filestream->open(url)) {
159 formatErrorResponse(HTTPServer::NOT_FOUND);
160 } else {
161 // Get the file size for the HTTPServer header
162 if (filestream->getFileType() == DiskStream::FILETYPE_NONE) {
163 formatErrorResponse(HTTPServer::NOT_FOUND);
164 } else {
165 // cache.addPath(_filespec, filestream->getFilespec());
166 // cache.addFile(_filespec, filestream);
169 // Close the file but leave resident for now.
170 if (filestream->fullyPopulated()) {
171 filestream->close();
173 // cache.addFile(_filespec, filestream);
175 #endif
177 return _cmd;
180 // A GET request asks the server to send a file to the client
181 cygnal::Buffer &
182 HTTPServer::processGetRequest(Handler *hand, int fd, cygnal::Buffer *buf)
184 GNASH_REPORT_FUNCTION;
186 // cerr << "QUE = " << _que.size() << endl;
188 // cerr << "YYYYYYY: " << (char *)buf->reference() << endl;
189 // cerr << hexify(buf->reference(), buf->allocated(), false) << endl;
191 if (buf == 0) {
192 // log_debug("Que empty, net connection dropped for fd #%d", getFileFd());
193 log_debug("Que empty, net connection dropped for fd #%d", fd);
194 cygnal::Buffer buf;
195 return buf;
198 clearHeader();
199 processHeaderFields(buf);
201 _docroot = crcfile.getDocumentRoot();
203 string url = _docroot + _filespec;
205 boost::shared_ptr<DiskStream> ds = hand->getDiskStream(fd);
206 if (ds) {
207 _diskstream = ds;
209 if (!_diskstream) {
210 _diskstream.reset(new DiskStream);
211 log_network("New filestream %s", _filespec);
212 } else {
213 log_network("Reusing filestream %s", _filespec);
216 // Oopen the file and read the first chunk into memory
217 if (_diskstream->open(url)) {
218 formatErrorResponse(HTTPServer::NOT_FOUND);
219 } else {
220 // Get the file size for the HTTPServer header
221 if (_diskstream->getFileType() == DiskStream::FILETYPE_NONE) {
222 formatErrorResponse(HTTPServer::NOT_FOUND);
223 } else {
224 // cache.addPath(_filespec, filestream->getFilespec());
225 // cache.addFile(_filespec, filestream);
228 // Closing the file closes the disk file, but leaves data resident
229 // in memory for future access to this file. If we've been opened,
230 // the next operation is to start writing the file next time
231 // ::play() is called.
232 if (_diskstream->fullyPopulated()) {
233 _diskstream->close();
235 _diskstream->setState(DiskStream::PLAY);
236 // cache.addFile(_filespec, _diskstream);
238 // Create the reply message
239 // _close = true; Force sending the close connection in the header
240 cygnal::Buffer &reply = formatHeader(_diskstream->getFileType(),
241 _diskstream->getFileSize(),
242 HTTPServer::OK);
244 writeNet(fd, reply);
246 size_t filesize = _diskstream->getFileSize();
247 // size_t bytes_read = 0;
248 // int ret;
249 // size_t page = 0;
250 if (filesize) {
251 #ifdef USE_STATS_CACHE
252 struct timespec start;
253 clock_gettime (CLOCK_REALTIME, &start);
254 #endif
256 #ifdef USE_STATS_CACHE
257 struct timespec end;
258 clock_gettime (CLOCK_REALTIME, &end);
259 double time = (end.tv_sec - start.tv_sec) + ((end.tv_nsec - start.tv_nsec)/1e9);
260 cerr << "File " << _filespec
261 << " transferred " << filesize << " bytes in: " << fixed
262 << time << " seconds for net fd #" << fd << endl;
263 #endif
266 return reply;
269 // A POST request asks sends a data from the client to the server. After processing
270 // the header like we normally do, we then read the amount of bytes specified by
271 // the "content-length" field, and then write that data to disk, or decode the amf.
272 boost::shared_ptr<cygnal::Buffer>
273 HTTPServer::processPostRequest(int fd, cygnal::Buffer *bufFIXME)
275 GNASH_REPORT_FUNCTION;
277 // cerr << "QUE1 = " << _que.size() << endl;
279 boost::shared_ptr<cygnal::Buffer> buf;
281 if (_que.size() == 0) {
282 return buf;
285 buf = _que.pop();
286 if (buf == 0) {
287 log_debug("Que empty, net connection dropped for fd #%d", getFileFd());
288 return buf;
290 // cerr << __FUNCTION__ << buf->allocated() << " : " << hexify(buf->reference(), buf->allocated(), true) << endl;
292 clearHeader();
293 boost::uint8_t *data = processHeaderFields(buf.get());
294 size_t length = strtol(getField("content-length").c_str(), NULL, 0);
295 boost::shared_ptr<cygnal::Buffer> content(new cygnal::Buffer(length));
296 int ret = 0;
297 if (buf->allocated() - (data - buf->reference()) ) {
298 // cerr << "Don't need to read more data: have " << buf->allocated() << " bytes" << endl;
299 content->copy(data, length);
300 ret = length;
301 } else {
302 // cerr << "Need to read more data, only have " << buf->allocated() << " bytes" << endl;
303 ret = readNet(fd, *content, 2);
304 data = content->reference();
307 if (getField("content-type") == "application/x-www-form-urlencoded") {
308 log_debug("Got file data in POST");
309 string url = _docroot + _filespec;
310 DiskStream ds(url, *content);
311 ds.writeToDisk();
312 // ds.close();
313 // oh boy, we got ourselves some encoded AMF objects instead of a boring file.
314 } else if (getField("content-type") == "application/x-amf") {
315 log_debug("Got AMF data in POST");
316 #if 0
317 amf::AMF amf;
318 boost::shared_ptr<cygnal::Element> el = amf.extractAMF(content.reference(), content.end());
319 el->dump(); // FIXME: do something intelligent
320 // with this Element
321 #endif
324 // Send the reply
326 // NOTE: this is a "special" path we trap until we have real CGI support
327 if ((getField("content-type") == "application/x-amf")
328 && (getField("content-type") == "application/x-amf")) {
329 #ifdef USE_CGIBIN
330 if (_filespec == "/echo/gateway") {
333 Proc cgis;
334 string path = _docroot;
335 path += _filespec;
336 cgis.startCGI(_filespec, true, CGIBIN_PORT);
337 cgis.createClient("localhost", CGIBIN_PORT);
338 cgis.writeNet(*content);
339 boost::shared_ptr<cygnal::Buffer> reply = cgis.readNet();
341 writeNet(fd, *reply);
342 // cgis.stopCGI(_filespec);
343 #else
344 vector<boost::shared_ptr<cygnal::Element> > headers = parseEchoRequest(*content);
345 //boost::shared_ptr<cygnal::Element> &el0 = headers[0];
347 if (headers.size() >= 4) {
348 if (headers[3]) {
349 cygnal::Buffer &reply = formatEchoResponse(headers[1]->getName(), *headers[3]);
350 // cerr << "FIXME 3: " << hexify(reply.reference(), reply.allocated(), true) << endl;
351 // cerr << "FIXME 3: " << hexify(reply.reference(), reply.allocated(), false) << endl;
352 writeNet(fd, reply);
355 #endif
356 } else {
357 cygnal::Buffer &reply = formatHeader(_filetype, _filesize, HTTPServer::OK);
358 writeNet(fd, reply);
361 return buf;
364 boost::shared_ptr<cygnal::Buffer>
365 HTTPServer::processPutRequest(int /* fd */, cygnal::Buffer */* buf */)
367 boost::shared_ptr<cygnal::Buffer> buf;
368 // GNASH_REPORT_FUNCTION;
369 log_unimpl("PUT request");
371 return buf;
374 boost::shared_ptr<cygnal::Buffer>
375 HTTPServer::processDeleteRequest(int /* fd */, cygnal::Buffer */* buf */)
377 // GNASH_REPORT_FUNCTION;
378 boost::shared_ptr<cygnal::Buffer> buf;
379 log_unimpl("DELETE request");
381 return buf;
384 boost::shared_ptr<cygnal::Buffer>
385 HTTPServer::processConnectRequest(int /* fd */, cygnal::Buffer */* buf */)
387 // GNASH_REPORT_FUNCTION;
388 boost::shared_ptr<cygnal::Buffer> buf;
389 log_unimpl("CONNECT request");
391 return buf;
394 boost::shared_ptr<cygnal::Buffer>
395 HTTPServer::processOptionsRequest(int /* fd */, cygnal::Buffer */* buf */)
397 // GNASH_REPORT_FUNCTION;
398 boost::shared_ptr<cygnal::Buffer> buf;
399 log_unimpl("OPTIONS request");
401 return buf;
404 boost::shared_ptr<cygnal::Buffer>
405 HTTPServer::processHeadRequest(int /* fd */, cygnal::Buffer */* buf */)
407 // GNASH_REPORT_FUNCTION;
408 boost::shared_ptr<cygnal::Buffer> buf;
409 log_unimpl("HEAD request");
411 return buf;
414 boost::shared_ptr<cygnal::Buffer>
415 HTTPServer::processTraceRequest(int /* fd */, cygnal::Buffer */* buf */)
417 // GNASH_REPORT_FUNCTION;
418 boost::shared_ptr<cygnal::Buffer> buf;
419 log_unimpl("TRACE request");
421 return buf;
424 cygnal::Buffer &
425 HTTPServer::formatErrorResponse(http_status_e code)
427 // GNASH_REPORT_FUNCTION;
429 char num[12];
430 // First build the message body, so we know how to set Content-Length
431 _buffer += "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n";
432 _buffer += "<html><head>\r\n";
433 _buffer += "<title>";
434 sprintf(num, "%d", code);
435 _buffer += num;
436 _buffer += " Not Found</title>\r\n";
437 _buffer += "</head><body>\r\n";
438 _buffer += "<h1>Not Found</h1>\r\n";
439 _buffer += "<p>The requested URL ";
440 _buffer += _filespec;
441 _buffer += " was not found on this server.</p>\r\n";
442 _buffer += "<hr>\r\n";
443 _buffer += "<address>Cygnal (GNU/Linux) Server at ";
444 _buffer += getField("host");
445 _buffer += " </address>\r\n";
446 _buffer += "</body></html>\r\n";
448 // First build the header
449 formatDate();
450 formatServer();
451 formatContentLength(_filesize);
452 formatConnection("close");
453 formatContentType(_filetype);
455 // All HTTPServer messages are followed by a blank line.
456 terminateHeader();
458 return _buffer;
461 cygnal::Buffer &
462 HTTPServer::formatGetReply(http_status_e code)
465 // GNASH_REPORT_FUNCTION;
467 return formatHeader(_filesize, code);
470 cygnal::Buffer &
471 HTTPServer::formatGetReply(size_t size, http_status_e code)
473 // GNASH_REPORT_FUNCTION;
475 formatHeader(size, code);
477 // int ret = Network::writeNet(_header.str());
478 // boost::uint8_t *ptr = (boost::uint8_t *)_body.str().c_str();
479 // buf->copy(ptr, _body.str().size());
480 // _handler->dump();
482 #if 0
483 if (_header.str().size()) {
484 log_debug (_("Sent GET Reply"));
485 return _buffer;
486 } else {
487 clearHeader();
488 log_debug (_("Couldn't send GET Reply, no header data"));
490 #endif
492 return _buffer;
495 cygnal::Buffer &
496 HTTPServer::formatPostReply(rtmpt_cmd_e /* code */)
498 GNASH_REPORT_FUNCTION;
500 formatDate();
501 formatServer();
502 formatContentType(DiskStream::FILETYPE_AMF);
503 // All HTTPServer messages are followed by a blank line.
504 terminateHeader();
505 return _buffer;
507 #if 0
508 formatHeader(_filesize, code);
509 boost::shared_ptr<cygnal::Buffer> buf = new cygnal::Buffer;
510 if (_header.str().size()) {
511 buf->resize(_header.str().size());
512 string str = _header.str();
513 buf->copy(str);
514 _handler->pushout(buf);
515 _handler->notifyout();
516 log_debug (_("Sent GET Reply"));
517 return true; // Default to true
518 } else {
519 clearHeader();
520 log_debug (_("Couldn't send POST Reply, no header data"));
522 #endif
524 return _buffer;
527 #ifndef USE_CGIBIN
528 // Parse an Echo Request message coming from the Red5 echo_test. This
529 // method should only be used for testing purposes.
530 vector<boost::shared_ptr<cygnal::Element > >
531 HTTPServer::parseEchoRequest(boost::uint8_t *data, size_t size)
533 // GNASH_REPORT_FUNCTION;
535 vector<boost::shared_ptr<cygnal::Element > > headers;
537 // skip past the header bytes, we don't care about them.
538 boost::uint8_t *tmpptr = data + 6;
540 boost::uint16_t length;
541 length = ntohs((*(boost::uint16_t *)tmpptr) & 0xffff);
542 tmpptr += sizeof(boost::uint16_t);
544 // Get the first name, which is a raw string, and not preceded by
545 // a type byte.
546 boost::shared_ptr<cygnal::Element > el1(new cygnal::Element);
548 // If the length of the name field is corrupted, then we get out of
549 // range quick, and corrupt memory. This is a bit of a hack, but
550 // reduces memory errors caused by some of the corrupted tes cases.
551 boost::uint8_t *endstr = std::find(tmpptr, tmpptr+length, '\0');
552 if (endstr != tmpptr+length) {
553 log_debug("Caught corrupted string! length was %d, null at %d",
554 length, endstr-tmpptr);
555 length = endstr-tmpptr;
557 el1->setName(tmpptr, length);
558 tmpptr += length;
559 headers.push_back(el1);
561 // Get the second name, which is a raw string, and not preceded by
562 // a type byte.
563 length = ntohs((*(boost::uint16_t *)tmpptr) & 0xffff);
564 tmpptr += sizeof(boost::uint16_t);
565 boost::shared_ptr<cygnal::Element > el2(new cygnal::Element);
567 // std::string name2(reinterpret_cast<const char *>(tmpptr), length);
568 // el2->setName(name2.c_str(), name2.size());
569 // If the length of the name field is corrupted, then we get out of
570 // range quick, and corrupt memory. This is a bit of a hack, but
571 // reduces memory errors caused by some of the corrupted tes cases.
572 endstr = std::find(tmpptr, tmpptr+length, '\0');
573 if (endstr != tmpptr+length) {
574 log_debug("Caught corrupted string! length was %d, null at %d",
575 length, endstr-tmpptr);
576 length = endstr-tmpptr;
578 el2->setName(tmpptr, length);
579 headers.push_back(el2);
580 tmpptr += length;
582 // Get the last two pieces of data, which are both AMF encoded
583 // with a type byte.
584 amf::AMF amf;
585 boost::shared_ptr<cygnal::Element> el3 = amf.extractAMF(tmpptr, tmpptr + size);
586 headers.push_back(el3);
587 tmpptr += amf.totalsize();
589 boost::shared_ptr<cygnal::Element> el4 = amf.extractAMF(tmpptr, tmpptr + size);
590 headers.push_back(el4);
592 return headers;
595 // format a response to the 'echo' test used for testing Gnash. This
596 // is only used for testing by developers. The format appears to be
597 // two strings, followed by a double, followed by the "onResult".
598 cygnal::Buffer &
599 HTTPServer::formatEchoResponse(const std::string &num, cygnal::Element &el)
601 // GNASH_REPORT_FUNCTION;
602 boost::shared_ptr<cygnal::Buffer> data;
604 cygnal::Element nel;
605 if (el.getType() == cygnal::Element::TYPED_OBJECT_AMF0) {
606 nel.makeTypedObject();
607 string name = el.getName();
608 nel.setName(name);
609 if (el.propertySize()) {
610 // FIXME: see about using std::reverse() instead.
611 for (int i=el.propertySize()-1; i>=0; i--) {
612 // for (int i=0 ; i<el.propertySize(); i++) {
613 boost::shared_ptr<cygnal::Element> child = el.getProperty(i);
614 nel.addProperty(child);
616 data = nel.encode();
617 } else {
618 data = el.encode();
620 } else {
621 data = el.encode();
624 return formatEchoResponse(num, data->reference(), data->allocated());
627 cygnal::Buffer &
628 HTTPServer::formatEchoResponse(const std::string &num, cygnal::Buffer &data)
630 // GNASH_REPORT_FUNCTION;
631 return formatEchoResponse(num, data.reference(), data.allocated());
634 cygnal::Buffer &
635 HTTPServer::formatEchoResponse(const std::string &num, boost::uint8_t *data, size_t size)
637 // GNASH_REPORT_FUNCTION;
639 //boost::uint8_t *tmpptr = data;
641 // FIXME: temporary hacks while debugging
642 cygnal::Buffer fixme("00 00 00 00 00 01");
643 cygnal::Buffer fixme2("ff ff ff ff");
645 _buffer = "HTTPServer/1.1 200 OK\r\n";
646 formatContentType(DiskStream::FILETYPE_AMF);
647 // formatContentLength(size);
648 // FIXME: this is a hack ! Calculate a real size!
649 formatContentLength(size+29);
651 // Don't pretend to be the Red5 server
652 formatServer("Cygnal (0.8.6)");
654 // All HTTPServer messages are followed by a blank line.
655 terminateHeader();
657 // Add the binary blob for the header
658 _buffer += fixme;
660 // Make the result response, which is the 2nd data item passed in
661 // the request, a slash followed by a number like "/2".
662 string result = num;
663 result += "/onResult";
664 boost::shared_ptr<cygnal::Buffer> res = amf::AMF::encodeString(result);
665 _buffer.append(res->begin()+1, res->size()-1);
667 // Add the null data item
668 boost::shared_ptr<cygnal::Buffer> null = amf::AMF::encodeString("null");
669 _buffer.append(null->begin()+1, null->size()-1);
671 // Add the other binary blob
672 _buffer += fixme2;
674 cygnal::Element::amf0_type_e type = static_cast<cygnal::Element::amf0_type_e>(*data);
675 if ((type == cygnal::Element::UNSUPPORTED_AMF0)
676 || (type == cygnal::Element::NULL_AMF0)) {
677 _buffer += type;
678 // Red5 returns a NULL object when it's recieved an undefined one in the echo_test
679 } else if (type == cygnal::Element::UNDEFINED_AMF0) {
680 _buffer += cygnal::Element::NULL_AMF0;
681 } else {
682 // Add the AMF data we're echoing back
683 if (size) {
684 _buffer.append(data, size);
688 return _buffer;
690 #endif
692 #if 0
693 /// These methods extract data from an RTMPT message. RTMP is an
694 /// extension to HTTP that adds commands to manipulate the
695 /// connection's persistance.
697 /// The URL to be opened has the following form:
698 /// http://server/<comand>/[<client>/]<index>
699 /// <command>
700 /// denotes the RTMPT request type, "OPEN", "SEND", "IDLE", "CLOSE")
701 /// <client>
702 /// specifies the id of the client that performs the requests
703 /// (only sent for established sessions)
704 /// <index>
705 /// is a consecutive number that seems to be used to detect missing packages
706 HTTP::rtmpt_cmd_e
707 HTTP::extractRTMPT(boost::uint8_t *data)
709 GNASH_REPORT_FUNCTION;
711 string body = reinterpret_cast<const char *>(data);
712 string tmp, cid, indx;
713 HTTP::rtmpt_cmd_e cmd;
715 // force the case to make comparisons easier
716 std::transform(body.begin(), body.end(), body.begin(),
717 (int(*)(int)) toupper);
718 string::size_type start, end;
720 // Extract the command first
721 start = body.find("OPEN", 0);
722 if (start != string::npos) {
723 cmd = HTTP::OPEN;
725 start = body.find("SEND", 0);
726 if (start != string::npos) {
727 cmd = HTTP::SEND;
729 start = body.find("IDLE", 0);
730 if (start != string::npos) {
731 cmd = HTTP::IDLE;
733 start = body.find("CLOSE", 0);
734 if (start != string::npos) {
735 cmd = HTTP::CLOSE;
738 // Extract the optional client id
739 start = body.find("/", start+1);
740 if (start != string::npos) {
741 end = body.find("/", start+1);
742 if (end != string::npos) {
743 indx = body.substr(end, body.size());
744 cid = body.substr(start, (end-start));
745 } else {
746 cid = body.substr(start, body.size());
750 _index = strtol(indx.c_str(), NULL, 0);
751 _clientid = strtol(cid.c_str(), NULL, 0);
752 end = body.find("\r\n", start);
753 // if (end != string::npos) {
754 // cmd = HTTP::CLOSE;
755 // }
757 return cmd;
760 #endif
762 /// These methods extract data from an RTMPT message. RTMP is an
763 /// extension to HTTPServer that adds commands to manipulate the
764 /// connection's persistance.
766 /// The URL to be opened has the following form:
767 /// http://server/<comand>/[<client>/]<index>
768 /// <command>
769 /// denotes the RTMPT request type, "OPEN", "SEND", "IDLE", "CLOSE")
770 /// <client>
771 /// specifies the id of the client that performs the requests
772 /// (only sent for established sessions)
773 /// <index>
774 /// is a consecutive number that seems to be used to detect missing packages
775 HTTPServer::rtmpt_cmd_e
776 HTTPServer::extractRTMPT(boost::uint8_t *data)
778 GNASH_REPORT_FUNCTION;
780 string body = reinterpret_cast<const char *>(data);
781 string tmp, cid, indx;
782 HTTPServer::rtmpt_cmd_e cmd;
784 // force the case to make comparisons easier
785 std::transform(body.begin(), body.end(), body.begin(),
786 (int(*)(int)) toupper);
787 string::size_type start, end;
789 // Extract the command first
790 start = body.find("OPEN", 0);
791 if (start != string::npos) {
792 cmd = HTTPServer::OPEN;
794 start = body.find("SEND", 0);
795 if (start != string::npos) {
796 cmd = HTTPServer::SEND;
798 start = body.find("IDLE", 0);
799 if (start != string::npos) {
800 cmd = HTTPServer::IDLE;
802 start = body.find("CLOSE", 0);
803 if (start != string::npos) {
804 cmd = HTTPServer::CLOSE;
807 // Extract the optional client id
808 start = body.find("/", start+1);
809 if (start != string::npos) {
810 end = body.find("/", start+1);
811 if (end != string::npos) {
812 indx = body.substr(end, body.size());
813 cid = body.substr(start, (end-start));
814 } else {
815 cid = body.substr(start, body.size());
819 _index = strtol(indx.c_str(), NULL, 0);
820 _clientid = strtol(cid.c_str(), NULL, 0);
821 end = body.find("\r\n", start);
822 // if (end != string::npos) {
823 // cmd = HTTPServer::CLOSE;
824 // }
826 return cmd;
829 #if 0
830 HTTPServer::http_method_e
831 HTTPServer::extractCommand(boost::uint8_t *data)
833 GNASH_REPORT_FUNCTION;
835 // string body = reinterpret_cast<const char *>(data);
836 HTTPServer::http_method_e cmd = HTTP::HTTP_NONE;
838 // force the case to make comparisons easier
839 // std::transform(body.begin(), body.end(), body.begin(),
840 // (int(*)(int)) toupper);
842 // Extract the command
843 if (memcmp(data, "GET", 3) == 0) {
844 cmd = HTTP::HTTP_GET;
845 } else if (memcmp(data, "POST", 4) == 0) {
846 cmd = HTTP::HTTP_POST;
847 } else if (memcmp(data, "HEAD", 4) == 0) {
848 cmd = HTTP::HTTP_HEAD;
849 } else if (memcmp(data, "CONNECT", 7) == 0) {
850 cmd = HTTP::HTTP_CONNECT;
851 } else if (memcmp(data, "TRACE", 5) == 0) {
852 cmd = HTTP::HTTP_TRACE;
853 } else if (memcmp(data, "PUT", 3) == 0) {
854 cmd = HTTP::HTTP_PUT;
855 } else if (memcmp(data, "OPTIONS", 4) == 0) {
856 cmd = HTTP::HTTP_OPTIONS;
857 } else if (memcmp(data, "DELETE", 4) == 0) {
858 cmd = HTTP::HTTP_DELETE;
861 // For valid requests, the second argument, delimited by spaces
862 // is the filespec of the file being requested or transmitted.
863 if (cmd != HTTP::HTTP_NONE) {
864 boost::uint8_t *start = std::find(data, data+7, ' ') + 1;
865 boost::uint8_t *end = std::find(start + 2, data+PATH_MAX, ' ');
866 boost::uint8_t *params = std::find(start, end, '?');
867 if (params != end) {
868 _params = std::string(params+1, end);
869 _filespec = std::string(start, params);
870 log_debug("Parameters for file: \"%s\"", _params);
871 } else {
872 // This is fine as long as end is within the buffer.
873 _filespec = std::string(start, end);
875 // log_debug("Requesting file: \"%s\"", _filespec);
877 // The third field is always the HTTP version
878 // The version is the last field and is the protocol name
879 // followed by a slash, and the version number. Note that
880 // the version is not a double, even though it has a dot
881 // in it. It's actually two separate integers.
882 _version.major = *(end+6) - '0';
883 _version.minor = *(end+8) - '0';
884 // log_debug (_("Version: %d.%d"), _version.major, _version.minor);
887 return cmd;
890 boost::uint8_t *
891 HTTPServer::processHeaderFields(cygnal::Buffer &buf)
893 // GNASH_REPORT_FUNCTION;
894 string head(reinterpret_cast<const char *>(buf.reference()));
896 // The end of the header block is always followed by a blank line
897 string::size_type end = head.find("\r\n\r\n", 0);
898 // head.erase(end, buf.size()-end);
899 Tok t(head, Sep("\r\n"));
900 for (Tok::iterator i = t.begin(); i != t.end(); ++i) {
901 string::size_type pos = i->find(":", 0);
902 if (pos != string::npos) {
903 string name = i->substr(0, pos);
904 string value = i->substr(pos+2, i->size());
905 std::transform(name.begin(), name.end(), name.begin(),
906 (int(*)(int)) tolower);
907 std::transform(value.begin(), value.end(), value.begin(),
908 (int(*)(int)) tolower);
909 _fields[name] = value;
910 if (name == "keep-alive") {
911 _keepalive = true;
912 if ((value != "on") && (value != "off")) {
913 _max_requests = strtol(value.c_str(), NULL, 0);
914 log_debug("Setting Max Requests for Keep-Alive to %d", _max_requests);
917 if (name == "connection") {
918 if (value.find("keep-alive", 0) != string::npos) {
919 _keepalive = true;
922 if (name == "content-length") {
923 _filesize = strtol(value.c_str(), NULL, 0);
924 log_debug("Setting Content Length to %d", _filesize);
926 if (name == "content-type") {
927 // This is the type used by flash when sending a AMF data via POST
928 if (value == "application/x-amf") {
929 // log_debug("Got AMF data in the POST request!");
930 _filetype = DiskStream::FILETYPE_AMF;
932 // This is the type used by wget when sending a file via POST
933 if (value == "application/x-www-form-urlencoded") {
934 // log_debug("Got file data in the POST request");
935 _filetype = DiskStream::FILETYPE_ENCODED;
937 log_debug("Setting Content Type to %d", _filetype);
940 // cerr << "FIXME: " << (void *)i << " : " << dec << end << endl;
941 } else {
942 const boost::uint8_t *cmd = reinterpret_cast<const boost::uint8_t *>(i->c_str());
943 if (extractCommand(const_cast<boost::uint8_t *>(cmd)) == HTTP::HTTP_NONE) {
944 break;
945 #if 1
946 } else {
947 log_debug("Got a request, parsing \"%s\"", *i);
948 string::size_type start = i->find(" ");
949 string::size_type params = i->find("?");
950 string::size_type pos = i->find("HTTP/");
951 if (pos != string::npos) {
952 // The version is the last field and is the protocol name
953 // followed by a slash, and the version number. Note that
954 // the version is not a double, even though it has a dot
955 // in it. It's actually two separate integers.
956 _version.major = i->at(pos+5) - '0';
957 _version.minor = i->at(pos+7) - '0';
958 // log_debug (_("Version: %d.%d"), _version.major, _version.minor);
959 // the filespec in the request is the middle field, deliminated
960 // by a space on each end.
961 if (params != string::npos) {
962 _params = i->substr(params+1, end);
963 _filespec = i->substr(start+1, params);
964 log_debug("Parameters for file: \"%s\"", _params);
965 } else {
966 _filespec = i->substr(start+1, pos-start-2);
968 // log_debug("Requesting file: \"%s\"", _filespec);
970 // HTTP 1.1 enables persistent network connections
971 // by default.
972 if (_version.minor > 0) {
973 log_debug("Enabling Keep Alive by default for HTTP > 1.0");
974 _keepalive = true;
978 #endif
982 return buf.reference() + end + 4;
984 #endif
986 void
987 HTTPServer::dump()
989 // GNASH_REPORT_FUNCTION;
990 if (_diskstream) {
991 _diskstream->dump();
995 bool
996 HTTPServer::http_handler(Handler *hand, int netfd, cygnal::Buffer *buf)
998 GNASH_REPORT_FUNCTION;
1000 // Handler *hand = reinterpret_cast<Handler *>(args->handler);
1001 // cygnal::Buffer *buf = args->buffer;
1002 // boost::shared_ptr<HTTPServer> www(new HTTPServer); // = hand->getHTTPHandler (args->netfd);
1004 string url, parameters;
1005 // by default, only look once unless changed later
1006 // www->setDocRoot(crcfile.getDocumentRoot());
1007 // log_network("Docroot for HTTP files is %s", crcfile.getDocumentRoot());
1008 log_network("Processing HTTP data for fd #%d", netfd);
1010 // Wait for data, and when we get it, process it.
1011 #ifdef USE_STATISTICS
1012 struct timespec start;
1013 clock_gettime (CLOCK_REALTIME, &start);
1014 #endif
1016 if (buf) {
1017 log_network("FIXME: Existing data in packet!");
1018 } else {
1019 log_network("FIXME: No existing data in packet!");
1020 // See if we have any messages waiting
1021 if (recvMsg(netfd) == 0) {
1022 log_debug("Net HTTP server failed to read from fd #%d...", netfd);
1023 return false;
1027 // Process incoming messages
1028 HTTP::http_method_e cmd = processClientRequest(hand, netfd, buf);
1029 if (cmd != HTTP::HTTP_GET) {
1030 log_debug("No active DiskStreams for fd #%d: %s...", netfd, _filespec);
1031 } else {
1032 if (_diskstream) {
1033 log_debug("Found active DiskStream! for fd #%d: %s", netfd, _filespec);
1034 hand->setDiskStream(netfd, _diskstream);
1035 cache.addFile(_filespec, _diskstream);
1036 // Send the first chunk of the file to the client.
1037 // log_network("Sending first chunk of %s", _filespec);
1038 _diskstream->play(netfd, false);
1042 // www->dump();
1043 if ((getField("content-type") == "application/x-amf")
1044 && (getField("content-type") == "application/x-amf")
1045 && (getFilespec() == "/echo/gateway")) {
1046 cerr << "GOT A GATEWAY REQUEST" << endl;
1049 #if 0
1050 string response = cache.findResponse(filestream->getFilespec());
1051 if (response.empty()) {
1052 cerr << "FIXME no cache hit for: " << www.getFilespec() << endl;
1053 // www.clearHeader();
1054 // cygnal::Buffer &ss = www.formatHeader(filestream->getFileSize(), HTTP::LIFE_IS_GOOD);
1055 // www.writeNet(args->netfd, (boost::uint8_t *)www.getHeader().c_str(), www.getHeader().size());
1056 // cache.addResponse(www.getFilespec(), www.getHeader());
1057 } else {
1058 cerr << "FIXME cache hit on: " << www.getFilespec() << endl;
1059 www.writeNet(args->netfd, (boost::uint8_t *)response.c_str(), response.size());
1061 #endif
1063 // Unless the Keep-Alive flag is set, this isn't a persisant network
1064 // connection.
1065 if (!keepAlive()) {
1066 log_debug("Keep-Alive is off", keepAlive());
1067 } else {
1068 log_debug("Keep-Alive is on", keepAlive());
1070 #ifdef USE_STATISTICS
1071 struct timespec end;
1072 clock_gettime (CLOCK_REALTIME, &end);
1073 log_debug("Processing time for GET request was %f seconds",
1074 static_cast<float>(((end.tv_sec - start.tv_sec) +
1075 ((end.tv_nsec - start.tv_nsec)/1e9))));
1076 #endif
1078 return keepAlive();
1080 } // end of http_handler
1082 } // end of gnash namespace
1085 // local Variables:
1086 // mode: C++
1087 // indent-tabs-mode: t
1088 // End: