// --------------------------------------------------------------------------- // - HttpResponse.cpp - // - afnix:nwg module - http response class implementation - // --------------------------------------------------------------------------- // - This program is free software; you can redistribute it and/or modify - // - it provided that this copyright notice is kept intact. - // - - // - This program is distributed in the hope that it will be useful, but - // - without any warranty; without even the implied warranty of - // - merchantability or fitness for a particular purpose. In no event shall - // - the copyright holder be liable for any direct, indirect, incidental or - // - special damages arising in any way out of the use of this software. - // --------------------------------------------------------------------------- // - copyright (c) 1999-2007 amaury darsch - // --------------------------------------------------------------------------- #include "Regex.hpp" #include "Vector.hpp" #include "Boolean.hpp" #include "Integer.hpp" #include "Utility.hpp" #include "Runnable.hpp" #include "QuarkZone.hpp" #include "Exception.hpp" #include "HttpResponse.hpp" namespace afnix { // ------------------------------------------------------------------------- // - private section - // ------------------------------------------------------------------------- // the http status code static const long HTTP_STAT_200 = 200; // OK static const long HTTP_STAT_201 = 201; // Created static const long HTTP_STAT_301 = 301; // Move Permanently static const long HTTP_STAT_302 = 302; // Found static const long HTTP_STAT_303 = 303; // See Other /// the status line regex static const String HTTP_RESP_RGX = "HTTP/1.$d$b+($d+)$b+$N+"; /// the content length property static const String HTTP_HEAD_LEN = "Content-Length"; /// the content type property static const String HTTP_HEAD_CTY = "Content-Type"; /// the location property static const String HTTP_HEAD_LOC = "Location"; /// the property regex static const String HTTP_PROP_RGX = "(<$a->+):$b+($N+)"; /// the media type regex static const String HTTP_MIME_RGX = "(<$a-+>+/<$a-+>+)[;$N+]?"; /// the charset regex static const String HTTP_CHAR_RGX = "$N+charset=(<$a->+)"; /// the default http media type static const String HTTP_MIME_DEF = "text/html"; /// the default http charset static const String HTTP_CHAR_DEF = "DEFAULT"; /// the default content length static const long HTTP_CLEN_DEF = -1; /// the default timeout - 10 minutes static const long HTTP_TOUT_DEF = 10 * 60 * 1000; // this procedure returns the response status code static long http_status_code (Input* is) { // check for an input stream if (is == nilp){ throw Exception ("htpp-error", "invalid nil input stream while reading status"); } // check for valid stream if (is->valid (HTTP_TOUT_DEF) == false) { throw Exception ("http-error", "http status timeout"); } // create a status regex Regex re = HTTP_RESP_RGX; // get the status line String line = is->readln (); if (re == line) { String code = re.getstr (0); return Utility::tointeger (code); } throw Exception ("http-error", "invalid http status line", line); } // this procedure read the http header and update the property list static void http_read_header (Plist& head, Input* is) { // do nothing without a stream if (is == nilp) return; // create a property regex Regex re = HTTP_PROP_RGX; // loop while valid while (is->valid (HTTP_TOUT_DEF) == true) { // get the header line String line = is->readln (); // check for last empty line if (line.isnil () == true) break; // check for valid header if (re == line) { String name = re.getstr (0); String pval = re.getstr (1); head.set (name.strip (), pval.strip ()); } } } // this procedure extract the media type from the header static String http_get_media (const Plist& head) { // check for the content type property if (head.exists (HTTP_HEAD_CTY) == true) { // get the content type property String pval = head.getpval (HTTP_HEAD_CTY); // extract the content value Regex re = HTTP_MIME_RGX; if (re == pval) return re.getstr (0); } // by default we operate in byte mode return HTTP_MIME_DEF; } // this procedure returns true if the encoding mode is defined in the header static bool http_is_emod (const Plist& head) { // check for the content type property if (head.exists (HTTP_HEAD_CTY) == true) { // get the content type property String pval = head.getpval (HTTP_HEAD_CTY); // extract the content value Regex re = HTTP_CHAR_RGX; return (re == pval); } // not defined return false; } // this procedure extract the character set from the header static String http_get_emod (const Plist& head) { // check for the content type property if (head.exists (HTTP_HEAD_CTY) == true) { // get the content type property String pval = head.getpval (HTTP_HEAD_CTY); // extract the content value Regex re = HTTP_CHAR_RGX; if (re == pval) return re.getstr (0); } // by default we operate in byte mode return HTTP_CHAR_DEF; } // return the header response content length static long http_get_length (const Plist& head) { // check for a content lenth property if (head.exists (HTTP_HEAD_LEN) == true) { // get the content length property String pval = head.getpval (HTTP_HEAD_LEN); // convert to an integer return Utility::tointeger(pval); } // no length specified return HTTP_CLEN_DEF; } // ------------------------------------------------------------------------- // - class section - // ------------------------------------------------------------------------- // create a default response HttpResponse::HttpResponse (void) { p_is = nilp; reset (); } // create a http response by stream HttpResponse::HttpResponse (Input* is){ p_is = nilp; setis (is); } // destroy this http response HttpResponse::~HttpResponse (void) { Object::dref (p_is); } // return the class name String HttpResponse::repr (void) const { return "HttpResponse"; } // reset the response and reset void HttpResponse::reset (void) { wrlock (); try { // reset the stream buffer d_buffer.reset (); // reset the response header d_head.reset (); d_code = 0; // reset the local data d_clen = HTTP_CLEN_DEF; d_ccnt = 0; // unlock and return unlock (); } catch (...) { Object::dref (p_is); p_is = nilp; unlock (); throw; } } // set a new input stream and rescan void HttpResponse::setis (Input* is) { wrlock (); try { // reset everything reset (); // set the new stream Object::iref (is); Object::dref (p_is); p_is = is; // reset the encoding mode if (p_is != nilp) { settmod (p_is->gettmod ()); setemod (p_is->getemod ()); } // read the header status d_code = http_status_code (p_is); // read the header http_read_header (d_head, p_is); // update the character set setemod (http_get_emod (d_head)); // update the content length d_clen = http_get_length (d_head); // here we are ready to read unlock (); } catch (...) { Object::dref (p_is); p_is = nilp; unlock (); throw; } } // get the header length long HttpResponse::hlength (void) const { rdlock (); try { long result = d_head.length (); unlock (); return result; } catch (...) { unlock (); throw; } } // return true if a header property exists bool HttpResponse::hexists (const String& name) const { rdlock (); try { bool result = d_head.exists (name); unlock (); return result; } catch (...) { unlock (); throw; } } // get a header property by index Property* HttpResponse::hget (const long index) const { rdlock (); try { Property* result = d_head.get (index); unlock (); return result; } catch (...) { unlock (); throw; } } // get a header property by name Property* HttpResponse::hfind (const String& name) const { rdlock (); try { Property* result = d_head.find (name); unlock (); return result; } catch (...) { unlock (); throw; } } // get a header property by name or throw an exception Property* HttpResponse::hlookup (const String& name) const { rdlock (); try { Property* result = d_head.lookup (name); unlock (); return result; } catch (...) { unlock (); throw; } } // get a header property value by name String HttpResponse::hgetval (const String& name) const { rdlock (); try { String result = d_head.getpval (name); unlock (); return result; } catch (...) { unlock (); throw; } } // return the status code long HttpResponse::getcode (void) const { rdlock (); long result = d_code; unlock (); return result; } // return the media type String HttpResponse::getmedia (void) const { rdlock (); try { String result = http_get_media (d_head); unlock (); return result; } catch (...) { unlock (); throw; } } // return true if the status code is ok bool HttpResponse::isok (void) const { rdlock (); bool status = (d_code == HTTP_STAT_200); unlock (); return status; } // return true if the encoding mode is defined in the header bool HttpResponse::isemod (void) const { rdlock (); try { bool result = http_is_emod (d_head); unlock (); return result; } catch (...) { unlock (); throw; } } // return true if there is something at another location bool HttpResponse::ishloc (void) const { rdlock (); try { // check first for a location property if (hexists (HTTP_HEAD_LOC) == false) { unlock (); return false; } // check the status code bool status = false; switch (d_code) { case HTTP_STAT_201: case HTTP_STAT_301: case HTTP_STAT_302: case HTTP_STAT_303: status = true; break; default: status = false; break; } unlock (); return status; } catch (...) { unlock (); throw; } } // return the header location value String HttpResponse::gethloc (void) const { rdlock (); try { String result = hgetval (HTTP_HEAD_LOC); unlock (); return result; } catch (...) { unlock (); throw; } } // return the stream descriptor int HttpResponse::getsid (void) const { rdlock (); try { int result = (p_is == nilp) ? Input::getsid () : p_is->getsid (); unlock (); return result; } catch (...) { Object::dref (p_is); unlock (); throw; } } // return the next available character char HttpResponse::read (void) { wrlock (); try { // check for content length if ((d_clen >= 0) && (d_ccnt >= d_clen)) { unlock (); return eofc; } // check the local pushback buffer if (d_buffer.empty () == false) { char result = d_buffer.read (); d_ccnt++; unlock (); return result; } // check for nil stream if (p_is == nilp) { unlock (); return eofc; } // read the base stream char result = p_is->read (); d_ccnt++; unlock (); return result; } catch (...) { unlock (); throw; } } // return true if we are at the eof bool HttpResponse::iseof (void) const { rdlock (); try { // check for content length if ((d_clen >= 0) && (d_ccnt >= d_clen)) { unlock (); return true; } // check the local pushback buffer if (d_buffer.empty () == false) { unlock (); return false; } // check for nil stream if (p_is == nilp) { unlock (); return true; } // read the stream status bool result = p_is->iseof (); unlock (); return result; } catch (...) { unlock (); throw; } } // check if we can read a character bool HttpResponse::valid (const long tout) const { rdlock (); try { // check for content length if ((d_clen >= 0) && (d_ccnt >= d_clen)) { unlock (); return false; } // check the local pushback buffer if (d_buffer.empty () == false) { unlock (); return true; } // check for nil stream if (p_is == nilp) { unlock (); return false; } // read the stream status bool result = p_is->valid (tout); unlock (); return result; } catch (...) { unlock (); throw; } } // pushback a character void HttpResponse::pushback (const char value) { wrlock (); try { if (p_is == nilp) { long blen = d_buffer.length (); Input::pushback (value); d_ccnt -= (d_buffer.length () - blen); } else { long blen = p_is->buflen (); p_is->pushback (value); d_ccnt -= (p_is->buflen () - blen); } if (d_ccnt < 0) d_ccnt = 0; unlock (); } catch (...) { unlock (); throw; } } // pushback a c-string on this stream void HttpResponse::pushback (const char* s) { wrlock (); try { if (p_is == nilp) { long blen = d_buffer.length (); Input::pushback (s); d_ccnt -= (d_buffer.length () - blen); } else { long blen = p_is->buflen (); p_is->pushback (s); d_ccnt -= (p_is->buflen () - blen); } if (d_ccnt < 0) d_ccnt = 0; unlock (); } catch (...) { unlock (); throw; } } // pushback a c-string on this stream void HttpResponse::pushback (const char* s, const long size) { wrlock (); try { if (p_is == nilp) { long blen = d_buffer.length (); Input::pushback (s, size); d_ccnt -= (d_buffer.length () - blen); } else { long blen = p_is->buflen (); p_is->pushback (s, size); d_ccnt -= (p_is->buflen () - blen); } if (d_ccnt < 0) d_ccnt = 0; unlock (); } catch (...) { unlock (); throw; } } // pushback a string on this stream void HttpResponse::pushback (const String& s) { wrlock (); try { if (p_is == nilp) { long blen = d_buffer.length (); Input::pushback (s); d_ccnt -= (d_buffer.length () - blen); } else { long blen = p_is->buflen (); p_is->pushback (s); d_ccnt -= (p_is->buflen () - blen); } if (d_ccnt < 0) d_ccnt = 0; unlock (); } catch (...) { unlock (); throw; } } // ------------------------------------------------------------------------- // - object section - // ------------------------------------------------------------------------- // the quark zone static const long QUARK_ZONE_LENGTH = 13; static QuarkZone zone (QUARK_ZONE_LENGTH); // the object supported quarks static const long QUARK_OKP = zone.intern ("ok-p"); static const long QUARK_EMODP = zone.intern ("encoding-mode-p"); static const long QUARK_LOCP = zone.intern ("location-p"); static const long QUARK_HGET = zone.intern ("header-get"); static const long QUARK_HFIND = zone.intern ("header-find"); static const long QUARK_RESET = zone.intern ("reset"); static const long QUARK_SETIS = zone.intern ("set-input-stream"); static const long QUARK_HLENGTH = zone.intern ("header-length"); static const long QUARK_HEXISTP = zone.intern ("header-exists-p"); static const long QUARK_HLOOKUP = zone.intern ("header-lookup"); static const long QUARK_HGETVAL = zone.intern ("header-get-value"); static const long QUARK_GETCODE = zone.intern ("get-status-code"); static const long QUARK_GETHLOC = zone.intern ("get-location"); static const long QUARK_GETMEDIA = zone.intern ("get-media-type"); // create a new object in a generic way Object* HttpResponse::mknew (Vector* argv) { long argc = (argv == nilp) ? 0 : argv->length (); // check for 0 argument if (argc == 0) return new HttpResponse; // check for 1 argument if (argc == 1) { Object* obj = argv->get (0); Input* is = dynamic_cast (obj); if (is == nilp) { throw Exception ("type-error", "invalid object for http response constructor", Object::repr (obj)); } return new HttpResponse (is); } throw Exception ("argument-error", "too many arguments with http response constructor"); } // return true if the given quark is defined bool HttpResponse::isquark (const long quark, const bool hflg) const { rdlock (); if (zone.exists (quark) == true) { unlock (); return true; } bool result = hflg ? Input::isquark (quark, hflg) : false; unlock (); return result; } // apply this object with a set of arguments and a quark Object* HttpResponse::apply (Runnable* robj, Nameset* nset, const long quark, Vector* argv) { // get the number of arguments long argc = (argv == nilp) ? 0 : argv->length (); // dispatch 0 argument if (argc == 0) { if (quark == QUARK_OKP) return new Boolean (isok ()); if (quark == QUARK_EMODP) return new Boolean (isemod ()); if (quark == QUARK_LOCP) return new Boolean (ishloc ()); if (quark == QUARK_HLENGTH) return new Integer (hlength ()); if (quark == QUARK_GETCODE) return new Integer (getcode ()); if (quark == QUARK_GETHLOC) return new String (gethloc ()); if (quark == QUARK_GETMEDIA) return new String (getmedia ()); if (quark == QUARK_RESET) { reset (); return nilp; } } // dispatch 1 argument if (argc == 1) { if (quark == QUARK_SETIS) { Object* obj = argv->get (0); Input* is = dynamic_cast (obj); if (is == nilp) { throw Exception ("type-error", "invalid http response input stream object", Object::repr (obj)); } setis (is); return nilp; } if (quark == QUARK_HEXISTP) { String name = argv->getstring (0); return new Boolean (hexists (name)); } if (quark == QUARK_HGETVAL) { String name = argv->getstring (0); return new String (hgetval (name)); } if (quark == QUARK_HFIND) { rdlock (); try { String name = argv->getstring (0); Object* result = hfind (name); robj->post (result); unlock (); return result; } catch (...) { unlock (); throw; } } if (quark == QUARK_HLOOKUP) { rdlock (); try { String name = argv->getstring (0); Object* result = hlookup (name); robj->post (result); unlock (); return result; } catch (...) { unlock (); throw; } } if (quark == QUARK_HGET) { long index = argv->getint (0); rdlock(); try { Object* result = hget (index); robj->post (result); unlock (); return result; } catch (...) { unlock (); throw; } } } // call the input method return Input::apply (robj, nset, quark, argv); } }