#include
#include
#include
#include
#include
#include /* strcasecmp() */
#include /* strdup() */
#include /* free() */
// This is a binding to http_parser (http://github.com/ry/http-parser)
// The goal is to decouple sockets from parsing for more javascript-level
// agility. A Buffer is read from a socket and passed to parser.execute().
// The parser then issues callbacks with slices of the data
// parser.onMessageBegin
// parser.onPath
// parser.onBody
// ...
// No copying is performed when slicing the buffer, only small reference
// allocations.
namespace node {
using namespace v8;
static Persistent on_message_begin_sym;
static Persistent on_path_sym;
static Persistent on_query_string_sym;
static Persistent on_url_sym;
static Persistent on_fragment_sym;
static Persistent on_header_field_sym;
static Persistent on_header_value_sym;
static Persistent on_headers_complete_sym;
static Persistent on_body_sym;
static Persistent on_message_complete_sym;
static Persistent delete_sym;
static Persistent get_sym;
static Persistent head_sym;
static Persistent post_sym;
static Persistent put_sym;
static Persistent connect_sym;
static Persistent options_sym;
static Persistent trace_sym;
static Persistent copy_sym;
static Persistent lock_sym;
static Persistent mkcol_sym;
static Persistent move_sym;
static Persistent propfind_sym;
static Persistent proppatch_sym;
static Persistent unlock_sym;
static Persistent report_sym;
static Persistent mkactivity_sym;
static Persistent checkout_sym;
static Persistent merge_sym;
static Persistent unknown_method_sym;
static Persistent method_sym;
static Persistent status_code_sym;
static Persistent http_version_sym;
static Persistent version_major_sym;
static Persistent version_minor_sym;
static Persistent should_keep_alive_sym;
static Persistent upgrade_sym;
static struct http_parser_settings settings;
// Callback prototype for http_cb
#define DEFINE_HTTP_CB(name) \
static int name(http_parser *p) { \
Parser *parser = static_cast(p->data); \
Local cb_value = parser->handle_->Get(name##_sym); \
if (!cb_value->IsFunction()) return 0; \
Local cb = Local::Cast(cb_value); \
Local ret = cb->Call(parser->handle_, 0, NULL); \
if (ret.IsEmpty()) { \
parser->got_exception_ = true; \
return -1; \
} else { \
return 0; \
} \
}
// Callback prototype for http_data_cb
#define DEFINE_HTTP_DATA_CB(name) \
static int name(http_parser *p, const char *at, size_t length) { \
Parser *parser = static_cast(p->data); \
assert(parser->buffer_); \
Local cb_value = parser->handle_->Get(name##_sym); \
if (!cb_value->IsFunction()) return 0; \
Local cb = Local::Cast(cb_value); \
Local argv[3] = { Local::New(parser->buffer_->handle_) \
, Integer::New(at - parser->buffer_->data()) \
, Integer::New(length) \
}; \
Local ret = cb->Call(parser->handle_, 3, argv); \
assert(parser->buffer_); \
if (ret.IsEmpty()) { \
parser->got_exception_ = true; \
return -1; \
} else { \
return 0; \
} \
}
static inline Persistent
method_to_str(unsigned short m) {
switch (m) {
case HTTP_DELETE: return delete_sym;
case HTTP_GET: return get_sym;
case HTTP_HEAD: return head_sym;
case HTTP_POST: return post_sym;
case HTTP_PUT: return put_sym;
case HTTP_CONNECT: return connect_sym;
case HTTP_OPTIONS: return options_sym;
case HTTP_TRACE: return trace_sym;
case HTTP_COPY: return copy_sym;
case HTTP_LOCK: return lock_sym;
case HTTP_MKCOL: return mkcol_sym;
case HTTP_MOVE: return move_sym;
case HTTP_PROPFIND: return propfind_sym;
case HTTP_PROPPATCH: return proppatch_sym;
case HTTP_UNLOCK: return unlock_sym;
case HTTP_REPORT: return report_sym;
case HTTP_MKACTIVITY: return mkactivity_sym;
case HTTP_CHECKOUT: return checkout_sym;
case HTTP_MERGE: return merge_sym;
default: return unknown_method_sym;
}
}
class Parser : public ObjectWrap {
public:
Parser(enum http_parser_type type) : ObjectWrap() {
buffer_ = NULL;
Init(type);
}
~Parser() {
assert(buffer_ == NULL && "Destroying a parser while it's parsing");
}
DEFINE_HTTP_CB(on_message_begin)
DEFINE_HTTP_CB(on_message_complete)
DEFINE_HTTP_DATA_CB(on_path)
DEFINE_HTTP_DATA_CB(on_url)
DEFINE_HTTP_DATA_CB(on_fragment)
DEFINE_HTTP_DATA_CB(on_query_string)
DEFINE_HTTP_DATA_CB(on_header_field)
DEFINE_HTTP_DATA_CB(on_header_value)
DEFINE_HTTP_DATA_CB(on_body)
static int on_headers_complete(http_parser *p) {
Parser *parser = static_cast(p->data);
Local cb_value = parser->handle_->Get(on_headers_complete_sym);
if (!cb_value->IsFunction()) return 0;
Local cb = Local::Cast(cb_value);
Local message_info = Object::New();
// METHOD
if (p->type == HTTP_REQUEST) {
message_info->Set(method_sym, method_to_str(p->method));
}
// STATUS
if (p->type == HTTP_RESPONSE) {
message_info->Set(status_code_sym, Integer::New(p->status_code));
}
// VERSION
message_info->Set(version_major_sym, Integer::New(p->http_major));
message_info->Set(version_minor_sym, Integer::New(p->http_minor));
message_info->Set(should_keep_alive_sym,
http_should_keep_alive(p) ? True() : False());
message_info->Set(upgrade_sym, p->upgrade ? True() : False());
Local argv[1] = { message_info };
Local head_response = cb->Call(parser->handle_, 1, argv);
if (head_response.IsEmpty()) {
parser->got_exception_ = true;
return -1;
} else {
return head_response->IsTrue() ? 1 : 0;
}
}
static Handle New(const Arguments& args) {
HandleScope scope;
String::Utf8Value type(args[0]->ToString());
Parser *parser;
if (0 == strcasecmp(*type, "request")) {
parser = new Parser(HTTP_REQUEST);
} else if (0 == strcasecmp(*type, "response")) {
parser = new Parser(HTTP_RESPONSE);
} else {
return ThrowException(Exception::Error(
String::New("Constructor argument be 'request' or 'response'")));
}
parser->Wrap(args.This());
assert(!parser->buffer_);
return args.This();
}
// var bytesParsed = parser->execute(buffer, off, len);
static Handle Execute(const Arguments& args) {
HandleScope scope;
Parser *parser = ObjectWrap::Unwrap(args.This());
assert(!parser->buffer_);
if (parser->buffer_) {
return ThrowException(Exception::TypeError(
String::New("Already parsing a buffer")));
}
if (!Buffer::HasInstance(args[0])) {
return ThrowException(Exception::TypeError(
String::New("Argument should be a buffer")));
}
Buffer * buffer = ObjectWrap::Unwrap(args[0]->ToObject());
size_t off = args[1]->Int32Value();
if (off >= buffer->length()) {
return ThrowException(Exception::Error(
String::New("Offset is out of bounds")));
}
size_t len = args[2]->Int32Value();
if (off+len > buffer->length()) {
return ThrowException(Exception::Error(
String::New("Length is extends beyond buffer")));
}
// Assign 'buffer_' while we parse. The callbacks will access that varible.
parser->buffer_ = buffer;
parser->got_exception_ = false;
size_t nparsed =
http_parser_execute(&parser->parser_, &settings, buffer->data()+off, len);
// Unassign the 'buffer_' variable
assert(parser->buffer_);
parser->buffer_ = NULL;
// If there was an exception in one of the callbacks
if (parser->got_exception_) return Local();
Local nparsed_obj = Integer::New(nparsed);
// If there was a parse error in one of the callbacks
// TODO What if there is an error on EOF?
if (!parser->parser_.upgrade && nparsed != len) {
Local e = Exception::Error(String::NewSymbol("Parse Error"));
Local obj = e->ToObject();
obj->Set(String::NewSymbol("bytesParsed"), nparsed_obj);
return scope.Close(e);
} else {
return scope.Close(nparsed_obj);
}
}
static Handle Finish(const Arguments& args) {
HandleScope scope;
Parser *parser = ObjectWrap::Unwrap(args.This());
assert(!parser->buffer_);
parser->got_exception_ = false;
http_parser_execute(&(parser->parser_), &settings, NULL, 0);
if (parser->got_exception_) return Local();
return Undefined();
}
static Handle Reinitialize(const Arguments& args) {
HandleScope scope;
Parser *parser = ObjectWrap::Unwrap(args.This());
String::Utf8Value type(args[0]->ToString());
if (0 == strcasecmp(*type, "request")) {
parser->Init(HTTP_REQUEST);
} else if (0 == strcasecmp(*type, "response")) {
parser->Init(HTTP_RESPONSE);
} else {
return ThrowException(Exception::Error(
String::New("Argument be 'request' or 'response'")));
}
return Undefined();
}
private:
void Init (enum http_parser_type type) {
assert(buffer_ == NULL); // don't call this during Execute()
http_parser_init(&parser_, type);
parser_.data = this;
}
Buffer * buffer_; // The buffer currently being parsed.
bool got_exception_;
http_parser parser_;
};
static Handle UrlDecode (const Arguments& args) {
HandleScope scope;
if (!args[0]->IsString()) {
return ThrowException(Exception::TypeError(
String::New("First arg must be a string")));
}
bool decode_spaces = args[1]->IsTrue();
String::Utf8Value in_v(args[0]->ToString());
size_t l = in_v.length();
char* out = strdup(*in_v);
enum { CHAR, HEX0, HEX1 } state = CHAR;
int n, m, hexchar;
size_t in_index = 0, out_index = 0;
char c;
for (; in_index