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
22 #include "gnashconfig.h"
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>
39 #include <sys/types.h>
42 #include "GnashSystemIOHeaders.h" // read()
50 //#include "handler.h"
54 #include "diskstream.h"
56 // Cygnal specific headers
57 #include "http_server.h"
61 // Not POSIX, so best not rely on it if possible.
63 # define PATH_MAX 1024
66 using namespace gnash
;
69 static boost::mutex stl_mutex
;
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;
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
;
101 HTTPServer::processClientRequest(Handler
*hand
, int fd
, cygnal::Buffer
*buf
)
103 GNASH_REPORT_FUNCTION
;
105 cygnal::Buffer result
;
108 _cmd
= extractCommand(buf
->reference());
111 result
= processGetRequest(hand
, fd
, buf
);
113 case HTTP::HTTP_POST
:
114 result
= processPostRequest(fd
, buf
);
116 case HTTP::HTTP_HEAD
:
117 result
= processHeadRequest(fd
, buf
);
119 case HTTP::HTTP_CONNECT
:
120 result
= processConnectRequest(fd
, buf
);
122 case HTTP::HTTP_TRACE
:
123 result
= processTraceRequest(fd
, buf
);
125 case HTTP::HTTP_OPTIONS
:
126 result
= processOptionsRequest(fd
, buf
);
129 result
= processPutRequest(fd
, buf
);
131 case HTTP::HTTP_DELETE
:
132 result
= processDeleteRequest(fd
, buf
);
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
));
150 log_debug("FIXME: found filestream %s in cache!", _filespec
);
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
);
161 // Get the file size for the HTTPServer header
162 if (filestream
->getFileType() == DiskStream::FILETYPE_NONE
) {
163 formatErrorResponse(HTTPServer::NOT_FOUND
);
165 // cache.addPath(_filespec, filestream->getFilespec());
166 // cache.addFile(_filespec, filestream);
169 // Close the file but leave resident for now.
170 if (filestream
->fullyPopulated()) {
173 // cache.addFile(_filespec, filestream);
180 // A GET request asks the server to send a file to the client
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;
192 // log_debug("Que empty, net connection dropped for fd #%d", getFileFd());
193 log_debug("Que empty, net connection dropped for fd #%d", fd
);
199 processHeaderFields(buf
);
201 _docroot
= crcfile
.getDocumentRoot();
203 string url
= _docroot
+ _filespec
;
205 boost::shared_ptr
<DiskStream
> ds
= hand
->getDiskStream(fd
);
210 _diskstream
.reset(new DiskStream
);
211 log_network("New filestream %s", _filespec
);
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
);
220 // Get the file size for the HTTPServer header
221 if (_diskstream
->getFileType() == DiskStream::FILETYPE_NONE
) {
222 formatErrorResponse(HTTPServer::NOT_FOUND
);
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(),
246 size_t filesize
= _diskstream
->getFileSize();
247 // size_t bytes_read = 0;
251 #ifdef USE_STATS_CACHE
252 struct timespec start
;
253 clock_gettime (CLOCK_REALTIME
, &start
);
256 #ifdef USE_STATS_CACHE
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
;
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) {
287 log_debug("Que empty, net connection dropped for fd #%d", getFileFd());
290 // cerr << __FUNCTION__ << buf->allocated() << " : " << hexify(buf->reference(), buf->allocated(), true) << endl;
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
));
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
);
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
);
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");
318 boost::shared_ptr
<cygnal::Element
> el
= amf
.extractAMF(content
.reference(), content
.end());
319 el
->dump(); // FIXME: do something intelligent
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")) {
330 if (_filespec
== "/echo/gateway") {
334 string path
= _docroot
;
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);
344 vector
<boost::shared_ptr
<cygnal::Element
> > headers
= parseEchoRequest(*content
);
345 //boost::shared_ptr<cygnal::Element> &el0 = headers[0];
347 if (headers
.size() >= 4) {
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;
357 cygnal::Buffer
&reply
= formatHeader(_filetype
, _filesize
, HTTPServer::OK
);
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");
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");
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");
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");
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");
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");
425 HTTPServer::formatErrorResponse(http_status_e code
)
427 // GNASH_REPORT_FUNCTION;
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
);
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
451 formatContentLength(_filesize
);
452 formatConnection("close");
453 formatContentType(_filetype
);
455 // All HTTPServer messages are followed by a blank line.
462 HTTPServer::formatGetReply(http_status_e code
)
465 // GNASH_REPORT_FUNCTION;
467 return formatHeader(_filesize
, code
);
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());
483 if (_header
.str().size()) {
484 log_debug (_("Sent GET Reply"));
488 log_debug (_("Couldn't send GET Reply, no header data"));
496 HTTPServer::formatPostReply(rtmpt_cmd_e
/* code */)
498 GNASH_REPORT_FUNCTION
;
502 formatContentType(DiskStream::FILETYPE_AMF
);
503 // All HTTPServer messages are followed by a blank line.
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();
514 _handler
->pushout(buf
);
515 _handler
->notifyout();
516 log_debug (_("Sent GET Reply"));
517 return true; // Default to true
520 log_debug (_("Couldn't send POST Reply, no header data"));
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
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
);
559 headers
.push_back(el1
);
561 // Get the second name, which is a raw string, and not preceded by
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
);
582 // Get the last two pieces of data, which are both AMF encoded
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
);
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".
599 HTTPServer::formatEchoResponse(const std::string
&num
, cygnal::Element
&el
)
601 // GNASH_REPORT_FUNCTION;
602 boost::shared_ptr
<cygnal::Buffer
> data
;
605 if (el
.getType() == cygnal::Element::TYPED_OBJECT_AMF0
) {
606 nel
.makeTypedObject();
607 string name
= el
.getName();
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
);
624 return formatEchoResponse(num
, data
->reference(), data
->allocated());
628 HTTPServer::formatEchoResponse(const std::string
&num
, cygnal::Buffer
&data
)
630 // GNASH_REPORT_FUNCTION;
631 return formatEchoResponse(num
, data
.reference(), data
.allocated());
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.
657 // Add the binary blob for the header
660 // Make the result response, which is the 2nd data item passed in
661 // the request, a slash followed by a number like "/2".
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
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
)) {
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
;
682 // Add the AMF data we're echoing back
684 _buffer
.append(data
, size
);
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>
700 /// denotes the RTMPT request type, "OPEN", "SEND", "IDLE", "CLOSE")
702 /// specifies the id of the client that performs the requests
703 /// (only sent for established sessions)
705 /// is a consecutive number that seems to be used to detect missing packages
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
) {
725 start
= body
.find("SEND", 0);
726 if (start
!= string::npos
) {
729 start
= body
.find("IDLE", 0);
730 if (start
!= string::npos
) {
733 start
= body
.find("CLOSE", 0);
734 if (start
!= string::npos
) {
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
));
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;
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>
769 /// denotes the RTMPT request type, "OPEN", "SEND", "IDLE", "CLOSE")
771 /// specifies the id of the client that performs the requests
772 /// (only sent for established sessions)
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
));
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;
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
, '?');
868 _params
= std::string(params
+1, end
);
869 _filespec
= std::string(start
, params
);
870 log_debug("Parameters for file: \"%s\"", _params
);
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);
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") {
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
) {
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;
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
) {
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
);
966 _filespec
= i
->substr(start
+1, pos
-start
-2);
968 // log_debug("Requesting file: \"%s\"", _filespec);
970 // HTTP 1.1 enables persistent network connections
972 if (_version
.minor
> 0) {
973 log_debug("Enabling Keep Alive by default for HTTP > 1.0");
982 return buf
.reference() + end
+ 4;
989 // GNASH_REPORT_FUNCTION;
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
);
1017 log_network("FIXME: Existing data in packet!");
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
);
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
);
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);
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
;
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());
1058 cerr
<< "FIXME cache hit on: " << www
.getFilespec() << endl
;
1059 www
.writeNet(args
->netfd
, (boost::uint8_t *)response
.c_str(), response
.size());
1063 // Unless the Keep-Alive flag is set, this isn't a persisant network
1066 log_debug("Keep-Alive is off", keepAlive());
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
))));
1080 } // end of http_handler
1082 } // end of gnash namespace
1087 // indent-tabs-mode: t