// ---------------------------------------------------------------------------
// - 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 <Input*> (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 <Input*> (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);
}
}
syntax highlighted by Code2HTML, v. 0.9.1