1
0
Fork 0
mirror of https://github.com/puma/puma.git synced 2022-11-09 13:48:40 -05:00
puma--puma/ext/puma_http11/puma_http11.c
Olle Jonsson fce04921c8
Fix typo in local #define name (#2502)
EXPLAIN_MAX_LENGTH_VALUE is only used right here, for quoting.
2020-12-01 06:46:57 -07:00

484 lines
12 KiB
C

/**
* Copyright (c) 2005 Zed A. Shaw
* You can redistribute it and/or modify it under the same terms as Ruby.
* License 3-clause BSD
*/
#define RSTRING_NOT_MODIFIED 1
#include "ruby.h"
#include "ext_help.h"
#include <assert.h>
#include <string.h>
#include <ctype.h>
#include "http11_parser.h"
#ifndef MANAGED_STRINGS
#ifndef RSTRING_PTR
#define RSTRING_PTR(s) (RSTRING(s)->ptr)
#endif
#ifndef RSTRING_LEN
#define RSTRING_LEN(s) (RSTRING(s)->len)
#endif
#define rb_extract_chars(e, sz) (*sz = RSTRING_LEN(e), RSTRING_PTR(e))
#define rb_free_chars(e) /* nothing */
#endif
static VALUE eHttpParserError;
#define HTTP_PREFIX "HTTP_"
#define HTTP_PREFIX_LEN (sizeof(HTTP_PREFIX) - 1)
static VALUE global_request_method;
static VALUE global_request_uri;
static VALUE global_fragment;
static VALUE global_query_string;
static VALUE global_http_version;
static VALUE global_request_path;
/** Defines common length and error messages for input length validation. */
#define QUOTE(s) #s
#define EXPLAIN_MAX_LENGTH_VALUE(s) QUOTE(s)
#define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP element " # N " is longer than the " EXPLAIN_MAX_LENGTH_VALUE(length) " allowed length (was %d)"
/** Validates the max length of given input and throws an HttpParserError exception if over. */
#define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, MAX_##N##_LENGTH_ERR, len); }
/** Defines global strings in the init method. */
#define DEF_GLOBAL(N, val) global_##N = rb_str_new2(val); rb_global_variable(&global_##N)
/* Defines the maximum allowed lengths for various input elements.*/
#ifndef PUMA_QUERY_STRING_MAX_LENGTH
#define PUMA_QUERY_STRING_MAX_LENGTH (1024 * 10)
#endif
DEF_MAX_LENGTH(FIELD_NAME, 256);
DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
DEF_MAX_LENGTH(REQUEST_PATH, 8192);
DEF_MAX_LENGTH(QUERY_STRING, PUMA_QUERY_STRING_MAX_LENGTH);
DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
struct common_field {
const size_t len;
const char *name;
int raw;
VALUE value;
};
/*
* A list of common HTTP headers we expect to receive.
* This allows us to avoid repeatedly creating identical string
* objects to be used with rb_hash_aset().
*/
static struct common_field common_http_fields[] = {
# define f(N) { (sizeof(N) - 1), N, 0, Qnil }
# define fr(N) { (sizeof(N) - 1), N, 1, Qnil }
f("ACCEPT"),
f("ACCEPT_CHARSET"),
f("ACCEPT_ENCODING"),
f("ACCEPT_LANGUAGE"),
f("ALLOW"),
f("AUTHORIZATION"),
f("CACHE_CONTROL"),
f("CONNECTION"),
f("CONTENT_ENCODING"),
fr("CONTENT_LENGTH"),
fr("CONTENT_TYPE"),
f("COOKIE"),
f("DATE"),
f("EXPECT"),
f("FROM"),
f("HOST"),
f("IF_MATCH"),
f("IF_MODIFIED_SINCE"),
f("IF_NONE_MATCH"),
f("IF_RANGE"),
f("IF_UNMODIFIED_SINCE"),
f("KEEP_ALIVE"), /* Firefox sends this */
f("MAX_FORWARDS"),
f("PRAGMA"),
f("PROXY_AUTHORIZATION"),
f("RANGE"),
f("REFERER"),
f("TE"),
f("TRAILER"),
f("TRANSFER_ENCODING"),
f("UPGRADE"),
f("USER_AGENT"),
f("VIA"),
f("X_FORWARDED_FOR"), /* common for proxies */
f("X_REAL_IP"), /* common for proxies */
f("WARNING")
# undef f
};
static void init_common_fields(void)
{
unsigned i;
struct common_field *cf = common_http_fields;
char tmp[256]; /* MAX_FIELD_NAME_LENGTH */
memcpy(tmp, HTTP_PREFIX, HTTP_PREFIX_LEN);
for(i = 0; i < ARRAY_SIZE(common_http_fields); cf++, i++) {
if(cf->raw) {
cf->value = rb_str_new(cf->name, cf->len);
} else {
memcpy(tmp + HTTP_PREFIX_LEN, cf->name, cf->len + 1);
cf->value = rb_str_new(tmp, HTTP_PREFIX_LEN + cf->len);
}
rb_global_variable(&cf->value);
}
}
static VALUE find_common_field_value(const char *field, size_t flen)
{
unsigned i;
struct common_field *cf = common_http_fields;
for(i = 0; i < ARRAY_SIZE(common_http_fields); i++, cf++) {
if (cf->len == flen && !memcmp(cf->name, field, flen))
return cf->value;
}
return Qnil;
}
void http_field(puma_parser* hp, const char *field, size_t flen,
const char *value, size_t vlen)
{
VALUE f = Qnil;
VALUE v;
VALIDATE_MAX_LENGTH(flen, FIELD_NAME);
VALIDATE_MAX_LENGTH(vlen, FIELD_VALUE);
f = find_common_field_value(field, flen);
if (f == Qnil) {
/*
* We got a strange header that we don't have a memoized value for.
* Fallback to creating a new string to use as a hash key.
*/
size_t new_size = HTTP_PREFIX_LEN + flen;
assert(new_size < BUFFER_LEN);
memcpy(hp->buf, HTTP_PREFIX, HTTP_PREFIX_LEN);
memcpy(hp->buf + HTTP_PREFIX_LEN, field, flen);
f = rb_str_new(hp->buf, new_size);
}
while (vlen > 0 && isspace(value[vlen - 1])) vlen--;
/* check for duplicate header */
v = rb_hash_aref(hp->request, f);
if (v == Qnil) {
v = rb_str_new(value, vlen);
rb_hash_aset(hp->request, f, v);
} else {
/* if duplicate header, normalize to comma-separated values */
rb_str_cat2(v, ", ");
rb_str_cat(v, value, vlen);
}
}
void request_method(puma_parser* hp, const char *at, size_t length)
{
VALUE val = Qnil;
val = rb_str_new(at, length);
rb_hash_aset(hp->request, global_request_method, val);
}
void request_uri(puma_parser* hp, const char *at, size_t length)
{
VALUE val = Qnil;
VALIDATE_MAX_LENGTH(length, REQUEST_URI);
val = rb_str_new(at, length);
rb_hash_aset(hp->request, global_request_uri, val);
}
void fragment(puma_parser* hp, const char *at, size_t length)
{
VALUE val = Qnil;
VALIDATE_MAX_LENGTH(length, FRAGMENT);
val = rb_str_new(at, length);
rb_hash_aset(hp->request, global_fragment, val);
}
void request_path(puma_parser* hp, const char *at, size_t length)
{
VALUE val = Qnil;
VALIDATE_MAX_LENGTH(length, REQUEST_PATH);
val = rb_str_new(at, length);
rb_hash_aset(hp->request, global_request_path, val);
}
void query_string(puma_parser* hp, const char *at, size_t length)
{
VALUE val = Qnil;
VALIDATE_MAX_LENGTH(length, QUERY_STRING);
val = rb_str_new(at, length);
rb_hash_aset(hp->request, global_query_string, val);
}
void http_version(puma_parser* hp, const char *at, size_t length)
{
VALUE val = rb_str_new(at, length);
rb_hash_aset(hp->request, global_http_version, val);
}
/** Finalizes the request header to have a bunch of stuff that's
needed. */
void header_done(puma_parser* hp, const char *at, size_t length)
{
hp->body = rb_str_new(at, length);
}
void HttpParser_free(void *data) {
TRACE();
if(data) {
xfree(data);
}
}
void HttpParser_mark(void *ptr) {
puma_parser *hp = ptr;
if(hp->request) rb_gc_mark(hp->request);
if(hp->body) rb_gc_mark(hp->body);
}
const rb_data_type_t HttpParser_data_type = {
"HttpParser",
{ HttpParser_mark, HttpParser_free, 0 },
0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
};
VALUE HttpParser_alloc(VALUE klass)
{
puma_parser *hp = ALLOC_N(puma_parser, 1);
TRACE();
hp->http_field = http_field;
hp->request_method = request_method;
hp->request_uri = request_uri;
hp->fragment = fragment;
hp->request_path = request_path;
hp->query_string = query_string;
hp->http_version = http_version;
hp->header_done = header_done;
hp->request = Qnil;
puma_parser_init(hp);
return TypedData_Wrap_Struct(klass, &HttpParser_data_type, hp);
}
/**
* call-seq:
* parser.new -> parser
*
* Creates a new parser.
*/
VALUE HttpParser_init(VALUE self)
{
puma_parser *http = NULL;
DATA_GET(self, puma_parser, &HttpParser_data_type, http);
puma_parser_init(http);
return self;
}
/**
* call-seq:
* parser.reset -> nil
*
* Resets the parser to it's initial state so that you can reuse it
* rather than making new ones.
*/
VALUE HttpParser_reset(VALUE self)
{
puma_parser *http = NULL;
DATA_GET(self, puma_parser, &HttpParser_data_type, http);
puma_parser_init(http);
return Qnil;
}
/**
* call-seq:
* parser.finish -> true/false
*
* Finishes a parser early which could put in a "good" or bad state.
* You should call reset after finish it or bad things will happen.
*/
VALUE HttpParser_finish(VALUE self)
{
puma_parser *http = NULL;
DATA_GET(self, puma_parser, &HttpParser_data_type, http);
puma_parser_finish(http);
return puma_parser_is_finished(http) ? Qtrue : Qfalse;
}
/**
* call-seq:
* parser.execute(req_hash, data, start) -> Integer
*
* Takes a Hash and a String of data, parses the String of data filling in the Hash
* returning an Integer to indicate how much of the data has been read. No matter
* what the return value, you should call HttpParser#finished? and HttpParser#error?
* to figure out if it's done parsing or there was an error.
*
* This function now throws an exception when there is a parsing error. This makes
* the logic for working with the parser much easier. You can still test for an
* error, but now you need to wrap the parser with an exception handling block.
*
* The third argument allows for parsing a partial request and then continuing
* the parsing from that position. It needs all of the original data as well
* so you have to append to the data buffer as you read.
*/
VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
{
puma_parser *http = NULL;
int from = 0;
char *dptr = NULL;
long dlen = 0;
DATA_GET(self, puma_parser, &HttpParser_data_type, http);
from = FIX2INT(start);
dptr = rb_extract_chars(data, &dlen);
if(from >= dlen) {
rb_free_chars(dptr);
rb_raise(eHttpParserError, "%s", "Requested start is after data buffer end.");
} else {
http->request = req_hash;
puma_parser_execute(http, dptr, dlen, from);
rb_free_chars(dptr);
VALIDATE_MAX_LENGTH(puma_parser_nread(http), HEADER);
if(puma_parser_has_error(http)) {
rb_raise(eHttpParserError, "%s", "Invalid HTTP format, parsing fails. Are you trying to open an SSL connection to a non-SSL Puma?");
} else {
return INT2FIX(puma_parser_nread(http));
}
}
}
/**
* call-seq:
* parser.error? -> true/false
*
* Tells you whether the parser is in an error state.
*/
VALUE HttpParser_has_error(VALUE self)
{
puma_parser *http = NULL;
DATA_GET(self, puma_parser, &HttpParser_data_type, http);
return puma_parser_has_error(http) ? Qtrue : Qfalse;
}
/**
* call-seq:
* parser.finished? -> true/false
*
* Tells you whether the parser is finished or not and in a good state.
*/
VALUE HttpParser_is_finished(VALUE self)
{
puma_parser *http = NULL;
DATA_GET(self, puma_parser, &HttpParser_data_type, http);
return puma_parser_is_finished(http) ? Qtrue : Qfalse;
}
/**
* call-seq:
* parser.nread -> Integer
*
* Returns the amount of data processed so far during this processing cycle. It is
* set to 0 on initialize or reset calls and is incremented each time execute is called.
*/
VALUE HttpParser_nread(VALUE self)
{
puma_parser *http = NULL;
DATA_GET(self, puma_parser, &HttpParser_data_type, http);
return INT2FIX(http->nread);
}
/**
* call-seq:
* parser.body -> nil or String
*
* If the request included a body, returns it.
*/
VALUE HttpParser_body(VALUE self) {
puma_parser *http = NULL;
DATA_GET(self, puma_parser, &HttpParser_data_type, http);
return http->body;
}
#ifdef HAVE_OPENSSL_BIO_H
void Init_mini_ssl(VALUE mod);
#endif
void Init_puma_http11()
{
VALUE mPuma = rb_define_module("Puma");
VALUE cHttpParser = rb_define_class_under(mPuma, "HttpParser", rb_cObject);
DEF_GLOBAL(request_method, "REQUEST_METHOD");
DEF_GLOBAL(request_uri, "REQUEST_URI");
DEF_GLOBAL(fragment, "FRAGMENT");
DEF_GLOBAL(query_string, "QUERY_STRING");
DEF_GLOBAL(http_version, "HTTP_VERSION");
DEF_GLOBAL(request_path, "REQUEST_PATH");
eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eIOError);
rb_global_variable(&eHttpParserError);
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
rb_define_method(cHttpParser, "reset", HttpParser_reset, 0);
rb_define_method(cHttpParser, "finish", HttpParser_finish, 0);
rb_define_method(cHttpParser, "execute", HttpParser_execute, 3);
rb_define_method(cHttpParser, "error?", HttpParser_has_error, 0);
rb_define_method(cHttpParser, "finished?", HttpParser_is_finished, 0);
rb_define_method(cHttpParser, "nread", HttpParser_nread, 0);
rb_define_method(cHttpParser, "body", HttpParser_body, 0);
init_common_fields();
#ifdef HAVE_OPENSSL_BIO_H
Init_mini_ssl(mPuma);
#endif
}