Permalink
Browse files

Add HTTP limits (security feature).

* Limit the total HTTP request size.
* Fail early, as soon as invalid characters are found.
* Fixes issue #33.
  • Loading branch information...
1 parent a26a868 commit 85d242f63a11232618b316fd162c19c4373d9a07 @nicolasff committed Feb 29, 2012
Showing with 116 additions and 0 deletions.
  1. +1 −0 README.markdown
  2. +4 −0 client.c
  3. +1 −0 client.h
  4. +3 −0 conf.c
  5. +1 −0 conf.h
  6. +104 −0 tests/limits.py
  7. +2 −0 worker.c
View
@@ -41,6 +41,7 @@ curl -d "GET/hello" http://127.0.0.1:7379/
* With the JSON output, the return value of INFO is parsed and transformed into an object.
* Optional daemonize.
* Default root object: Add `"default_root": "/GET/index.html"` in webdis.json to substitute the request to `/` with a Redis request.
+* HTTP request limit with `http_max_request_size` (in bytes, set to 128MB by default).
# Ideas, TODO...
* Add better support for PUT, DELETE, HEAD, OPTIONS? How? For which commands?
View
@@ -252,6 +252,7 @@ http_client_reset(struct http_client *c) {
free(c->type); c->type = NULL;
free(c->jsonp); c->jsonp = NULL;
free(c->filename); c->filename = NULL;
+ c->request_sz = 0;
/* no last known header callback */
c->last_cb = LAST_CB_NONE;
@@ -304,6 +305,9 @@ http_client_read(struct http_client *c) {
memcpy(c->buffer + c->sz, buffer, ret);
c->sz += ret;
+ /* keep track of total sent */
+ c->request_sz += ret;
+
return ret;
}
View
@@ -30,6 +30,7 @@ struct http_client {
struct http_parser_settings settings;
char *buffer;
size_t sz;
+ size_t request_sz; /* accumulated so far. */
last_cb_t last_cb;
/* various flags. */
View
@@ -30,6 +30,7 @@ conf_read(const char *filename) {
conf->redis_port = 6379;
conf->http_host = strdup("0.0.0.0");
conf->http_port = 7379;
+ conf->http_max_request_size = 128*1024*1024;
conf->http_threads = 4;
conf->user = getuid();
conf->group = getgid();
@@ -60,6 +61,8 @@ conf_read(const char *filename) {
conf->http_host = strdup(json_string_value(jtmp));
} else if(strcmp(json_object_iter_key(kv), "http_port") == 0 && json_typeof(jtmp) == JSON_INTEGER) {
conf->http_port = (short)json_integer_value(jtmp);
+ } else if(strcmp(json_object_iter_key(kv), "http_max_request_size") == 0 && json_typeof(jtmp) == JSON_INTEGER) {
+ conf->http_max_request_size = (size_t)json_integer_value(jtmp);
} else if(strcmp(json_object_iter_key(kv), "threads") == 0 && json_typeof(jtmp) == JSON_INTEGER) {
conf->http_threads = (short)json_integer_value(jtmp);
} else if(strcmp(json_object_iter_key(kv), "acl") == 0 && json_typeof(jtmp) == JSON_ARRAY) {
View
@@ -15,6 +15,7 @@ struct conf {
char *http_host;
short http_port;
short http_threads;
+ size_t http_max_request_size;
/* pool size, one pool per worker thread */
int pool_size_per_thread;
View
@@ -0,0 +1,104 @@
+#!/usr/bin/python
+import socket
+import unittest
+
+HOST = "127.0.0.1"
+PORT = 7379
+
+class BlockingSocket:
+
+ def __init__(self):
+ self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.s.setblocking(True)
+ self.s.connect((HOST, PORT))
+
+ def recv(self):
+ out = ""
+ while True:
+ try:
+ ret = self.s.recv(4096)
+ except:
+ return out
+ if len(ret) == 0:
+ return out
+ out += ret
+
+ def recv_until(self, limit):
+ out = ""
+ while not limit in out:
+ try:
+ out += self.s.recv(4096)
+ except:
+ return False
+ return out
+
+ def send(self, buf):
+ sz = len(buf)
+ done = 0
+ while done < sz:
+ try:
+ ret = self.s.send(buf[done:4096])
+ except:
+ return False
+ done = done + ret
+ # print "Sent %d/%d so far (%s just now)" % (done, sz, ret)
+ if ret < 0:
+ return False
+ return True
+
+class LargeString:
+
+ def __init__(self, c, n):
+ self.char = c
+ self.len = n
+
+ def __len__(self):
+ return self.len
+
+ def __getitem__(self, chunk):
+ if chunk.start > self.len:
+ return ""
+ if chunk.start + chunk.stop > self.len:
+ return self.char * (self.len - chunk.start)
+ return self.char * chunk.stop
+
+class TestSocket(unittest.TestCase):
+
+ def __init__(self, *args, **kwargs):
+ super(TestSocket, self).__init__(*args, **kwargs)
+ self.s = BlockingSocket()
+
+
+class TestHugeUrl(TestSocket):
+
+ def test_huge_url(self):
+ n = 1024*1024*1024 # 1GB query-string
+
+ start = "GET /GET/x"
+ end = " HTTP/1.0\r\n\r\n"
+
+ ok = self.s.send(start)
+ fail1 = self.s.send(LargeString("A", n))
+ fail2 = self.s.send(end)
+ out = self.s.recv()
+
+ self.assertTrue(ok)
+ self.assertTrue("400 Bad Request" in out)
+
+ def test_huge_upload(self):
+ n = 1024*1024*1024 # upload 1GB
+
+ start = "PUT /SET/x HTTP/1.0\r\n"\
+ + ("Content-Length: %d\r\n" % (n))\
+ + "Expect: 100-continue\r\n\r\n"
+
+ ok = self.s.send(start)
+ cont = self.s.recv_until("\r\n")
+ fail = self.s.send(LargeString("A", n))
+
+ self.assertTrue(ok)
+ self.assertTrue("HTTP/1.1 100 Continue" in cont)
+ self.assertFalse(fail)
+
+if __name__ == '__main__':
+ unittest.main()
View
@@ -66,6 +66,8 @@ worker_can_read(int fd, short event, void *p) {
free(c->buffer);
c->buffer = NULL;
c->sz = 0;
+ } else if(nparsed != ret || c->request_sz > c->s->cfg->http_max_request_size) {
+ http_send_error(c, 400, "Bad Request");
}
}

0 comments on commit 85d242f

Please sign in to comment.