Import webdis_0.1.1.orig.tar.gz
authorAndriy Senkovych <jolly_roger@itblog.org.ua>
Sun, 3 Aug 2014 23:10:55 +0000 (00:10 +0100)
committerAndriy Senkovych <jolly_roger@itblog.org.ua>
Sun, 3 Aug 2014 23:10:55 +0000 (00:10 +0100)
[dgit import orig webdis_0.1.1.orig.tar.gz]

108 files changed:
.gitignore [new file with mode: 0644]
.travis.yml [new file with mode: 0644]
COPYING [new file with mode: 0644]
Makefile [new file with mode: 0644]
README.markdown [new file with mode: 0644]
acl.c [new file with mode: 0644]
acl.h [new file with mode: 0644]
b64/cencode.c [new file with mode: 0644]
b64/cencode.h [new file with mode: 0644]
client.c [new file with mode: 0644]
client.h [new file with mode: 0644]
cmd.c [new file with mode: 0644]
cmd.h [new file with mode: 0644]
conf.c [new file with mode: 0644]
conf.h [new file with mode: 0644]
formats/common.c [new file with mode: 0644]
formats/common.h [new file with mode: 0644]
formats/custom-type.c [new file with mode: 0644]
formats/custom-type.h [new file with mode: 0644]
formats/json.c [new file with mode: 0644]
formats/json.h [new file with mode: 0644]
formats/msgpack.c [new file with mode: 0644]
formats/msgpack.h [new file with mode: 0644]
formats/raw.c [new file with mode: 0644]
formats/raw.h [new file with mode: 0644]
hiredis/.gitignore [new file with mode: 0644]
hiredis/COPYING [new file with mode: 0644]
hiredis/Makefile [new file with mode: 0644]
hiredis/README.md [new file with mode: 0644]
hiredis/TODO [new file with mode: 0644]
hiredis/adapters/ae.h [new file with mode: 0644]
hiredis/adapters/libev.h [new file with mode: 0644]
hiredis/adapters/libevent.h [new file with mode: 0644]
hiredis/async.c [new file with mode: 0644]
hiredis/async.h [new file with mode: 0644]
hiredis/dict.c [new file with mode: 0644]
hiredis/dict.h [new file with mode: 0644]
hiredis/example-ae.c [new file with mode: 0644]
hiredis/example-libev.c [new file with mode: 0644]
hiredis/example-libevent.c [new file with mode: 0644]
hiredis/example.c [new file with mode: 0644]
hiredis/fmacros.h [new file with mode: 0644]
hiredis/hiredis.c [new file with mode: 0644]
hiredis/hiredis.h [new file with mode: 0644]
hiredis/net.c [new file with mode: 0644]
hiredis/net.h [new file with mode: 0644]
hiredis/sds.c [new file with mode: 0644]
hiredis/sds.h [new file with mode: 0644]
hiredis/test.c [new file with mode: 0644]
hiredis/util.h [new file with mode: 0644]
http-parser/.gitignore [new file with mode: 0644]
http-parser/CONTRIBUTIONS [new file with mode: 0644]
http-parser/LICENSE-MIT [new file with mode: 0644]
http-parser/README.md [new file with mode: 0644]
http-parser/http_parser.c [new file with mode: 0644]
http-parser/http_parser.h [new file with mode: 0644]
http.c [new file with mode: 0644]
http.h [new file with mode: 0644]
jansson/.gitignore [new file with mode: 0644]
jansson/CHANGES [new file with mode: 0644]
jansson/LICENSE [new file with mode: 0644]
jansson/Makefile.am [new file with mode: 0644]
jansson/README.rst [new file with mode: 0644]
jansson/configure.ac [new file with mode: 0644]
jansson/jansson.pc.in [new file with mode: 0644]
jansson/src/Makefile.am [new file with mode: 0644]
jansson/src/dump.c [new file with mode: 0644]
jansson/src/error.c [new file with mode: 0644]
jansson/src/hashtable.c [new file with mode: 0644]
jansson/src/hashtable.h [new file with mode: 0644]
jansson/src/jansson.h [new file with mode: 0644]
jansson/src/jansson_config.h [new file with mode: 0644]
jansson/src/jansson_config.h.in [new file with mode: 0644]
jansson/src/jansson_config.h.win32 [new file with mode: 0644]
jansson/src/jansson_private.h [new file with mode: 0644]
jansson/src/load.c [new file with mode: 0644]
jansson/src/strbuffer.c [new file with mode: 0644]
jansson/src/strbuffer.h [new file with mode: 0644]
jansson/src/utf.c [new file with mode: 0644]
jansson/src/utf.h [new file with mode: 0644]
jansson/src/value.c [new file with mode: 0644]
jansson/src/variadic.c [new file with mode: 0644]
md5/md5.c [new file with mode: 0644]
md5/md5.h [new file with mode: 0644]
pool.c [new file with mode: 0644]
pool.h [new file with mode: 0644]
server.c [new file with mode: 0644]
server.h [new file with mode: 0644]
sha1/sha1.c [new file with mode: 0644]
sha1/sha1.h [new file with mode: 0644]
slog.c [new file with mode: 0644]
slog.h [new file with mode: 0644]
tests/Makefile [new file with mode: 0644]
tests/README.tests [new file with mode: 0644]
tests/basic.py [new file with mode: 0755]
tests/bench.sh [new file with mode: 0755]
tests/limits.py [new file with mode: 0755]
tests/pubsub.c [new file with mode: 0644]
tests/websocket.c [new file with mode: 0644]
tests/websocket.html [new file with mode: 0644]
version.h [new file with mode: 0644]
webdis.c [new file with mode: 0644]
webdis.json [new file with mode: 0644]
webdis.prod.json [new file with mode: 0644]
websocket.c [new file with mode: 0644]
websocket.h [new file with mode: 0644]
worker.c [new file with mode: 0644]
worker.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..67cccf6
--- /dev/null
@@ -0,0 +1,7 @@
+*.log
+*.swp
+*.o
+webdis
+websocket
+*.png
+pubsub
diff --git a/.travis.yml b/.travis.yml
new file mode 100644 (file)
index 0000000..1d124ec
--- /dev/null
@@ -0,0 +1,9 @@
+script: "make clean all"
+language: c
+compiler:
+  - gcc
+  - clang
+before_install:
+  - sudo apt-get update
+  - sudo apt-get install libevent-dev
+install: "sudo make install"
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..a9ae619
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,24 @@
+Copyright (c) 2010-2011, Nicolas Favre-Felix
+
+All rights reserved.
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright notice, 
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..e2b4b58
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,66 @@
+OUT=webdis
+HIREDIS_OBJ?=hiredis/hiredis.o hiredis/sds.o hiredis/net.o hiredis/async.o
+JANSSON_OBJ?=jansson/src/dump.o jansson/src/error.o jansson/src/hashtable.o jansson/src/load.o jansson/src/strbuffer.o jansson/src/utf.o jansson/src/value.o jansson/src/variadic.o
+B64_OBJS?=b64/cencode.o
+FORMAT_OBJS?=formats/json.o formats/raw.o formats/common.o formats/custom-type.o
+HTTP_PARSER_OBJS?=http-parser/http_parser.o
+
+CFLAGS ?= -O0 -ggdb -Wall -Wextra -I. -Ijansson/src -Ihttp-parser
+LDFLAGS ?= -levent -pthread
+
+# check for MessagePack
+MSGPACK_LIB=$(shell ls /usr/lib/libmsgpack.so 2>/dev/null)
+ifneq ($(strip $(MSGPACK_LIB)),)
+       FORMAT_OBJS += formats/msgpack.o
+       CFLAGS += -DMSGPACK=1
+       LDFLAGS += -lmsgpack
+endif
+
+
+DEPS=$(FORMAT_OBJS) $(HIREDIS_OBJ) $(JANSSON_OBJ) $(HTTP_PARSER_OBJS) $(B64_OBJS)
+OBJS=webdis.o cmd.o worker.o slog.o server.o acl.o md5/md5.o sha1/sha1.o http.o client.o websocket.o pool.o conf.o $(DEPS)
+
+
+
+PREFIX ?= /usr/local
+CONFDIR ?= $(DESTDIR)/etc
+
+INSTALL_DIRS = $(DESTDIR) \
+              $(DESTDIR)/$(PREFIX) \
+              $(DESTDIR)/$(PREFIX)/bin \
+              $(CONFDIR)
+
+all: $(OUT) Makefile
+
+$(OUT): $(OBJS) Makefile
+       $(CC) -o $(OUT) $(OBJS) $(LDFLAGS)
+
+%.o: %.c %.h Makefile
+       $(CC) -c $(CFLAGS) -o $@ $<
+
+%.o: %.c Makefile
+       $(CC) -c $(CFLAGS) -o $@ $<
+
+$(INSTALL_DIRS):
+       mkdir -p $@
+
+clean:
+       rm -f $(OBJS) $(OUT)
+
+install: $(OUT) $(INSTALL_DIRS)
+       cp $(OUT) $(DESTDIR)/$(PREFIX)/bin
+       cp webdis.prod.json $(CONFDIR)
+
+
+WEBDIS_PORT ?= 7379
+
+test_all: test perftest
+
+test:
+       python tests/basic.py
+       python tests/limits.py
+       ./tests/pubsub -p $(WEBDIS_PORT)
+
+perftest:
+       # This is a performance test that requires apache2-utils and curl
+       ./tests/bench.sh
diff --git a/README.markdown b/README.markdown
new file mode 100644 (file)
index 0000000..54ad875
--- /dev/null
@@ -0,0 +1,321 @@
+# About
+
+A very simple web server providing an HTTP interface to Redis. It uses [hiredis](https://github.com/antirez/hiredis), [jansson](https://github.com/akheron/jansson), [libevent](http://monkey.org/~provos/libevent/), and [http-parser](https://github.com/ry/http-parser/).
+
+Webdis depends on libevent-dev. You can install it on Ubuntu by typing `sudo apt-get install libevent-dev` or on OS X by typing `brew install libevent`.
+<pre>
+make clean all
+
+./webdis &
+
+curl http://127.0.0.1:7379/SET/hello/world
+→ {"SET":[true,"OK"]}
+
+curl http://127.0.0.1:7379/GET/hello
+→ {"GET":"world"}
+
+curl -d "GET/hello" http://127.0.0.1:7379/
+→ {"GET":"world"}
+
+</pre>
+
+# Features
+* `GET` and `POST` are supported, as well as `PUT` for file uploads.
+* JSON output by default, optional JSONP parameter (`?jsonp=myFunction` or `?callback=myFunction`).
+* Raw Redis 2.0 protocol output with `.raw` suffix
+* MessagePack output with `.msg` suffix
+* HTTP 1.1 pipelining (70,000 http requests per second on a desktop Linux machine.)
+* Multi-threaded server, configurable number of worker threads.
+* WebSocket support (Currently using the “hixie-76” specification).
+* Connects to Redis using a TCP or UNIX socket.
+* Restricted commands by IP range (CIDR subnet + mask) or HTTP Basic Auth, returning 403 errors.
+* Possible Redis authentication in the config file.
+* Pub/Sub using `Transfer-Encoding: chunked`, works with JSONP as well. Webdis can be used as a Comet server.
+* Drop privileges on startup.
+* Custom Content-Type using a pre-defined file extension, or with `?type=some/thing`.
+* URL-encoded parameters for binary data or slashes and question marks. For instance, `%2f` is decoded as `/` but not used as a command separator.
+* Logs, with a configurable verbosity.
+* Cross-origin requests, usable with XMLHttpRequest2 (Cross-Origin Resource Sharing - CORS).
+* File upload with PUT.
+* With the JSON output, the return value of INFO is parsed and transformed into an object.
+* Optional daemonize: set `"daemonize": true` and `"pidfile": "/var/run/webdis.pid"` in webdis.json.
+* 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).
+* Database selection in the URL, using e.g. `/7/GET/key` to run the command on DB 7.
+
+# Ideas, TODO...
+* Add better support for PUT, DELETE, HEAD, OPTIONS? How? For which commands?
+       * This could be done using a “strict mode” with a table of commands and the verbs that can/must be used with each command. Strict mode would be optional, configurable. How would webdis know of new commands remains to be determined.
+* MULTI/EXEC/DISCARD/WATCH are disabled at the moment; find a way to use them.
+* Support POST of raw Redis protocol data, and execute the whole thing. This could be useful for MULTI/EXEC transactions.
+* Enrich config file:
+       * Provide timeout (maybe for some commands only?). What should the response be? 504 Gateway Timeout? 503 Service Unavailable?
+* Multi-server support, using consistent hashing.
+* SSL?
+       * Not sure if this is such a good idea.
+* SPDY?
+       * SPDY is mostly useful for parallel fetches. Not sure if it would make sense for Webdis.
+* Send your ideas using the github tracker, on twitter [@yowgi](http://twitter.com/yowgi) or by mail to n.favrefelix@gmail.com.
+
+# HTTP error codes
+* Unknown HTTP verb: 405 Method Not Allowed.
+* Redis is unreachable: 503 Service Unavailable.
+* Matching ETag sent using `If-None-Match`: 304 Not Modified.
+* Could also be used:
+       * Timeout on the redis side: 503 Service Unavailable.
+       * Missing key: 404 Not Found.
+       * Unauthorized command (disabled in config file): 403 Forbidden.
+
+# Command format
+The URI `/COMMAND/arg0/arg1/.../argN.ext` executes the command on Redis and returns the response to the client. GET, POST, and PUT are supported:
+
+* `GET /COMMAND/arg0/.../argN.ext`
+* `POST /` with `COMMAND/arg0/.../argN` in the HTTP body.
+* `PUT /COMMAND/arg0.../argN-1` with `argN` in the HTTP body (see section on [file uploads](#file-upload).)
+
+`.ext` is an optional extension; it is not read as part of the last argument but only represents the output format. Several formats are available (see below).
+
+Special characters: `/` and `.` have special meanings, `/` separates arguments and `.` changes the Content-Type. They can be replaced by `%2f` and `%2e`, respectively.
+
+# ACL
+Access control is configured in `webdis.json`. Each configuration tries to match a client profile according to two criterias:
+
+* [CIDR](http://en.wikipedia.org/wiki/CIDR) subnet + mask
+* [HTTP Basic Auth](http://en.wikipedia.org/wiki/Basic_access_authentication) in the format of "user:password".
+
+Each ACL contains two lists of commands, `enabled` and `disabled`. All commands being enabled by default, it is up to the administrator to disable or re-enable them on a per-profile basis.
+Examples:
+<pre>
+{
+       "disabled":     ["DEBUG", "FLUSHDB", "FLUSHALL"],
+},
+
+{
+       "http_basic_auth": "user:password",
+       "disabled":     ["DEBUG", "FLUSHDB", "FLUSHALL"],
+       "enabled":      ["SET"]
+},
+
+{
+       "ip":           "192.168.10.0/24",
+       "enabled":      ["SET"]
+},
+
+{
+       "http_basic_auth": "user:password",
+       "ip":           "192.168.10.0/24",
+       "enabled":      ["SET", "DEL"]
+}
+</pre>
+ACLs are interpreted in order, later authorizations superseding earlier ones if a client matches several. The special value "*" matches all commands.
+
+# JSON output
+JSON is the default output format. Each command returns a JSON object with the command as a key and the result as a value.
+
+**Examples:**
+<pre>
+// string
+$ curl http://127.0.0.1:7379/GET/y
+{"GET":"41"}
+
+// number
+$ curl http://127.0.0.1:7379/INCR/y
+{"INCR":42}
+
+// list
+$ curl http://127.0.0.1:7379/LRANGE/x/0/1
+{"LRANGE":["abc","def"]}
+
+// status
+$ curl http://127.0.0.1:7379/TYPE/y
+{"TYPE":[true,"string"]}
+
+// error, which is basically a status
+$ curl http://127.0.0.1:7379/MAKE-ME-COFFEE
+{"MAKE-ME-COFFEE":[false,"ERR unknown command 'MAKE-ME-COFFEE'"]}
+
+// JSONP callback:
+$ curl  "http://127.0.0.1:7379/TYPE/y?jsonp=myCustomFunction"
+myCustomFunction({"TYPE":[true,"string"]})
+</pre>
+
+# RAW output
+This is the raw output of Redis; enable it with the `.raw` suffix.
+<pre>
+
+// string
+$ curl http://127.0.0.1:7379/GET/z.raw
+$5
+hello
+
+// number
+curl http://127.0.0.1:7379/INCR/a.raw
+:2
+
+// list
+$ curl http://127.0.0.1:7379/LRANGE/x/0/-1.raw
+*2
+$3
+abc
+$3
+def
+
+// status
+$ curl http://127.0.0.1:7379/TYPE/y.raw
++zset
+
+// error, which is basically a status
+$ curl http://127.0.0.1:7379/MAKE-ME-COFFEE.raw
+-ERR unknown command 'MAKE-ME-COFFEE'
+</pre>
+
+# Custom content-type
+Several content-types are available:
+
+* `.json` for `application/json` (this is the default Content-Type).
+* `.msg` for `application/x-msgpack`. See [http://msgpack.org/](http://msgpack.org/) for the specs.
+* `.txt` for `text/plain`
+* `.html` for `text/html`
+* `xhtml` for `application/xhtml+xml`
+* `xml` for `text/xml`
+* `.png` for `image/png`
+* `jpg` or `jpeg` for `image/jpeg`
+* Any other with the `?type=anything/youwant` query string.
+* Add a custom separator for list responses with `?sep=,` query string.
+
+<pre>
+curl -v "http://127.0.0.1:7379/GET/hello.html"
+[...]
+&lt; HTTP/1.1 200 OK
+&lt; Content-Type: text/html
+&lt; Date: Mon, 03 Jan 2011 20:43:36 GMT
+&lt; Content-Length: 137
+&lt;
+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+[...]
+&lt;/html&gt;
+
+curl -v "http://127.0.0.1:7379/GET/hello.txt"
+[...]
+&lt; HTTP/1.1 200 OK
+&lt; Content-Type: text/plain
+&lt; Date: Mon, 03 Jan 2011 20:43:36 GMT
+&lt; Content-Length: 137
+[...]
+
+curl -v "http://127.0.0.1:7379/GET/big-file?type=application/pdf"
+[...]
+&lt; HTTP/1.1 200 OK
+&lt; Content-Type: application/pdf
+&lt; Date: Mon, 03 Jan 2011 20:45:12 GMT
+[...]
+</pre>
+
+# File upload
+Webdis supports file upload using HTTP PUT. The command URI is slightly different, as the last argument is taken from the HTTP body.
+For example: instead of `/SET/key/value`, the URI becomes `/SET/key` and the value is the entirety of the body. This works for other commands such as LPUSH, etc.
+
+**Uploading a binary file to webdis**:
+<pre>
+$ file redis-logo.png
+redis-logo.png: PNG image, 513 x 197, 8-bit/color RGBA, non-interlaced
+
+$ wc -c redis-logo.png
+16744 redis-logo.png
+
+$ curl -v --upload-file redis-logo.png http://127.0.0.1:7379/SET/logo
+[...]
+&gt; PUT /SET/logo HTTP/1.1
+&gt; User-Agent: curl/7.19.7 (x86_64-pc-linux-gnu) libcurl/7.19.7 OpenSSL/0.9.8k zlib/1.2.3.3 libidn/1.15
+&gt; Host: 127.0.0.1:7379
+&gt; Accept: */*
+&gt; Content-Length: 16744
+&gt; Expect: 100-continue
+&gt;
+&lt; HTTP/1.1 100 Continue
+&lt; HTTP/1.1 200 OK
+&lt; Content-Type: application/json
+&lt; ETag: "0db1124cf79ffeb80aff6d199d5822f8"
+&lt; Date: Sun, 09 Jan 2011 16:48:19 GMT
+&lt; Content-Length: 19
+&lt;
+{"SET":[true,"OK"]}
+
+$ curl -vs http://127.0.0.1:7379/GET/logo.png -o out.png
+&gt; GET /GET/logo.png HTTP/1.1
+&gt; User-Agent: curl/7.19.7 (x86_64-pc-linux-gnu) libcurl/7.19.7 OpenSSL/0.9.8k zlib/1.2.3.3 libidn/1.15
+&gt; Host: 127.0.0.1:7379
+&gt; Accept: */*
+&gt;
+&lt; HTTP/1.1 200 OK
+&lt; Content-Type: image/png
+&lt; ETag: "1991df597267d70bf9066a7d11969da0"
+&lt; Date: Sun, 09 Jan 2011 16:50:51 GMT
+&lt; Content-Length: 16744
+
+$ md5sum redis-logo.png out.png
+1991df597267d70bf9066a7d11969da0  redis-logo.png
+1991df597267d70bf9066a7d11969da0  out.png
+</pre>
+
+The file was uploaded and re-downloaded properly: it has the same hash and the content-type was set properly thanks to the `.png` extension.
+
+# WebSockets
+Webdis supports WebSocket clients implementing [dixie-76](http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76).  
+Web Sockets are supported with the following formats, selected by the connection URL:
+
+* JSON (on `/` or `/.json`)
+* Raw Redis wire protocol (on `/.raw`)
+
+**Example**:
+<pre>
+function testJSON() {
+       var jsonSocket = new WebSocket("ws://127.0.0.1:7379/.json");
+       jsonSocket.onopen = function() {
+
+               console.log("JSON socket connected!");
+               jsonSocket.send(JSON.stringify(["SET", "hello", "world"]));
+               jsonSocket.send(JSON.stringify(["GET", "hello"]));
+       };
+       jsonSocket.onmessage = function(messageEvent) {
+               console.log("JSON received:", messageEvent.data);
+       };
+}
+testJSON();
+</pre>
+
+This produces the following output:
+<pre>
+JSON socket connected!
+JSON received: {"SET":[true,"OK"]}
+JSON received: {"GET":"world"}
+</pre>
+
+# Pub/Sub with chunked transfer encoding
+Webdis exposes Redis PUB/SUB channels to HTTP clients, forwarding messages in the channel as they are published by Redis. This is done using chunked transfer encoding.
+
+**Example using XMLHttpRequest**:
+<pre>
+var previous_response_length = 0
+xhr = new XMLHttpRequest()
+xhr.open("GET", "http://127.0.0.1:7379/SUBSCRIBE/hello", true);
+xhr.onreadystatechange = checkData;
+xhr.send(null);
+
+function checkData() {
+       if(xhr.readyState == 3)  {
+       response = xhr.responseText;
+       chunk = response.slice(previous_response_length);
+       previous_response_length = response.length;
+       console.log(chunk);
+    }
+};
+</pre>
+
+Publish messages to redis to see output similar to the following:
+<pre>
+{"SUBSCRIBE":["subscribe","hello",1]}
+{"SUBSCRIBE":["message","hello","some message"]}
+{"SUBSCRIBE":["message","hello","some other message"]} 
+</pre>
diff --git a/acl.c b/acl.c
new file mode 100644 (file)
index 0000000..160e6e5
--- /dev/null
+++ b/acl.c
@@ -0,0 +1,98 @@
+#include "acl.h"
+#include "cmd.h"
+#include "conf.h"
+#include "http.h"
+#include "client.h"
+
+#include <string.h>
+#include <evhttp.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+int
+acl_match_client(struct acl *a, struct http_client *client, in_addr_t *ip) {
+
+       /* check HTTP Basic Auth */
+       const char *auth;
+       auth = client_get_header(client, "Authorization");
+       if(a->http_basic_auth) {
+               if(auth && strncasecmp(auth, "Basic ", 6) == 0) { /* sent auth */
+                       if(strcmp(auth + 6, a->http_basic_auth) != 0) { /* bad password */
+                               return 0;
+                       }
+               } else { /* no auth sent, required to match this ACL */
+                       return 0;
+               }
+       }
+
+       /* CIDR check. */
+       if(a->cidr.enabled == 0) { /* none given, all match */
+               return 1;
+       }
+       if(((*ip) & a->cidr.mask) == (a->cidr.subnet & a->cidr.mask)) {
+               return 1;
+       }
+
+       return 0;
+}
+
+int
+acl_allow_command(struct cmd *cmd, struct conf *cfg, struct http_client *client) {
+
+       char *always_off[] = {"MULTI", "EXEC", "WATCH", "DISCARD", "SELECT"};
+
+       unsigned int i;
+       int authorized = 1;
+       struct acl *a;
+
+       in_addr_t client_addr;
+
+       const char *cmd_name;
+       size_t cmd_len;
+
+       if(cmd->count == 0) {
+               return 0;
+       }
+
+       cmd_name = cmd->argv[0];
+       cmd_len = cmd->argv_len[0];
+
+       /* some commands are always disabled, regardless of the config file. */
+       for(i = 0; i < sizeof(always_off) / sizeof(always_off[0]); ++i) {
+               if(strncasecmp(always_off[i], cmd_name, cmd_len) == 0) {
+                       return 0;
+               }
+       }
+
+       /* find client's address */
+       client_addr = ntohl(client->addr);
+
+       /* go through permissions */
+       for(a = cfg->perms; a; a = a->next) {
+
+               if(!acl_match_client(a, client, &client_addr)) continue; /* match client */
+
+               /* go through authorized commands */
+               for(i = 0; i < a->enabled.count; ++i) {
+                       if(strncasecmp(a->enabled.commands[i], cmd_name, cmd_len) == 0) {
+                               authorized = 1;
+                       }
+                       if(strncasecmp(a->enabled.commands[i], "*", 1) == 0) {
+                               authorized = 1;
+                       }
+               }
+
+               /* go through unauthorized commands */
+               for(i = 0; i < a->disabled.count; ++i) {
+                       if(strncasecmp(a->disabled.commands[i], cmd_name, cmd_len) == 0) {
+                               authorized = 0;
+                       }
+                       if(strncasecmp(a->disabled.commands[i], "*", 1) == 0) {
+                               authorized = 0;
+                       }
+               }
+       }
+
+       return authorized;
+}
+
diff --git a/acl.h b/acl.h
new file mode 100644 (file)
index 0000000..2b68b93
--- /dev/null
+++ b/acl.h
@@ -0,0 +1,39 @@
+#ifndef ACL_H
+#define ACL_H
+
+#include <netinet/in.h>
+
+struct http_client;
+struct cmd;
+struct conf;
+
+struct acl_commands {
+       unsigned int count;
+       char **commands;
+};
+
+struct acl {
+
+       /* CIDR subnet + mask */
+       struct {
+               int enabled;
+               in_addr_t subnet;
+               in_addr_t mask;
+       } cidr;
+
+       char *http_basic_auth;
+
+       /* commands that have been enabled or disabled */
+       struct acl_commands enabled;
+       struct acl_commands disabled;
+
+       struct acl *next;
+};
+
+int
+acl_match_client(struct acl *a, struct http_client *client, in_addr_t *ip);
+
+int
+acl_allow_command(struct cmd *cmd, struct conf *cfg, struct http_client *client);
+
+#endif
diff --git a/b64/cencode.c b/b64/cencode.c
new file mode 100644 (file)
index 0000000..a8c8fee
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+cencoder.c - c source to a base64 encoding algorithm implementation
+
+This is part of the libb64 project, and has been placed in the public domain.
+For details, see http://sourceforge.net/projects/libb64
+*/
+
+#include "cencode.h"
+
+const int CHARS_PER_LINE = 72;
+
+void base64_init_encodestate(base64_encodestate* state_in)
+{
+       state_in->step = step_A;
+       state_in->result = 0;
+       state_in->stepcount = 0;
+}
+
+char base64_encode_value(char value_in)
+{
+       static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+       if (value_in > 63) return '=';
+       return encoding[(int)value_in];
+}
+
+int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in)
+{
+       const char* plainchar = plaintext_in;
+       const char* const plaintextend = plaintext_in + length_in;
+       char* codechar = code_out;
+       char result;
+       char fragment;
+       
+       result = state_in->result;
+       
+       switch (state_in->step)
+       {
+               while (1)
+               {
+       case step_A:
+                       if (plainchar == plaintextend)
+                       {
+                               state_in->result = result;
+                               state_in->step = step_A;
+                               return codechar - code_out;
+                       }
+                       fragment = *plainchar++;
+                       result = (fragment & 0x0fc) >> 2;
+                       *codechar++ = base64_encode_value(result);
+                       result = (fragment & 0x003) << 4;
+       case step_B:
+                       if (plainchar == plaintextend)
+                       {
+                               state_in->result = result;
+                               state_in->step = step_B;
+                               return codechar - code_out;
+                       }
+                       fragment = *plainchar++;
+                       result |= (fragment & 0x0f0) >> 4;
+                       *codechar++ = base64_encode_value(result);
+                       result = (fragment & 0x00f) << 2;
+       case step_C:
+                       if (plainchar == plaintextend)
+                       {
+                               state_in->result = result;
+                               state_in->step = step_C;
+                               return codechar - code_out;
+                       }
+                       fragment = *plainchar++;
+                       result |= (fragment & 0x0c0) >> 6;
+                       *codechar++ = base64_encode_value(result);
+                       result  = (fragment & 0x03f) >> 0;
+                       *codechar++ = base64_encode_value(result);
+                       
+                       ++(state_in->stepcount);
+                       if (state_in->stepcount == CHARS_PER_LINE/4)
+                       {
+                               *codechar++ = '\n';
+                               state_in->stepcount = 0;
+                       }
+               }
+       }
+       /* control should not reach here */
+       return codechar - code_out;
+}
+
+int base64_encode_blockend(char* code_out, base64_encodestate* state_in)
+{
+       char* codechar = code_out;
+       
+       switch (state_in->step)
+       {
+       case step_B:
+               *codechar++ = base64_encode_value(state_in->result);
+               *codechar++ = '=';
+               *codechar++ = '=';
+               break;
+       case step_C:
+               *codechar++ = base64_encode_value(state_in->result);
+               *codechar++ = '=';
+               break;
+       case step_A:
+               break;
+       }
+       *codechar++ = '\n';
+       
+       return codechar - code_out;
+}
+
diff --git a/b64/cencode.h b/b64/cencode.h
new file mode 100644 (file)
index 0000000..c1e3464
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+cencode.h - c header for a base64 encoding algorithm
+
+This is part of the libb64 project, and has been placed in the public domain.
+For details, see http://sourceforge.net/projects/libb64
+*/
+
+#ifndef BASE64_CENCODE_H
+#define BASE64_CENCODE_H
+
+typedef enum
+{
+       step_A, step_B, step_C
+} base64_encodestep;
+
+typedef struct
+{
+       base64_encodestep step;
+       char result;
+       int stepcount;
+} base64_encodestate;
+
+void base64_init_encodestate(base64_encodestate* state_in);
+
+char base64_encode_value(char value_in);
+
+int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in);
+
+int base64_encode_blockend(char* code_out, base64_encodestate* state_in);
+
+#endif /* BASE64_CENCODE_H */
diff --git a/client.c b/client.c
new file mode 100644 (file)
index 0000000..cdcd762
--- /dev/null
+++ b/client.c
@@ -0,0 +1,370 @@
+#include "client.h"
+#include "http_parser.h"
+#include "http.h"
+#include "server.h"
+#include "worker.h"
+#include "websocket.h"
+#include "cmd.h"
+#include "conf.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <hiredis/hiredis.h>
+#include <hiredis/async.h>
+
+#define CHECK_ALLOC(c, ptr) if(!(ptr)) { c->failed_alloc = 1; return -1;}
+
+static int
+http_client_on_url(struct http_parser *p, const char *at, size_t sz) {
+
+       struct http_client *c = p->data;
+
+       CHECK_ALLOC(c, c->path = realloc(c->path, c->path_sz + sz + 1));
+       memcpy(c->path + c->path_sz, at, sz);
+       c->path_sz += sz;
+       c->path[c->path_sz] = 0;
+
+       return 0;
+}
+
+/*
+ * Called when the body is parsed.
+ */
+static int
+http_client_on_body(struct http_parser *p, const char *at, size_t sz) {
+
+       struct http_client *c = p->data;
+       return http_client_add_to_body(c, at, sz);
+}
+
+int
+http_client_add_to_body(struct http_client *c, const char *at, size_t sz) {
+
+       CHECK_ALLOC(c, c->body = realloc(c->body, c->body_sz + sz + 1));
+       memcpy(c->body + c->body_sz, at, sz);
+       c->body_sz += sz;
+       c->body[c->body_sz] = 0;
+
+       return 0;
+}
+
+static int
+http_client_on_header_name(struct http_parser *p, const char *at, size_t sz) {
+
+       struct http_client *c = p->data;
+       size_t n = c->header_count;
+
+       /* if we're not adding to the same header name as last time, realloc to add one field. */
+       if(c->last_cb != LAST_CB_KEY) {
+               n = ++c->header_count;
+               CHECK_ALLOC(c, c->headers = realloc(c->headers, n * sizeof(struct http_header)));
+               memset(&c->headers[n-1], 0, sizeof(struct http_header));
+       }
+
+       /* Add data to the current header name. */
+       CHECK_ALLOC(c, c->headers[n-1].key = realloc(c->headers[n-1].key,
+                       c->headers[n-1].key_sz + sz + 1));
+       memcpy(c->headers[n-1].key + c->headers[n-1].key_sz, at, sz);
+       c->headers[n-1].key_sz += sz;
+       c->headers[n-1].key[c->headers[n-1].key_sz] = 0;
+
+       c->last_cb = LAST_CB_KEY;
+
+       return 0;
+}
+
+static char *
+wrap_filename(const char *val, size_t val_len) {
+
+       char format[] = "attachment; filename=\"";
+       size_t sz = sizeof(format) - 1 + val_len + 1;
+       char *p = calloc(sz + 1, 1);
+
+       memcpy(p, format, sizeof(format)-1); /* copy format */
+       memcpy(p + sizeof(format)-1, val, val_len); /* copy filename */
+       p[sz-1] = '"';
+
+       return p;
+}
+
+/*
+ * Split query string into key/value pairs, process some of them.
+ */
+static int
+http_client_on_query_string(struct http_parser *parser, const char *at, size_t sz) {
+
+       struct http_client *c = parser->data;
+       const char *p = at;
+
+       while(p < at + sz) {
+
+               const char *key = p, *val;
+               int key_len, val_len;
+               char *eq = memchr(key, '=', sz - (p-at));
+               if(!eq || eq > at + sz) { /* last argument */
+                       break;
+               } else { /* found an '=' */
+                       char *amp;
+                       val = eq + 1;
+                       key_len = eq - key;
+                       p = eq + 1;
+
+                       amp = memchr(p, '&', sz - (p-at));
+                       if(!amp || amp > at + sz) {
+                               val_len = at + sz - p; /* last arg */
+                       } else {
+                               val_len = amp - val; /* cur arg */
+                               p = amp + 1;
+                       }
+
+                       if(key_len == 4 && strncmp(key, "type", 4) == 0) {
+                               c->type = calloc(1 + val_len, 1);
+                               memcpy(c->type, val, val_len);
+                       } else if((key_len == 5 && strncmp(key, "jsonp", 5) == 0)
+                               || (key_len == 8 && strncmp(key, "callback", 8) == 0)) {
+                               c->jsonp = calloc(1 + val_len, 1);
+                               memcpy(c->jsonp, val, val_len);
+                       } else if(key_len == 3 && strncmp(key, "sep", 3) == 0) {
+                               c->separator = calloc(1 + val_len, 1);
+                               memcpy(c->separator, val, val_len);
+                       } else if(key_len == 8 && strncmp(key, "filename", 8) == 0) {
+                               c->filename = wrap_filename(val, val_len);
+                       }
+
+                       if(!amp) {
+                               break;
+                       }
+               }
+       }
+       return 0;
+}
+
+static int
+http_client_on_header_value(struct http_parser *p, const char *at, size_t sz) {
+
+       struct http_client *c = p->data;
+       size_t n = c->header_count;
+
+       /* Add data to the current header value. */
+       CHECK_ALLOC(c, c->headers[n-1].val = realloc(c->headers[n-1].val,
+                       c->headers[n-1].val_sz + sz + 1));
+       memcpy(c->headers[n-1].val + c->headers[n-1].val_sz, at, sz);
+       c->headers[n-1].val_sz += sz;
+       c->headers[n-1].val[c->headers[n-1].val_sz] = 0;
+
+       c->last_cb = LAST_CB_VAL;
+
+
+       /* react to some values. */
+       if(strncmp("Expect", c->headers[n-1].key, c->headers[n-1].key_sz) == 0) {
+               if(sz == 12 && strncasecmp(at, "100-continue", sz) == 0) {
+                       /* support HTTP file upload */
+                       char http100[] = "HTTP/1.1 100 Continue\r\n\r\n";
+                       int ret = write(c->fd, http100, sizeof(http100)-1);
+                       (void)ret;
+               }
+       } else if(strncasecmp("Connection", c->headers[n-1].key, c->headers[n-1].key_sz) == 0) {
+               if(sz == 10 && strncasecmp(at, "Keep-Alive", sz) == 0) {
+                       c->keep_alive = 1;
+               }
+       }
+
+       return 0;
+}
+
+static int
+http_client_on_message_complete(struct http_parser *p) {
+
+       struct http_client *c = p->data;
+
+       /* keep-alive detection */
+       if(c->parser.http_major == 1 && c->parser.http_minor == 1) { /* 1.1 */
+               c->keep_alive = 1;
+       }
+       c->http_version = c->parser.http_minor;
+
+       if(p->upgrade && c->w->s->cfg->websockets) { /* WebSocket, don't execute just yet */
+               c->is_websocket = 1;
+               return 0;
+       }
+
+       /* handle default root object */
+       if(c->path_sz == 1 && *c->path == '/' && c->w->s->cfg->default_root) { /* replace */
+               free(c->path);
+               c->path = strdup(c->w->s->cfg->default_root);
+               c->path_sz = strlen(c->path);
+       }
+
+
+       worker_process_client(c);
+       http_client_reset(c);
+
+       return 0;
+}
+
+struct http_client *
+http_client_new(struct worker *w, int fd, in_addr_t addr) {
+
+       struct http_client *c = calloc(1, sizeof(struct http_client));
+
+       c->fd = fd;
+       c->w = w;
+       c->addr = addr;
+       c->s = w->s;
+
+       /* parser */
+       http_parser_init(&c->parser, HTTP_REQUEST);
+       c->parser.data = c;
+
+       /* callbacks */
+       c->settings.on_url = http_client_on_url;
+       c->settings.on_query_string = http_client_on_query_string;
+       c->settings.on_body = http_client_on_body;
+       c->settings.on_message_complete = http_client_on_message_complete;
+       c->settings.on_header_field = http_client_on_header_name;
+       c->settings.on_header_value = http_client_on_header_value;
+
+       c->last_cb = LAST_CB_NONE;
+
+       return c;
+}
+
+
+void
+http_client_reset(struct http_client *c) {
+
+       int i;
+
+       /* headers */
+       for(i = 0; i < c->header_count; ++i) {
+               free(c->headers[i].key);
+               free(c->headers[i].val);
+       }
+       free(c->headers);
+       c->headers = NULL;
+       c->header_count = 0;
+
+       /* other data */
+       free(c->body); c->body = NULL;
+       c->body_sz = 0;
+       free(c->path); c->path = NULL;
+       c->path_sz = 0;
+       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;
+
+       /* mark as broken if client doesn't support Keep-Alive. */
+       if(c->keep_alive == 0) {
+               c->broken = 1;
+       }
+}
+
+void
+http_client_free(struct http_client *c) {
+
+       http_client_reset(c);
+       free(c->buffer);
+       free(c);
+}
+
+int
+http_client_read(struct http_client *c) {
+
+       char buffer[4096];
+       int ret;
+
+       ret = read(c->fd, buffer, sizeof(buffer));
+       if(ret <= 0) {
+               /* broken link, free buffer and client object */
+
+               /* disconnect pub/sub client if there is one. */
+               if(c->pub_sub && c->pub_sub->ac) {
+                       struct cmd *cmd = c->pub_sub;
+
+                       /* disconnect from all channels */
+                       redisAsyncDisconnect(c->pub_sub->ac);
+                       if(c->pub_sub) c->pub_sub->ac = NULL;
+                       c->pub_sub = NULL;
+
+                       /* delete command object */
+                       cmd_free(cmd);
+               }
+
+               close(c->fd);
+
+               http_client_free(c);
+               return (int)CLIENT_DISCONNECTED;
+       }
+
+       /* save what we've just read */
+       c->buffer = realloc(c->buffer, c->sz + ret);
+       if(!c->buffer) {
+               return (int)CLIENT_OOM;
+       }
+       memcpy(c->buffer + c->sz, buffer, ret);
+       c->sz += ret;
+
+       /* keep track of total sent */
+       c->request_sz += ret;
+
+       return ret;
+}
+
+int
+http_client_remove_data(struct http_client *c, size_t sz) {
+
+       char *buffer;
+       if(c->sz < sz)
+               return -1;
+
+       /* replace buffer */
+       CHECK_ALLOC(c, buffer = malloc(c->sz - sz));
+       memcpy(buffer, c->buffer + sz, c->sz - sz);
+       free(c->buffer);
+       c->buffer = buffer;
+       c->sz -= sz;
+
+       return 0;
+}
+
+int
+http_client_execute(struct http_client *c) {
+
+       int nparsed = http_parser_execute(&c->parser, &c->settings, c->buffer, c->sz);
+
+       if(!c->is_websocket) {
+               /* removed consumed data, all has been copied already. */
+               free(c->buffer);
+               c->buffer = NULL;
+               c->sz = 0;
+       }
+       return nparsed;
+}
+
+/*
+ * Find header value, returns NULL if not found.
+ */
+const char *
+client_get_header(struct http_client *c, const char *key) {
+
+       int i;
+       size_t sz = strlen(key);
+
+       for(i = 0; i < c->header_count; ++i) {
+
+               if(sz == c->headers[i].key_sz &&
+                       strncasecmp(key, c->headers[i].key, sz) == 0) {
+                       return c->headers[i].val;
+               }
+
+       }
+
+       return NULL;
+}
+
diff --git a/client.h b/client.h
new file mode 100644 (file)
index 0000000..72594ef
--- /dev/null
+++ b/client.h
@@ -0,0 +1,93 @@
+#ifndef CLIENT_H
+#define CLIENT_H
+
+#include <event.h>
+#include <arpa/inet.h>
+#include "http_parser.h"
+#include "websocket.h"
+
+struct http_header;
+struct server;
+struct cmd;
+
+typedef enum {
+       LAST_CB_NONE = 0,
+       LAST_CB_KEY = 1,
+       LAST_CB_VAL = 2} last_cb_t;
+
+typedef enum {
+       CLIENT_DISCONNECTED = -1,
+       CLIENT_OOM = -2} client_error_t;
+
+struct http_client {
+
+       int fd;
+       in_addr_t addr;
+       struct event ev;
+
+       struct worker *w;
+       struct server *s;
+
+
+       /* HTTP parsing */
+       struct http_parser parser;
+       struct http_parser_settings settings;
+       char *buffer;
+       size_t sz;
+       size_t request_sz; /* accumulated so far. */
+       last_cb_t last_cb;
+
+       /* various flags. */
+       char keep_alive;
+       char broken;
+       char is_websocket;
+       char http_version;
+       char failed_alloc;
+
+       /* HTTP data */
+       char *path;
+       size_t path_sz;
+
+       /* headers */
+       struct http_header *headers;
+       int header_count;
+
+       char *body;
+       size_t body_sz;
+
+       char *type; /* forced output content-type */
+       char *jsonp; /* jsonp wrapper */
+       char *separator; /* list separator for raw lists */
+       char *filename; /* content-disposition */
+
+       struct cmd *pub_sub;
+
+       struct ws_msg *frame; /* websocket frame */
+};
+
+struct http_client *
+http_client_new(struct worker *w, int fd, in_addr_t addr);
+
+void
+http_client_reset(struct http_client *c);
+
+void
+http_client_free(struct http_client *c);
+
+int
+http_client_read(struct http_client *c);
+
+int
+http_client_remove_data(struct http_client *c, size_t sz);
+
+int
+http_client_execute(struct http_client *c);
+
+int
+http_client_add_to_body(struct http_client *c, const char *at, size_t sz);
+
+const char *
+client_get_header(struct http_client *c, const char *key);
+
+
+#endif
diff --git a/cmd.c b/cmd.c
new file mode 100644 (file)
index 0000000..1c661ca
--- /dev/null
+++ b/cmd.c
@@ -0,0 +1,379 @@
+#include "cmd.h"
+#include "conf.h"
+#include "acl.h"
+#include "client.h"
+#include "pool.h"
+#include "worker.h"
+#include "http.h"
+#include "server.h"
+#include "slog.h"
+
+#include "formats/json.h"
+#include "formats/raw.h"
+#ifdef MSGPACK
+#include "formats/msgpack.h"
+#endif
+#include "formats/custom-type.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <hiredis/hiredis.h>
+#include <hiredis/async.h>
+#include <ctype.h>
+
+struct cmd *
+cmd_new(int count) {
+
+       struct cmd *c = calloc(1, sizeof(struct cmd));
+
+       c->count = count;
+
+       c->argv = calloc(count, sizeof(char*));
+       c->argv_len = calloc(count, sizeof(size_t));
+
+       return c;
+}
+
+
+void
+cmd_free(struct cmd *c) {
+
+       int i;
+       if(!c) return;
+
+       for(i = 0; i < c->count; ++i) {
+               free((char*)c->argv[i]);
+       }
+
+       free(c->jsonp);
+       free(c->separator);
+       free(c->if_none_match);
+       if(c->mime_free) free(c->mime);
+
+       if (c->ac && /* we have a connection */
+               (c->database != c->w->s->cfg->database /* custom DB */
+               || cmd_is_subscribe(c))) {
+               pool_free_context(c->ac);
+       }
+       free(c->argv);
+       free(c->argv_len);
+
+       free(c);
+}
+
+/* taken from libevent */
+static char *
+decode_uri(const char *uri, size_t length, size_t *out_len, int always_decode_plus) {
+       char c;
+       size_t i, j;
+       int in_query = always_decode_plus;
+
+       char *ret = malloc(length);
+
+       for (i = j = 0; i < length; i++) {
+               c = uri[i];
+               if (c == '?') {
+                       in_query = 1;
+               } else if (c == '+' && in_query) {
+                       c = ' ';
+               } else if (c == '%' && isxdigit((unsigned char)uri[i+1]) &&
+                   isxdigit((unsigned char)uri[i+2])) {
+                       char tmp[] = { uri[i+1], uri[i+2], '\0' };
+                       c = (char)strtol(tmp, NULL, 16);
+                       i += 2;
+               }
+               ret[j++] = c;
+       }
+       *out_len = (size_t)j;
+
+       return ret;
+}
+
+/* setup headers */
+void
+cmd_setup(struct cmd *cmd, struct http_client *client) {
+
+       int i;
+       cmd->keep_alive = client->keep_alive;
+       cmd->w = client->w; /* keep track of the worker */
+
+       for(i = 0; i < client->header_count; ++i) {
+               if(strcasecmp(client->headers[i].key, "If-None-Match") == 0) {
+                       cmd->if_none_match = calloc(1+client->headers[i].val_sz, 1);
+                       memcpy(cmd->if_none_match, client->headers[i].val,
+                                       client->headers[i].val_sz);
+               } else if(strcasecmp(client->headers[i].key, "Connection") == 0 &&
+                               strcasecmp(client->headers[i].val, "Keep-Alive") == 0) {
+                       cmd->keep_alive = 1;
+               }
+       }
+
+       if(client->type) {      /* transfer pointer ownership */
+               cmd->mime = client->type;
+               cmd->mime_free = 1;
+               client->type = NULL;
+       }
+
+       if(client->jsonp) {     /* transfer pointer ownership */
+               cmd->jsonp = client->jsonp;
+               client->jsonp = NULL;
+       }
+
+       if(client->separator) { /* transfer pointer ownership */
+               cmd->separator = client->separator;
+               client->separator = NULL;
+       }
+
+       if(client->filename) {  /* transfer pointer ownership */
+               cmd->filename = client->filename;
+               client->filename = NULL;
+       }
+
+       cmd->fd = client->fd;
+       cmd->http_version = client->http_version;
+}
+
+
+cmd_response_t
+cmd_run(struct worker *w, struct http_client *client,
+               const char *uri, size_t uri_len,
+               const char *body, size_t body_len) {
+
+       char *qmark = memchr(uri, '?', uri_len);
+       char *slash;
+       const char *p, *cmd_name = uri;
+       int cmd_len;
+       int param_count = 0, cur_param = 1;
+
+       struct cmd *cmd;
+       formatting_fun f_format;
+
+       /* count arguments */
+       if(qmark) {
+               uri_len = qmark - uri;
+       }
+       for(p = uri; p && p < uri + uri_len; param_count++) {
+               p = memchr(p+1, '/', uri_len - (p+1-uri));
+       }
+
+       if(body && body_len) { /* PUT request */
+               param_count++;
+       }
+       if(param_count == 0) {
+               return CMD_PARAM_ERROR;
+       }
+
+       cmd = cmd_new(param_count);
+       cmd->fd = client->fd;
+       cmd->database = w->s->cfg->database;
+
+       /* get output formatting function */
+       uri_len = cmd_select_format(client, cmd, uri, uri_len, &f_format);
+
+       /* add HTTP info */
+       cmd_setup(cmd, client);
+
+       /* check if we only have one command or more. */
+       slash = memchr(uri, '/', uri_len);
+       if(slash) {
+
+               /* detect DB number by checking if first arg is only numbers */
+               int has_db = 1;
+               int db_num = 0;
+               for(p = uri; p < slash; ++p) {
+                       if(*p < '0' || *p > '9') {
+                               has_db = 0;
+                               break;
+                       }
+                       db_num = db_num * 10 + (*p - '0');
+               }
+
+               /* shift to next arg if a db was set up */
+               if(has_db) {
+                       char *next;
+                       cmd->database = db_num;
+                       cmd->count--; /* overcounted earlier */
+                       cmd_name = slash + 1;
+
+                       if((next = memchr(cmd_name, '/', uri_len - (slash - uri)))) {
+                               cmd_len = next - cmd_name;
+                       } else {
+                               cmd_len = uri_len - (slash - uri + 1);
+                       }
+               } else {
+                       cmd_len = slash - uri;
+               }
+       } else {
+               cmd_len = uri_len;
+       }
+
+       /* there is always a first parameter, it's the command name */
+       cmd->argv[0] = malloc(cmd_len);
+       memcpy(cmd->argv[0], cmd_name, cmd_len);
+       cmd->argv_len[0] = cmd_len;
+
+       /* check that the client is able to run this command */
+       if(!acl_allow_command(cmd, w->s->cfg, client)) {
+               cmd_free(cmd);
+               return CMD_ACL_FAIL;
+       }
+
+       if(cmd_is_subscribe(cmd)) {
+               /* create a new connection to Redis */
+               cmd->ac = (redisAsyncContext*)pool_connect(w->pool, cmd->database, 0);
+
+               /* register with the client, used upon disconnection */
+               client->pub_sub = cmd;
+               cmd->pub_sub_client = client;
+       } else if(cmd->database != w->s->cfg->database) {
+               /* create a new connection to Redis for custom DBs */
+               cmd->ac = (redisAsyncContext*)pool_connect(w->pool, cmd->database, 0);
+       } else {
+               /* get a connection from the pool */
+               cmd->ac = (redisAsyncContext*)pool_get_context(w->pool);
+       }
+
+       /* no args (e.g. INFO command) */
+       if(!slash) {
+               if(!cmd->ac) {
+                       cmd_free(cmd);
+                       return CMD_REDIS_UNAVAIL;
+               }
+               redisAsyncCommandArgv(cmd->ac, f_format, cmd, 1,
+                               (const char **)cmd->argv, cmd->argv_len);
+               return CMD_SENT;
+       }
+       p = cmd_name + cmd_len + 1;
+       while(p < uri + uri_len) {
+
+               const char *arg = p;
+               int arg_len;
+               char *next = memchr(arg, '/', uri_len - (arg-uri));
+               if(!next || next > uri + uri_len) { /* last argument */
+                       p = uri + uri_len;
+                       arg_len = p - arg;
+               } else { /* found a slash */
+                       arg_len = next - arg;
+                       p = next + 1;
+               }
+
+               /* record argument */
+               cmd->argv[cur_param] = decode_uri(arg, arg_len, &cmd->argv_len[cur_param], 1);
+               cur_param++;
+       }
+
+       if(body && body_len) { /* PUT request */
+               cmd->argv[cur_param] = malloc(body_len);
+               memcpy(cmd->argv[cur_param], body, body_len);
+               cmd->argv_len[cur_param] = body_len;
+       }
+
+       /* send it off! */
+       if(cmd->ac) {
+               cmd_send(cmd, f_format);
+               return CMD_SENT;
+       }
+       /* failed to find a suitable connection to Redis. */
+       cmd_free(cmd);
+       client->pub_sub = NULL;
+       return CMD_REDIS_UNAVAIL;
+}
+
+void
+cmd_send(struct cmd *cmd, formatting_fun f_format) {
+       redisAsyncCommandArgv(cmd->ac, f_format, cmd, cmd->count,
+               (const char **)cmd->argv, cmd->argv_len);
+}
+
+/**
+ * Select Content-Type and processing function.
+ */
+int
+cmd_select_format(struct http_client *client, struct cmd *cmd,
+               const char *uri, size_t uri_len, formatting_fun *f_format) {
+
+       const char *ext;
+       int ext_len = -1;
+       unsigned int i;
+       int found = 0; /* did we match it to a predefined format? */
+
+       /* those are the available reply formats */
+       struct reply_format {
+               const char *s;
+               size_t sz;
+               formatting_fun f;
+               const char *ct;
+       };
+       struct reply_format funs[] = {
+               {.s = "json", .sz = 4, .f = json_reply, .ct = "application/json"},
+               {.s = "raw", .sz = 3, .f = raw_reply, .ct = "binary/octet-stream"},
+
+#ifdef MSGPACK
+               {.s = "msg", .sz = 3, .f = msgpack_reply, .ct = "application/x-msgpack"},
+#endif
+
+               {.s = "bin", .sz = 3, .f = custom_type_reply, .ct = "binary/octet-stream"},
+               {.s = "txt", .sz = 3, .f = custom_type_reply, .ct = "text/plain"},
+               {.s = "html", .sz = 4, .f = custom_type_reply, .ct = "text/html"},
+               {.s = "xhtml", .sz = 5, .f = custom_type_reply, .ct = "application/xhtml+xml"},
+               {.s = "xml", .sz = 3, .f = custom_type_reply, .ct = "text/xml"},
+
+               {.s = "png", .sz = 3, .f = custom_type_reply, .ct = "image/png"},
+               {.s = "jpg", .sz = 3, .f = custom_type_reply, .ct = "image/jpeg"},
+               {.s = "jpeg", .sz = 4, .f = custom_type_reply, .ct = "image/jpeg"},
+
+               {.s = "js", .sz = 2, .f = json_reply, .ct = "application/javascript"},
+               {.s = "css", .sz = 3, .f = custom_type_reply, .ct = "text/css"},
+       };
+
+       /* default */
+       *f_format = json_reply;
+
+       /* find extension */
+       for(ext = uri + uri_len - 1; ext != uri && *ext != '/'; --ext) {
+               if(*ext == '.') {
+                       ext++;
+                       ext_len = uri + uri_len - ext;
+                       break;
+               }
+       }
+       if(!ext_len) return uri_len; /* nothing found */
+
+       /* find function for the given extension */
+       for(i = 0; i < sizeof(funs)/sizeof(funs[0]); ++i) {
+               if(ext_len == (int)funs[i].sz && strncmp(ext, funs[i].s, ext_len) == 0) {
+
+                       if(cmd->mime_free) free(cmd->mime);
+                       cmd->mime = (char*)funs[i].ct;
+                       cmd->mime_free = 0;
+
+                       *f_format = funs[i].f;
+                       found = 1;
+               }
+       }
+
+       /* the user can force it with ?type=some/thing */
+       if(client->type) {
+               *f_format = custom_type_reply;
+               cmd->mime = strdup(client->type);
+               cmd->mime_free = 1;
+       }
+
+       if(found) {
+               return uri_len - ext_len - 1;
+       } else {
+               /* no matching format, use default output with the full argument, extension included. */
+               return uri_len;
+       }
+}
+
+int
+cmd_is_subscribe(struct cmd *cmd) {
+
+       if(cmd->count >= 1 && cmd->argv[0] &&
+               (strncasecmp(cmd->argv[0], "SUBSCRIBE", cmd->argv_len[0]) == 0 ||
+               strncasecmp(cmd->argv[0], "PSUBSCRIBE", cmd->argv_len[0]) == 0)) {
+               return 1;
+       }
+       return 0;
+}
diff --git a/cmd.h b/cmd.h
new file mode 100644 (file)
index 0000000..e216bde
--- /dev/null
+++ b/cmd.h
@@ -0,0 +1,80 @@
+#ifndef CMD_H
+#define CMD_H
+
+#include <stdlib.h>
+#include <hiredis/async.h>
+#include <sys/queue.h>
+#include <event.h>
+#include <evhttp.h>
+
+struct evhttp_request;
+struct http_client;
+struct server;
+struct worker;
+struct cmd;
+
+typedef void (*formatting_fun)(redisAsyncContext *, void *, void *);
+typedef enum {CMD_SENT,
+       CMD_PARAM_ERROR,
+       CMD_ACL_FAIL,
+       CMD_REDIS_UNAVAIL} cmd_response_t;
+
+struct cmd {
+       int fd;
+
+       int count;
+       char **argv;
+       size_t *argv_len;
+
+       /* HTTP data */
+       char *mime; /* forced output content-type */
+       int mime_free; /* need to free mime buffer */
+
+       char *filename; /* content-disposition attachment */
+
+       char *if_none_match; /* used with ETags */
+       char *jsonp; /* jsonp wrapper */
+       char *separator; /* list separator for raw lists */
+       int keep_alive;
+
+       /* various flags */
+       int started_responding;
+       int is_websocket;
+       int http_version;
+       int database;
+
+       struct http_client *pub_sub_client;
+       redisAsyncContext *ac;
+       struct worker *w;
+};
+
+struct subscription {
+       struct server *s;
+       struct cmd *cmd;
+};
+
+struct cmd *
+cmd_new(int count);
+
+void
+cmd_free(struct cmd *c);
+
+cmd_response_t
+cmd_run(struct worker *w, struct http_client *client,
+               const char *uri, size_t uri_len,
+               const char *body, size_t body_len);
+
+int
+cmd_select_format(struct http_client *client, struct cmd *cmd,
+               const char *uri, size_t uri_len, formatting_fun *f_format);
+
+int
+cmd_is_subscribe(struct cmd *cmd);
+
+void
+cmd_send(struct cmd *cmd, formatting_fun f_format);
+
+void
+cmd_setup(struct cmd *cmd, struct http_client *client);
+
+#endif
diff --git a/conf.c b/conf.c
new file mode 100644 (file)
index 0000000..4fce300
--- /dev/null
+++ b/conf.c
@@ -0,0 +1,233 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include <jansson.h>
+#include <evhttp.h>
+#include <b64/cencode.h>
+#include "conf.h"
+#include "acl.h"
+
+static struct acl *
+conf_parse_acls(json_t *jtab);
+
+struct conf *
+conf_read(const char *filename) {
+
+       json_t *j;
+       json_error_t error;
+       struct conf *conf;
+       void *kv;
+
+       /* defaults */
+       conf = calloc(1, sizeof(struct conf));
+       conf->redis_host = strdup("127.0.0.1");
+       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();
+       conf->logfile = "webdis.log";
+       conf->verbosity = WEBDIS_NOTICE;
+       conf->daemonize = 0;
+       conf->pidfile = "webdis.pid";
+       conf->database = 0;
+       conf->pool_size_per_thread = 2;
+
+       j = json_load_file(filename, 0, &error);
+       if(!j) {
+               fprintf(stderr, "Error: %s (line %d)\n", error.text, error.line);
+               return conf;
+       }
+
+       for(kv = json_object_iter(j); kv; kv = json_object_iter_next(j, kv)) {
+               json_t *jtmp = json_object_iter_value(kv);
+
+               if(strcmp(json_object_iter_key(kv), "redis_host") == 0 && json_typeof(jtmp) == JSON_STRING) {
+                       free(conf->redis_host);
+                       conf->redis_host = strdup(json_string_value(jtmp));
+               } else if(strcmp(json_object_iter_key(kv), "redis_port") == 0 && json_typeof(jtmp) == JSON_INTEGER) {
+                       conf->redis_port = (short)json_integer_value(jtmp);
+               } else if(strcmp(json_object_iter_key(kv), "redis_auth") == 0 && json_typeof(jtmp) == JSON_STRING) {
+                       conf->redis_auth = strdup(json_string_value(jtmp));
+               } else if(strcmp(json_object_iter_key(kv), "http_host") == 0 && json_typeof(jtmp) == JSON_STRING) {
+                       free(conf->http_host);
+                       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) {
+                       conf->perms = conf_parse_acls(jtmp);
+               } else if(strcmp(json_object_iter_key(kv), "user") == 0 && json_typeof(jtmp) == JSON_STRING) {
+                       struct passwd *u;
+                       if((u = getpwnam(json_string_value(jtmp)))) {
+                               conf->user = u->pw_uid;
+                       }
+               } else if(strcmp(json_object_iter_key(kv), "group") == 0 && json_typeof(jtmp) == JSON_STRING) {
+                       struct group *g;
+                       if((g = getgrnam(json_string_value(jtmp)))) {
+                               conf->group = g->gr_gid;
+                       }
+               } else if(strcmp(json_object_iter_key(kv),"logfile") == 0 && json_typeof(jtmp) == JSON_STRING){
+                       conf->logfile = strdup(json_string_value(jtmp));
+               } else if(strcmp(json_object_iter_key(kv),"verbosity") == 0 && json_typeof(jtmp) == JSON_INTEGER){
+                       int tmp = json_integer_value(jtmp);
+                       if(tmp < 0) conf->verbosity = WEBDIS_ERROR;
+                       else if(tmp > (int)WEBDIS_DEBUG) conf->verbosity = WEBDIS_DEBUG;
+                       else conf->verbosity = (log_level)tmp;
+               } else if(strcmp(json_object_iter_key(kv), "daemonize") == 0 && json_typeof(jtmp) == JSON_TRUE) {
+                       conf->daemonize = 1;
+               } else if(strcmp(json_object_iter_key(kv),"pidfile") == 0 && json_typeof(jtmp) == JSON_STRING){
+                       conf->pidfile = strdup(json_string_value(jtmp));
+               } else if(strcmp(json_object_iter_key(kv), "websockets") == 0 && json_typeof(jtmp) == JSON_TRUE) {
+                       conf->websockets = 1;
+               } else if(strcmp(json_object_iter_key(kv), "database") == 0 && json_typeof(jtmp) == JSON_INTEGER) {
+                       conf->database = json_integer_value(jtmp);
+               } else if(strcmp(json_object_iter_key(kv), "pool_size") == 0 && json_typeof(jtmp) == JSON_INTEGER) {
+                       conf->pool_size_per_thread = json_integer_value(jtmp);
+               } else if(strcmp(json_object_iter_key(kv), "default_root") == 0 && json_typeof(jtmp) == JSON_STRING) {
+                       conf->default_root = strdup(json_string_value(jtmp));
+               }
+       }
+
+       json_decref(j);
+
+       return conf;
+}
+
+void
+acl_read_commands(json_t *jlist, struct acl_commands *ac) {
+
+       unsigned int i, n, cur;
+
+       /* count strings in the array */
+       for(i = 0, n = 0; i < json_array_size(jlist); ++i) {
+               json_t *jelem = json_array_get(jlist, (size_t)i);
+               if(json_typeof(jelem) == JSON_STRING) {
+                       n++;
+               }
+       }
+
+       /* allocate block */
+       ac->commands = calloc((size_t)n, sizeof(char*));
+       ac->count = n;
+
+       /* add all disabled commands */
+       for(i = 0, cur = 0; i < json_array_size(jlist); ++i) {
+               json_t *jelem = json_array_get(jlist, i);
+               if(json_typeof(jelem) == JSON_STRING) {
+                       size_t sz;
+                       const char *s = json_string_value(jelem);
+                       sz = strlen(s);
+
+                       ac->commands[cur] = calloc(1 + sz, 1);
+                       memcpy(ac->commands[cur], s, sz);
+                       cur++;
+               }
+       }
+}
+
+struct acl *
+conf_parse_acl(json_t *j) {
+
+       json_t *jcidr, *jbasic, *jlist;
+       unsigned short mask_bits = 0;
+
+       struct acl *a = calloc(1, sizeof(struct acl));
+
+       /* parse CIDR */
+       if((jcidr = json_object_get(j, "ip")) && json_typeof(jcidr) == JSON_STRING) {
+               const char *s;
+               char *p, *ip;
+
+               s = json_string_value(jcidr);
+               p = strchr(s, '/');
+               if(!p) {
+                       ip = strdup(s);
+               } else {
+                       ip = calloc((size_t)(p - s + 1), 1);
+                       memcpy(ip, s, (size_t)(p - s));
+                       mask_bits = (unsigned short)atoi(p+1);
+               }
+               a->cidr.enabled = 1;
+               a->cidr.mask = (mask_bits == 0 ? 0xffffffff : (0xffffffff << (32 - mask_bits)));
+               a->cidr.subnet = ntohl(inet_addr(ip)) & a->cidr.mask;
+               free(ip);
+       }
+
+       /* parse basic_auth */
+       if((jbasic = json_object_get(j, "http_basic_auth")) && json_typeof(jbasic) == JSON_STRING) {
+
+               /* base64 encode */
+               base64_encodestate b64;
+               int pos;
+               char *p;
+               const char *plain = json_string_value(jbasic);
+               size_t len, plain_len = strlen(plain) + 0;
+               len = (plain_len + 8) * 8 / 6;
+               a->http_basic_auth = calloc(len, 1);
+               
+               base64_init_encodestate(&b64);
+               pos = base64_encode_block(plain, (int)plain_len, a->http_basic_auth, &b64); /* FIXME: check return value */
+               base64_encode_blockend(a->http_basic_auth + pos, &b64);
+
+               /* end string with \0 rather than \n */
+               if((p = strchr(a->http_basic_auth + pos, '\n'))) {
+                       *p = 0;
+               }
+       }
+
+       /* parse enabled commands */
+       if((jlist = json_object_get(j, "enabled")) && json_typeof(jlist) == JSON_ARRAY) {
+               acl_read_commands(jlist, &a->enabled);
+       }
+
+       /* parse disabled commands */
+       if((jlist = json_object_get(j, "disabled")) && json_typeof(jlist) == JSON_ARRAY) {
+               acl_read_commands(jlist, &a->disabled);
+       }
+
+       return a;
+}
+
+struct acl *
+conf_parse_acls(json_t *jtab) {
+
+       struct acl *head = NULL, *tail = NULL, *tmp;
+
+       unsigned int i;
+       for(i = 0; i < json_array_size(jtab); ++i) {
+               json_t *val = json_array_get(jtab, i);
+
+               tmp = conf_parse_acl(val);
+               if(head == NULL && tail == NULL) {
+                       head = tail = tmp;
+               } else {
+                       tail->next = tmp;
+                       tail = tmp;
+               }
+       }
+
+       return head;
+}
+
+void
+conf_free(struct conf *conf) {
+
+       free(conf->redis_host);
+       free(conf->redis_auth);
+
+       free(conf->http_host);
+
+       free(conf);
+}
diff --git a/conf.h b/conf.h
new file mode 100644 (file)
index 0000000..28ba115
--- /dev/null
+++ b/conf.h
@@ -0,0 +1,54 @@
+#ifndef CONF_H
+#define CONF_H
+
+#include <sys/types.h>
+#include "slog.h"
+
+struct conf {
+
+       /* connection to Redis */
+       char *redis_host;
+       short redis_port;
+       char *redis_auth;
+
+       /* HTTP server interface */
+       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;
+
+       /* daemonize process, off by default */
+       int daemonize;
+       char *pidfile;
+
+       /* WebSocket support, off by default */
+       int websockets;
+
+       /* database number */
+       int database;
+
+       /* ACL */
+       struct acl *perms;
+
+       /* user/group */
+       uid_t user;
+       gid_t group;
+
+       /* Logging */
+       char *logfile;
+       log_level verbosity;
+
+       /* Request to serve on “/” */
+       char *default_root;
+};
+
+struct conf *
+conf_read(const char *filename);
+
+void
+conf_free(struct conf *conf);
+
+#endif /* CONF_H */
diff --git a/formats/common.c b/formats/common.c
new file mode 100644 (file)
index 0000000..e0b456f
--- /dev/null
@@ -0,0 +1,141 @@
+#include "common.h"
+#include "cmd.h"
+#include "http.h"
+#include "client.h"
+#include "websocket.h"
+
+#include "md5/md5.h"
+#include <string.h>
+#include <unistd.h>
+
+/* TODO: replace this with a faster hash function? */
+char *etag_new(const char *p, size_t sz) {
+
+       md5_byte_t buf[16];
+       char *etag = calloc(34 + 1, 1);
+       int i;
+
+       if(!etag)
+               return NULL;
+
+       md5_state_t pms;
+
+       md5_init(&pms);
+       md5_append(&pms, (const md5_byte_t *)p, (int)sz);
+       md5_finish(&pms, buf);
+
+       for(i = 0; i < 16; ++i) {
+               sprintf(etag + 1 + 2*i, "%.2x", (unsigned char)buf[i]);
+       }
+
+       etag[0] = '"';
+       etag[33] = '"';
+
+       return etag;
+}
+
+void
+format_send_error(struct cmd *cmd, short code, const char *msg) {
+
+       struct http_response *resp;
+
+       if(!cmd->is_websocket && !cmd->pub_sub_client) {
+               resp = http_response_init(cmd->w, code, msg);
+               resp->http_version = cmd->http_version;
+               http_response_set_keep_alive(resp, cmd->keep_alive);
+               http_response_write(resp, cmd->fd);
+       }
+
+       /* for pub/sub, remove command from client */
+       if(cmd->pub_sub_client) {
+               cmd->pub_sub_client->pub_sub = NULL;
+       } else {
+               cmd_free(cmd);
+       }
+}
+
+void
+format_send_reply(struct cmd *cmd, const char *p, size_t sz, const char *content_type) {
+
+       int free_cmd = 1;
+       const char *ct = cmd->mime?cmd->mime:content_type;
+       struct http_response *resp;
+
+       if(cmd->is_websocket) {
+               ws_reply(cmd, p, sz);
+
+               /* If it's a subscribe command, there'll be more responses */
+               if(!cmd_is_subscribe(cmd))
+                       cmd_free(cmd);
+               return;
+       }
+
+       if(cmd_is_subscribe(cmd)) {
+               free_cmd = 0;
+
+               /* start streaming */
+               if(cmd->started_responding == 0) {
+                       cmd->started_responding = 1;
+                       resp = http_response_init(cmd->w, 200, "OK");
+                       resp->http_version = cmd->http_version;
+                       if(cmd->filename) {
+                               http_response_set_header(resp, "Content-Disposition", cmd->filename);
+                       }
+                       http_response_set_header(resp, "Content-Type", ct);
+                       http_response_set_keep_alive(resp, 1);
+                       http_response_set_header(resp, "Transfer-Encoding", "chunked");
+                       http_response_set_body(resp, p, sz);
+                       http_response_write(resp, cmd->fd);
+               } else {
+                       /* Asynchronous chunk write. */
+                       http_response_write_chunk(cmd->fd, cmd->w, p, sz);
+               }
+
+       } else {
+               /* compute ETag */
+               char *etag = etag_new(p, sz);
+
+               if(etag) {
+                       /* check If-None-Match */
+                       if(cmd->if_none_match && strcmp(cmd->if_none_match, etag) == 0) {
+                               /* SAME! send 304. */
+                               resp = http_response_init(cmd->w, 304, "Not Modified");
+                       } else {
+                               resp = http_response_init(cmd->w, 200, "OK");
+                               if(cmd->filename) {
+                                       http_response_set_header(resp, "Content-Disposition", cmd->filename);
+                               }
+                               http_response_set_header(resp, "Content-Type", ct);
+                               http_response_set_header(resp, "ETag", etag);
+                               http_response_set_body(resp, p, sz);
+                       }
+                       resp->http_version = cmd->http_version;
+                       http_response_set_keep_alive(resp, cmd->keep_alive);
+                       http_response_write(resp, cmd->fd);
+                       free(etag);
+               } else {
+                       format_send_error(cmd, 503, "Service Unavailable");
+               }
+       }
+       
+       /* cleanup */
+       if(free_cmd) {
+               cmd_free(cmd);
+       }
+}
+
+int
+integer_length(long long int i) {
+       int sz = 0;
+       int ci = abs(i);
+       while (ci > 0) {
+               ci = (ci/10);
+               sz += 1;
+       }
+       if(i == 0) { /* log 0 doesn't make sense. */
+               sz = 1;
+       } else if(i < 0) { /* allow for neg sign as well. */
+               sz++;
+       }
+       return sz;
+}
diff --git a/formats/common.h b/formats/common.h
new file mode 100644 (file)
index 0000000..b4bfeb5
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef FORMATS_COMMON_H
+#define FORMATS_COMMON_H
+
+#include <stdlib.h>
+
+struct cmd;
+
+void
+format_send_reply(struct cmd *cmd,
+               const char *p, size_t sz,
+               const char *content_type);
+
+void
+format_send_error(struct cmd *cmd, short code, const char *msg);
+int
+integer_length(long long int i);
+
+#endif
diff --git a/formats/custom-type.c b/formats/custom-type.c
new file mode 100644 (file)
index 0000000..3fc6e08
--- /dev/null
@@ -0,0 +1,118 @@
+#include "custom-type.h"
+#include "cmd.h"
+#include "common.h"
+#include "http.h"
+
+#include <string.h>
+#include <hiredis/hiredis.h>
+#include <hiredis/async.h>
+
+static char *
+custom_array(struct cmd *cmd, const redisReply *r, size_t *sz);
+
+void
+custom_type_reply(redisAsyncContext *c, void *r, void *privdata) {
+
+       redisReply *reply = r;
+       struct cmd *cmd = privdata;
+       (void)c;
+       char int_buffer[50];
+       char *status_buf;
+       int int_len;
+       struct http_response *resp;
+       size_t sz;
+       char *array_out;
+
+
+       if (reply == NULL) { /* broken Redis link */
+               format_send_error(cmd, 503, "Service Unavailable");
+               return;
+       }
+
+       if(cmd->mime) { /* use the given content-type, but only for strings */
+               switch(reply->type) {
+
+                       case REDIS_REPLY_NIL: /* or nil values */
+                               format_send_error(cmd, 404, "Not found");
+                               return;
+
+                       case REDIS_REPLY_STRING:
+                               format_send_reply(cmd, reply->str, reply->len, cmd->mime);
+                               return;
+
+                       case REDIS_REPLY_STATUS:
+                       case REDIS_REPLY_ERROR:
+                               status_buf = calloc(1 + reply->len, 1);
+                               status_buf[0] = (reply->type == REDIS_REPLY_STATUS ? '+' : '-');
+                               memcpy(status_buf + 1, reply->str, reply->len);
+                               format_send_reply(cmd, status_buf, 1 + reply->len, cmd->mime);
+                               free(status_buf);
+                               return;
+
+                       case REDIS_REPLY_INTEGER:
+                               int_len = sprintf(int_buffer, "%lld", reply->integer);
+                               format_send_reply(cmd, int_buffer, int_len, cmd->mime);
+                               return;
+                       case REDIS_REPLY_ARRAY:
+                               array_out = custom_array(cmd, r, &sz);
+                               format_send_reply(cmd, array_out, sz, cmd->mime);
+                               free(array_out);
+                               return;
+               }
+       }
+
+       /* couldn't make sense of what the client wanted. */
+       resp = http_response_init(cmd->w, 400, "Bad Request");
+       http_response_set_header(resp, "Content-Length", "0");
+       http_response_set_keep_alive(resp, cmd->keep_alive);
+       http_response_write(resp, cmd->fd);
+
+       if(!cmd_is_subscribe(cmd)) {
+               cmd_free(cmd);
+       }
+}
+
+static char *
+custom_array(struct cmd *cmd, const redisReply *r, size_t *sz) {
+
+       unsigned int i;
+       char *ret, *p;
+       size_t sep_len = 0;
+
+       if(cmd->separator)
+               sep_len = strlen(cmd->separator);
+
+       /* compute size */
+       *sz = 0;
+       for(i = 0; i < r->elements; ++i) {
+               redisReply *e = r->element[i];
+               switch(e->type) {
+                       case REDIS_REPLY_STRING:
+                               if(sep_len && i != 0)
+                                       *sz += sep_len;
+                               *sz += e->len;
+                               break;
+
+               }
+       }
+
+       /* allocate */
+       p = ret = malloc(*sz);
+
+       /* copy */
+       for(i = 0; i < r->elements; ++i) {
+               redisReply *e = r->element[i];
+               switch(e->type) {
+                       case REDIS_REPLY_STRING:
+                               if(sep_len && i != 0) {
+                                       memcpy(p, cmd->separator, sep_len);
+                                       p += sep_len;
+                               }
+                               memcpy(p, e->str, e->len);
+                               p += e->len;
+                               break;
+               }
+       }
+
+       return ret;
+}
diff --git a/formats/custom-type.h b/formats/custom-type.h
new file mode 100644 (file)
index 0000000..cb5b7be
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef CUSTOM_TYPE_H
+#define CUSTOM_TYPE_H
+
+#include <hiredis/hiredis.h>
+#include <hiredis/async.h>
+
+struct cmd;
+
+void
+custom_type_reply(redisAsyncContext *c, void *r, void *privdata);
+
+#endif
diff --git a/formats/json.c b/formats/json.c
new file mode 100644 (file)
index 0000000..56c3a95
--- /dev/null
@@ -0,0 +1,293 @@
+#include "json.h"
+#include "common.h"
+#include "cmd.h"
+#include "http.h"
+#include "client.h"
+
+#include <string.h>
+#include <hiredis/hiredis.h>
+#include <hiredis/async.h>
+
+static json_t *
+json_wrap_redis_reply(const struct cmd *cmd, const redisReply *r);
+
+void
+json_reply(redisAsyncContext *c, void *r, void *privdata) {
+
+       redisReply *reply = r;
+       struct cmd *cmd = privdata;
+       json_t *j;
+       char *jstr;
+       (void)c;
+
+       if(cmd == NULL) {
+               /* broken connection */
+               return;
+       }
+
+       if (reply == NULL) { /* broken Redis link */
+               format_send_error(cmd, 503, "Service Unavailable");
+               return;
+       }
+
+       /* encode redis reply as JSON */
+       j = json_wrap_redis_reply(cmd, r);
+
+       /* get JSON as string, possibly with JSONP wrapper */
+       jstr = json_string_output(j, cmd->jsonp);
+
+       /* send reply */
+       format_send_reply(cmd, jstr, strlen(jstr), "application/json");
+
+       /* cleanup */
+       json_decref(j);
+       free(jstr);
+}
+
+/**
+ * Parse info message and return object.
+ */
+static json_t *
+json_info_reply(const char *s) {
+       const char *p = s;
+       size_t sz = strlen(s);
+
+       json_t *jroot = json_object();
+
+       /* TODO: handle new format */
+
+       while(p < s + sz) {
+               char *key, *val, *nl, *colon;
+
+               /* find key */
+               colon = strchr(p, ':');
+               if(!colon) {
+                       break;
+               }
+               key = calloc(colon - p + 1, 1);
+               memcpy(key, p, colon - p);
+               p = colon + 1;
+
+               /* find value */
+               nl = strchr(p, '\r');
+               if(!nl) {
+                       free(key);
+                       break;
+               }
+               val = calloc(nl - p + 1, 1);
+               memcpy(val, p, nl - p);
+               p = nl + 1;
+               if(*p == '\n') p++;
+
+               /* add to object */
+               json_object_set_new(jroot, key, json_string(val));
+               free(key);
+               free(val);
+       }
+
+       return jroot;
+}
+
+static json_t *
+json_hgetall_reply(const redisReply *r) {
+       /* zip keys and values together in a json object */
+       json_t *jroot;
+       unsigned int i;
+
+       if(r->elements % 2 != 0) {
+               return NULL;
+       }
+
+       jroot = json_object();
+       for(i = 0; i < r->elements; i += 2) {
+               redisReply *k = r->element[i], *v = r->element[i+1];
+
+               /* keys and values need to be strings */
+               if(k->type != REDIS_REPLY_STRING || v->type != REDIS_REPLY_STRING) {
+                       json_decref(jroot);
+                       return NULL;
+               }
+               json_object_set_new(jroot, k->str, json_string(v->str));
+       }
+       return jroot;
+}
+
+static json_t *
+json_wrap_redis_reply(const struct cmd *cmd, const redisReply *r) {
+
+       unsigned int i;
+       json_t *jlist, *jroot = json_object(); /* that's what we return */
+
+
+       /* copy verb, as jansson only takes a char* but not its length. */
+       char *verb;
+       if(cmd->count) {
+               verb = calloc(cmd->argv_len[0]+1, 1);
+               memcpy(verb, cmd->argv[0], cmd->argv_len[0]);
+       } else {
+               verb = strdup("");
+       }
+
+
+       switch(r->type) {
+               case REDIS_REPLY_STATUS:
+               case REDIS_REPLY_ERROR:
+                       jlist = json_array();
+                       json_array_append_new(jlist,
+                               r->type == REDIS_REPLY_ERROR ? json_false() : json_true());
+                       json_array_append_new(jlist, json_string(r->str));
+                       json_object_set_new(jroot, verb, jlist);
+                       break;
+
+               case REDIS_REPLY_STRING:
+                       if(strcasecmp(verb, "INFO") == 0) {
+                               json_object_set_new(jroot, verb, json_info_reply(r->str));
+                       } else {
+                               json_object_set_new(jroot, verb, json_string(r->str));
+                       }
+                       break;
+
+               case REDIS_REPLY_INTEGER:
+                       json_object_set_new(jroot, verb, json_integer(r->integer));
+                       break;
+
+               case REDIS_REPLY_ARRAY:
+                       if(strcasecmp(verb, "HGETALL") == 0) {
+                               json_t *jobj = json_hgetall_reply(r);
+                               if(jobj) {
+                                       json_object_set_new(jroot, verb, jobj);
+                                       break;
+                               }
+                       }
+                       jlist = json_array();
+                       for(i = 0; i < r->elements; ++i) {
+                               redisReply *e = r->element[i];
+                               switch(e->type) {
+                                       case REDIS_REPLY_STRING:
+                                               json_array_append_new(jlist, json_string(e->str));
+                                               break;
+                                       case REDIS_REPLY_INTEGER:
+                                               json_array_append_new(jlist, json_integer(e->integer));
+                                               break;
+                                       default:
+                                               json_array_append_new(jlist, json_null());
+                                               break;
+                               }
+                       }
+                       json_object_set_new(jroot, verb, jlist);
+                       break;
+
+               default:
+                       json_object_set_new(jroot, verb, json_null());
+                       break;
+       }
+
+       free(verb);
+       return jroot;
+}
+
+
+char *
+json_string_output(json_t *j, const char *jsonp) {
+
+       char *json_reply = json_dumps(j, JSON_COMPACT);
+
+       /* check for JSONP */
+       if(jsonp) {
+               size_t jsonp_len = strlen(jsonp);
+               size_t json_len = strlen(json_reply);
+               size_t ret_len = jsonp_len + 1 + json_len + 3;
+               char *ret = calloc(1 + ret_len, 1);
+
+               memcpy(ret, jsonp, jsonp_len);
+               ret[jsonp_len]='(';
+               memcpy(ret + jsonp_len + 1, json_reply, json_len);
+               memcpy(ret + jsonp_len + 1 + json_len, ");\n", 3);
+               free(json_reply);
+
+               return ret;
+       }
+
+       return json_reply;
+}
+
+/* extract JSON from WebSocket frame and fill struct cmd. */
+struct cmd *
+json_ws_extract(struct http_client *c, const char *p, size_t sz) {
+
+       struct cmd *cmd = NULL;
+       json_t *j;
+       char *jsonz; /* null-terminated */
+
+       unsigned int i, cur;
+       int argc = 0;
+       json_error_t jerror;
+
+       (void)c;
+
+       jsonz = calloc(sz + 1, 1);
+       memcpy(jsonz, p, sz);
+       j = json_loads(jsonz, sz, &jerror);
+       free(jsonz);
+
+       if(!j) {
+               return NULL;
+       }
+       if(json_typeof(j) != JSON_ARRAY) {
+               json_decref(j);
+               return NULL; /* invalid JSON */
+       }
+
+       /* count elements */
+       for(i = 0; i < json_array_size(j); ++i) {
+               json_t *jelem = json_array_get(j, i);
+
+               switch(json_typeof(jelem)) {
+                       case JSON_STRING:
+                       case JSON_INTEGER:
+                               argc++;
+                               break;
+
+                       default:
+                               break;
+               }
+       }
+
+       if(!argc) { /* not a single item could be decoded */
+               json_decref(j);
+               return NULL;
+       }
+
+       /* create command and add args */
+       cmd = cmd_new(argc);
+       for(i = 0, cur = 0; i < json_array_size(j); ++i) {
+               json_t *jelem = json_array_get(j, i);
+               char *tmp;
+
+               switch(json_typeof(jelem)) {
+                       case JSON_STRING:
+                               tmp = strdup(json_string_value(jelem));
+
+                               cmd->argv[cur] = tmp;
+                               cmd->argv_len[cur] = strlen(tmp);
+                               cur++;
+                               break;
+
+                       case JSON_INTEGER:
+                               tmp = malloc(40);
+                               sprintf(tmp, "%d", (int)json_integer_value(jelem));
+
+                               cmd->argv[cur] = tmp;
+                               cmd->argv_len[cur] = strlen(tmp);
+                               cur++;
+                               break;
+
+                       default:
+                               break;
+               }
+       }
+
+       json_decref(j);
+       return cmd;
+}
+
diff --git a/formats/json.h b/formats/json.h
new file mode 100644 (file)
index 0000000..911f8f5
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef JSON_H
+#define JSON_H
+
+#include <jansson.h>
+#include <hiredis/hiredis.h>
+#include <hiredis/async.h>
+
+struct cmd;
+struct http_client;
+
+void
+json_reply(redisAsyncContext *c, void *r, void *privdata);
+
+char *
+json_string_output(json_t *j, const char *jsonp);
+
+struct cmd *
+json_ws_extract(struct http_client *c, const char *p, size_t sz);
+
+#endif
diff --git a/formats/msgpack.c b/formats/msgpack.c
new file mode 100644 (file)
index 0000000..dc1ab0c
--- /dev/null
@@ -0,0 +1,236 @@
+#include "msgpack.h"
+#include "common.h"
+#include "cmd.h"
+#include "http.h"
+#include "client.h"
+
+#include <string.h>
+#include <hiredis/hiredis.h>
+#include <hiredis/async.h>
+
+struct msg_out {
+       char *p;
+       size_t sz;
+};
+
+static void
+msgpack_wrap_redis_reply(const struct cmd *cmd, struct msg_out *, const redisReply *r);
+
+void
+msgpack_reply(redisAsyncContext *c, void *r, void *privdata) {
+
+       redisReply *reply = r;
+       struct cmd *cmd = privdata;
+       struct msg_out out;
+       (void)c;
+
+       if(cmd == NULL) {
+               /* broken connection */
+               return;
+       }
+
+       if (reply == NULL) { /* broken Redis link */
+               format_send_error(cmd, 503, "Service Unavailable");
+               return;
+       }
+
+       /* prepare data structure for output */
+       out.p = NULL;
+       out.sz = 0;
+
+       /* encode redis reply */
+       msgpack_wrap_redis_reply(cmd, &out, r);
+
+       /* send reply */
+       format_send_reply(cmd, out.p, out.sz, "application/x-msgpack");
+
+       /* cleanup */
+       free(out.p);
+}
+
+static int
+on_msgpack_write(void *data, const char *s, unsigned int sz) {
+
+       struct msg_out *out = data;
+
+       out->p = realloc(out->p, out->sz + sz);
+       memcpy(out->p + out->sz, s, sz);
+       out->sz += sz;
+
+       return sz;
+}
+
+/**
+ * Parse info message and return object.
+ */
+void
+msg_info_reply(msgpack_packer* pk, const char *s, size_t sz) {
+
+       const char *p = s;
+       unsigned int count = 0;
+
+       /* TODO: handle new format */
+
+       /* count number of lines */
+       while(p < s + sz) {
+               p = strchr(p, '\r');
+               if(!p) break;
+
+               p++;
+               count++;
+       }
+
+       /* create msgpack object */
+       msgpack_pack_map(pk, count);
+
+       p = s;
+       while(p < s + sz) {
+               char *key, *val, *nl, *colon;
+               size_t key_sz, val_sz;
+
+               /* find key */
+               colon = strchr(p, ':');
+               if(!colon) {
+                       break;
+               }
+               key_sz = colon - p;
+               key = calloc(key_sz + 1, 1);
+               memcpy(key, p, key_sz);
+               p = colon + 1;
+
+               /* find value */
+               nl = strchr(p, '\r');
+               if(!nl) {
+                       free(key);
+                       break;
+               }
+               val_sz = nl - p;
+               val = calloc(val_sz + 1, 1);
+               memcpy(val, p, val_sz);
+               p = nl + 1;
+               if(*p == '\n') p++;
+
+               /* add to object */
+               msgpack_pack_raw(pk, key_sz);
+               msgpack_pack_raw_body(pk, key, key_sz);
+               msgpack_pack_raw(pk, val_sz);
+               msgpack_pack_raw_body(pk, val, val_sz);
+
+               free(key);
+               free(val);
+       }
+}
+
+static void
+msg_hgetall_reply(msgpack_packer* pk, const redisReply *r) {
+
+       /* zip keys and values together in a msgpack object */
+
+       unsigned int i;
+
+       if(r->elements % 2 != 0) {
+               return;
+       }
+
+       msgpack_pack_map(pk, r->elements / 2);
+       for(i = 0; i < r->elements; i += 2) {
+               redisReply *k = r->element[i], *v = r->element[i+1];
+
+               /* keys and values need to be strings */
+               if(k->type != REDIS_REPLY_STRING || v->type != REDIS_REPLY_STRING) {
+                       return;
+               }
+
+               /* key */
+               msgpack_pack_raw(pk, k->len);
+               msgpack_pack_raw_body(pk, k->str, k->len);
+
+               /* value */
+               msgpack_pack_raw(pk, v->len);
+               msgpack_pack_raw_body(pk, v->str, v->len);
+       }
+}
+
+static void
+msgpack_wrap_redis_reply(const struct cmd *cmd, struct msg_out *out, const redisReply *r) {
+
+       unsigned int i;
+       msgpack_packer* pk = msgpack_packer_new(out, on_msgpack_write);
+
+       /* copy verb, as jansson only takes a char* but not its length. */
+       char *verb = "";
+       size_t verb_sz = 0;
+       if(cmd->count) {
+               verb_sz = cmd->argv_len[0];
+               verb = cmd->argv[0];
+       }
+
+       /* Create map object */
+       msgpack_pack_map(pk, 1);
+
+       /* The single element is the verb */
+       msgpack_pack_raw(pk, verb_sz);
+       msgpack_pack_raw_body(pk, verb, verb_sz);
+
+       switch(r->type) {
+               case REDIS_REPLY_STATUS:
+               case REDIS_REPLY_ERROR:
+                       msgpack_pack_array(pk, 2);
+
+                       /* first element: book */
+                       if(r->type == REDIS_REPLY_ERROR)
+                               msgpack_pack_false(pk);
+                       else
+                               msgpack_pack_true(pk);
+
+                       /* second element: message */
+                       msgpack_pack_raw(pk, r->len);
+                       msgpack_pack_raw_body(pk, r->str, r->len);
+                       break;
+
+               case REDIS_REPLY_STRING:
+                       if(verb_sz ==4 && strncasecmp(verb, "INFO", 4) == 0) {
+                               msg_info_reply(pk, r->str, r->len);
+                       } else {
+                               msgpack_pack_raw(pk, r->len);
+                               msgpack_pack_raw_body(pk, r->str, r->len);
+                       }
+                       break;
+
+               case REDIS_REPLY_INTEGER:
+                       msgpack_pack_int(pk, r->integer);
+                       break;
+
+               case REDIS_REPLY_ARRAY:
+                       if(verb_sz == 7 && strncasecmp(verb, "HGETALL", 7) == 0) {
+                               msg_hgetall_reply(pk, r);
+                               break;
+                       }
+
+                       msgpack_pack_array(pk, r->elements);
+
+                       for(i = 0; i < r->elements; ++i) {
+                               redisReply *e = r->element[i];
+                               switch(e->type) {
+                                       case REDIS_REPLY_STRING:
+                                               msgpack_pack_raw(pk, e->len);
+                                               msgpack_pack_raw_body(pk, e->str, e->len);
+                                               break;
+                                       case REDIS_REPLY_INTEGER:
+                                               msgpack_pack_int(pk, e->integer);
+                                               break;
+                                       default:
+                                               msgpack_pack_nil(pk);
+                                               break;
+                               }
+                       }
+
+                       break;
+
+               default:
+                       msgpack_pack_nil(pk);
+                       break;
+       }
+
+       msgpack_packer_free(pk);
+}
diff --git a/formats/msgpack.h b/formats/msgpack.h
new file mode 100644 (file)
index 0000000..796caec
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef MSGPACK_H
+#define MSGPACK_H
+
+#include <msgpack.h>
+#include <hiredis/hiredis.h>
+#include <hiredis/async.h>
+
+void
+msgpack_reply(redisAsyncContext *c, void *r, void *privdata);
+
+#endif
diff --git a/formats/raw.c b/formats/raw.c
new file mode 100644 (file)
index 0000000..fdc9051
--- /dev/null
@@ -0,0 +1,192 @@
+#include "raw.h"
+#include "common.h"
+#include "http.h"
+#include "client.h"
+#include "cmd.h"
+
+#include <string.h>
+#include <hiredis/hiredis.h>
+#include <hiredis/async.h>
+
+static char *
+raw_wrap(const redisReply *r, size_t *sz);
+
+void
+raw_reply(redisAsyncContext *c, void *r, void *privdata) {
+
+       redisReply *reply = r;
+       struct cmd *cmd = privdata;
+       char *raw_out;
+       size_t sz;
+       (void)c;
+
+       if (reply == NULL) { /* broken Redis link */
+               format_send_error(cmd, 503, "Service Unavailable");
+               return;
+       }
+
+       raw_out = raw_wrap(r, &sz);
+
+       /* send reply */
+       format_send_reply(cmd, raw_out, sz, "binary/octet-stream");
+
+       /* cleanup */
+       free(raw_out);
+}
+
+/* extract Redis protocol string from WebSocket frame and fill struct cmd. */
+struct cmd *
+raw_ws_extract(struct http_client *c, const char *p, size_t sz) {
+
+       struct cmd *cmd = NULL;
+       void *reader = NULL;
+       redisReply *reply = NULL;
+       void **reply_ptr = (void**)&reply;
+       unsigned int i;
+       (void)c;
+
+       /* create protocol reader */
+       reader = redisReaderCreate();
+
+       /* add data */
+       redisReaderFeed(reader, (char*)p, sz);
+
+       /* parse data into reply object */
+       if(redisReaderGetReply(reader, reply_ptr) == REDIS_ERR) {
+               goto end;
+       }
+
+       /* add data from reply object to cmd struct */
+       if(reply->type != REDIS_REPLY_ARRAY) {
+               goto end;
+       }
+
+       /* create cmd object */
+       cmd = cmd_new(reply->elements);
+
+       for(i = 0; i < reply->elements; ++i) {
+               redisReply *ri = reply->element[i];
+
+               switch(ri->type) {
+                       case REDIS_REPLY_STRING:
+                               cmd->argv_len[i] = ri->len;
+                               cmd->argv[i] = calloc(cmd->argv_len[i] + 1, 1);
+                               memcpy(cmd->argv[i], ri->str, ri->len);
+                               break;
+
+                       case REDIS_REPLY_INTEGER:
+                               cmd->argv_len[i] = integer_length(ri->integer);
+                               cmd->argv[i] = calloc(cmd->argv_len[i] + 1, 1);
+                               sprintf(cmd->argv[i], "%lld", ri->integer);
+                               break;
+
+                       default:
+                               cmd_free(cmd);
+                               cmd = NULL;
+                               goto end;
+               }
+       }
+
+
+end:
+       /* free reader */
+       if(reader) redisReaderFree(reader);
+
+       /* free reply */
+       if(reply) freeReplyObject(reply);
+
+       return cmd;
+}
+
+
+static char *
+raw_array(const redisReply *r, size_t *sz) {
+       
+       unsigned int i;
+       char *ret, *p;
+
+       /* compute size */
+       *sz = 0;
+       *sz += 1 + integer_length(r->elements) + 2;
+       for(i = 0; i < r->elements; ++i) {
+               redisReply *e = r->element[i];
+               switch(e->type) {
+                       case REDIS_REPLY_STRING:
+                               *sz += 1 + integer_length(e->len) + 2
+                                       + e->len + 2;
+                               break;
+                       case REDIS_REPLY_INTEGER:
+                               *sz += 1 + integer_length(integer_length(e->integer)) + 2
+                                       + integer_length(e->integer) + 2;
+                               break;
+
+               }
+       }
+
+       /* allocate */
+       p = ret = malloc(1+*sz);
+       p += sprintf(p, "*%zd\r\n", r->elements);
+
+       /* copy */
+       for(i = 0; i < r->elements; ++i) {
+               redisReply *e = r->element[i];
+               switch(e->type) {
+                       case REDIS_REPLY_STRING:
+                               p += sprintf(p, "$%d\r\n", e->len);
+                               memcpy(p, e->str, e->len);
+                               p += e->len;
+                               *p = '\r';
+                               p++;
+                               *p = '\n';
+                               p++;
+                               break;
+                       case REDIS_REPLY_INTEGER:
+                               p += sprintf(p, "$%d\r\n%lld\r\n",
+                                       integer_length(e->integer), e->integer);
+                               break;
+               }
+       }
+
+       return ret;
+}
+
+static char *
+raw_wrap(const redisReply *r, size_t *sz) {
+
+       char *ret, *p;
+
+       switch(r->type) {
+               case REDIS_REPLY_STATUS:
+               case REDIS_REPLY_ERROR:
+                       *sz = 3 + r->len;
+                       ret = malloc(*sz);
+                       ret[0] = (r->type == REDIS_REPLY_STATUS?'+':'-');
+                       memcpy(ret+1, r->str, *sz-3);
+                       memcpy(ret+*sz - 2, "\r\n", 2);
+                       return ret;
+
+               case REDIS_REPLY_STRING:
+                       *sz = 1 + integer_length(r->len) + 2 + r->len + 2;
+                       p = ret = malloc(*sz);
+                       p += sprintf(p, "$%d\r\n", r->len);
+                       memcpy(p, r->str, *sz - 2 - (p-ret));
+                       memcpy(ret + *sz - 2, "\r\n", 2);
+                       return ret;
+
+               case REDIS_REPLY_INTEGER:
+                       *sz = 3 + integer_length(r->integer);
+                       ret = malloc(4+*sz);
+                       sprintf(ret, ":%lld\r\n", r->integer);
+                       return ret;
+
+               case REDIS_REPLY_ARRAY:
+                       return raw_array(r, sz);
+
+               default:
+                       *sz = 5;
+                       ret = malloc(*sz);
+                       memcpy(ret, "$-1\r\n", 5);
+                       return ret;
+       }
+}
+
diff --git a/formats/raw.h b/formats/raw.h
new file mode 100644 (file)
index 0000000..10321e3
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef RAW_H
+#define RAW_H
+
+#include <hiredis/hiredis.h>
+#include <hiredis/async.h>
+
+struct cmd;
+struct http_client;
+
+void
+raw_reply(redisAsyncContext *c, void *r, void *privdata);
+
+struct cmd *
+raw_ws_extract(struct http_client *c, const char *p, size_t sz);
+
+#endif
diff --git a/hiredis/.gitignore b/hiredis/.gitignore
new file mode 100644 (file)
index 0000000..1a4d60d
--- /dev/null
@@ -0,0 +1,6 @@
+/hiredis-test
+/hiredis-example*
+/*.o
+/*.so
+/*.dylib
+/*.a
diff --git a/hiredis/COPYING b/hiredis/COPYING
new file mode 100644 (file)
index 0000000..a5fc973
--- /dev/null
@@ -0,0 +1,29 @@
+Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice,
+  this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of Redis nor the names of its contributors may be used
+  to endorse or promote products derived from this software without specific
+  prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/hiredis/Makefile b/hiredis/Makefile
new file mode 100644 (file)
index 0000000..57f057e
--- /dev/null
@@ -0,0 +1,148 @@
+# Hiredis Makefile
+# Copyright (C) 2010-2011 Salvatore Sanfilippo <antirez at gmail dot com>
+# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
+# This file is released under the BSD license, see the COPYING file
+
+OBJ=net.o hiredis.o sds.o async.o
+BINS=hiredis-example hiredis-test
+LIBNAME=libhiredis
+
+HIREDIS_MAJOR=0
+HIREDIS_MINOR=10
+
+# Fallback to gcc when $CC is not in $PATH.
+CC:=$(shell sh -c 'type $(CC) >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
+OPTIMIZATION?=-O3
+WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings
+DEBUG?= -g -ggdb
+REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG)
+REAL_LDFLAGS=$(LDFLAGS)
+
+DYLIBSUFFIX=so
+STLIBSUFFIX=a
+DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR).$(HIREDIS_MINOR)
+DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
+DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
+DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
+STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
+STLIB_MAKE_CMD=ar rcs $(STLIBNAME)
+
+# Platform-specific overrides
+uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
+ifeq ($(uname_S),SunOS)
+  REAL_LDFLAGS+= -ldl -lnsl -lsocket
+  DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
+  INSTALL= cp -r
+endif
+ifeq ($(uname_S),Darwin)
+  DYLIBSUFFIX=dylib
+  DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(DYLIBSUFFIX)
+  DYLIB_MAJOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(DYLIBSUFFIX)
+  DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
+endif
+
+all: $(DYLIBNAME) $(BINS)
+
+# Deps (use make dep to generate this)
+net.o: net.c fmacros.h net.h hiredis.h
+async.o: async.c async.h hiredis.h sds.h dict.c dict.h
+example.o: example.c hiredis.h
+hiredis.o: hiredis.c fmacros.h hiredis.h net.h sds.h
+sds.o: sds.c sds.h
+test.o: test.c hiredis.h
+
+$(DYLIBNAME): $(OBJ)
+       $(DYLIB_MAKE_CMD) $(OBJ)
+
+$(STLIBNAME): $(OBJ)
+       $(STLIB_MAKE_CMD) $(OBJ)
+
+dynamic: $(DYLIBNAME)
+static: $(STLIBNAME)
+
+# Binaries:
+hiredis-example-libevent: example-libevent.c adapters/libevent.h $(STLIBNAME)
+       $(CC) -o $@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -levent example-libevent.c $(STLIBNAME)
+
+hiredis-example-libev: example-libev.c adapters/libev.h $(STLIBNAME)
+       $(CC) -o $@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -lev example-libev.c $(STLIBNAME)
+
+ifndef AE_DIR
+hiredis-example-ae:
+       @echo "Please specify AE_DIR (e.g. <redis repository>/src)"
+       @false
+else
+hiredis-example-ae: example-ae.c adapters/ae.h $(STLIBNAME)
+       $(CC) -o $@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I$(AE_DIR) $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o example-ae.c $(STLIBNAME)
+endif
+
+hiredis-%: %.o $(STLIBNAME)
+       $(CC) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME)
+
+test: hiredis-test
+       ./hiredis-test
+
+check: hiredis-test
+       echo \
+               "daemonize yes\n" \
+               "pidfile /tmp/hiredis-test-redis.pid\n" \
+               "port 56379\n" \
+               "bind 127.0.0.1\n" \
+               "unixsocket /tmp/hiredis-test-redis.sock" \
+                       | redis-server -
+       ./hiredis-test -h 127.0.0.1 -p 56379 -s /tmp/hiredis-test-redis.sock || \
+                       ( kill `cat /tmp/hiredis-test-redis.pid` && false )
+       kill `cat /tmp/hiredis-test-redis.pid`
+
+.c.o:
+       $(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $<
+
+clean:
+       rm -rf $(DYLIBNAME) $(STLIBNAME) $(BINS) hiredis-example* *.o *.gcda *.gcno *.gcov
+
+dep:
+       $(CC) -MM *.c
+
+# Installation related variables and target
+PREFIX?=/usr/local
+INCLUDE_PATH?=include/hiredis
+LIBRARY_PATH?=lib
+INSTALL_INCLUDE_PATH= $(PREFIX)/$(INCLUDE_PATH)
+INSTALL_LIBRARY_PATH= $(PREFIX)/$(LIBRARY_PATH)
+
+ifeq ($(uname_S),SunOS)
+  INSTALL?= cp -r
+endif
+
+INSTALL?= cp -a
+
+install: $(DYLIBNAME) $(STLIBNAME)
+       mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH)
+       $(INSTALL) hiredis.h async.h adapters $(INSTALL_INCLUDE_PATH)
+       $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
+       cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIB_MAJOR_NAME)
+       cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MAJOR_NAME) $(DYLIBNAME)
+       $(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH)
+
+32bit:
+       @echo ""
+       @echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386"
+       @echo ""
+       $(MAKE) CFLAGS="-m32" LDFLAGS="-m32"
+
+gprof:
+       $(MAKE) CFLAGS="-pg" LDFLAGS="-pg"
+
+gcov:
+       $(MAKE) CFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs"
+
+coverage: gcov
+       make check
+       mkdir -p tmp/lcov
+       lcov -d . -c -o tmp/lcov/hiredis.info
+       genhtml --legend -o tmp/lcov/report tmp/lcov/hiredis.info
+
+noopt:
+       $(MAKE) OPTIMIZATION=""
+
+.PHONY: all test check clean dep install 32bit gprof gcov noopt
diff --git a/hiredis/README.md b/hiredis/README.md
new file mode 100644 (file)
index 0000000..a58101c
--- /dev/null
@@ -0,0 +1,352 @@
+# HIREDIS
+
+Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database.
+
+It is minimalistic because it just adds minimal support for the protocol, but
+at the same time it uses an high level printf-alike API in order to make it
+much higher level than otherwise suggested by its minimal code base and the
+lack of explicit bindings for every Redis command.
+
+Apart from supporting sending commands and receiving replies, it comes with
+a reply parser that is decoupled from the I/O layer. It
+is a stream parser designed for easy reusability, which can for instance be used
+in higher level language bindings for efficient reply parsing.
+
+Hiredis only supports the binary-safe Redis protocol, so you can use it with any
+Redis version >= 1.2.0.
+
+The library comes with multiple APIs. There is the
+*synchronous API*, the *asynchronous API* and the *reply parsing API*.
+
+## UPGRADING
+
+Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing
+code using hiredis should not be a big pain. The key thing to keep in mind when
+upgrading is that hiredis >= 0.9.0 uses a `redisContext*` to keep state, in contrast to
+the stateless 0.0.1 that only has a file descriptor to work with.
+
+## Synchronous API
+
+To consume the synchronous API, there are only a few function calls that need to be introduced:
+
+    redisContext *redisConnect(const char *ip, int port);
+    void *redisCommand(redisContext *c, const char *format, ...);
+    void freeReplyObject(void *reply);
+
+### Connecting
+
+The function `redisConnect` is used to create a so-called `redisContext`. The
+context is where Hiredis holds state for a connection. The `redisContext`
+struct has an integer `err` field that is non-zero when an the connection is in
+an error state. The field `errstr` will contain a string with a description of
+the error. More information on errors can be found in the **Errors** section.
+After trying to connect to Redis using `redisConnect` you should
+check the `err` field to see if establishing the connection was successful:
+
+    redisContext *c = redisConnect("127.0.0.1", 6379);
+    if (c->err) {
+        printf("Error: %s\n", c->errstr);
+        // handle error
+    }
+
+### Sending commands
+
+There are several ways to issue commands to Redis. The first that will be introduced is
+`redisCommand`. This function takes a format similar to printf. In the simplest form,
+it is used like this:
+
+    reply = redisCommand(context, "SET foo bar");
+
+The specifier `%s` interpolates a string in the command, and uses `strlen` to
+determine the length of the string:
+
+    reply = redisCommand(context, "SET foo %s", value);
+
+When you need to pass binary safe strings in a command, the `%b` specifier can be
+used. Together with a pointer to the string, it requires a `size_t` length argument
+of the string:
+
+    reply = redisCommand(context, "SET foo %b", value, valuelen);
+
+Internally, Hiredis splits the command in different arguments and will
+convert it to the protocol used to communicate with Redis.
+One or more spaces separates arguments, so you can use the specifiers
+anywhere in an argument:
+
+    reply = redisCommand("SET key:%s %s", myid, value);
+
+### Using replies
+
+The return value of `redisCommand` holds a reply when the command was
+successfully executed. When an error occurs, the return value is `NULL` and
+the `err` field in the context will be set (see section on **Errors**).
+Once an error is returned the context cannot be reused and you should set up
+a new connection.
+
+The standard replies that `redisCommand` are of the type `redisReply`. The
+`type` field in the `redisReply` should be used to test what kind of reply
+was received:
+
+* **`REDIS_REPLY_STATUS`**:
+    * The command replied with a status reply. The status string can be accessed using `reply->str`.
+      The length of this string can be accessed using `reply->len`.
+
+* **`REDIS_REPLY_ERROR`**:
+    *  The command replied with an error. The error string can be accessed identical to `REDIS_REPLY_STATUS`.
+
+* **`REDIS_REPLY_INTEGER`**:
+    * The command replied with an integer. The integer value can be accessed using the
+      `reply->integer` field of type `long long`.
+
+* **`REDIS_REPLY_NIL`**:
+    * The command replied with a **nil** object. There is no data to access.
+
+* **`REDIS_REPLY_STRING`**:
+    * A bulk (string) reply. The value of the reply can be accessed using `reply->str`.
+      The length of this string can be accessed using `reply->len`.
+
+* **`REDIS_REPLY_ARRAY`**:
+    * A multi bulk reply. The number of elements in the multi bulk reply is stored in
+      `reply->elements`. Every element in the multi bulk reply is a `redisReply` object as well
+      and can be accessed via `reply->element[..index..]`.
+      Redis may reply with nested arrays but this is fully supported.
+
+Replies should be freed using the `freeReplyObject()` function.
+Note that this function will take care of freeing sub-replies objects
+contained in arrays and nested arrays, so there is no need for the user to
+free the sub replies (it is actually harmful and will corrupt the memory).
+
+**Important:** the current version of hiredis (0.10.0) free's replies when the
+asynchronous API is used. This means you should not call `freeReplyObject` when
+you use this API. The reply is cleaned up by hiredis _after_ the callback
+returns. This behavior will probably change in future releases, so make sure to
+keep an eye on the changelog when upgrading (see issue #39).
+
+### Cleaning up
+
+To disconnect and free the context the following function can be used:
+
+    void redisFree(redisContext *c);
+
+This function immediately closes the socket and then free's the allocations done in
+creating the context.
+
+### Sending commands (cont'd)
+
+Together with `redisCommand`, the function `redisCommandArgv` can be used to issue commands.
+It has the following prototype:
+
+    void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
+
+It takes the number of arguments `argc`, an array of strings `argv` and the lengths of the
+arguments `argvlen`. For convenience, `argvlen` may be set to `NULL` and the function will
+use `strlen(3)` on every argument to determine its length. Obviously, when any of the arguments
+need to be binary safe, the entire array of lengths `argvlen` should be provided.
+
+The return value has the same semantic as `redisCommand`.
+
+### Pipelining
+
+To explain how Hiredis supports pipelining in a blocking connection, there needs to be
+understanding of the internal execution flow.
+
+When any of the functions in the `redisCommand` family is called, Hiredis first formats the
+command according to the Redis protocol. The formatted command is then put in the output buffer
+of the context. This output buffer is dynamic, so it can hold any number of commands.
+After the command is put in the output buffer, `redisGetReply` is called. This function has the
+following two execution paths:
+
+1. The input buffer is non-empty:
+    * Try to parse a single reply from the input buffer and return it
+    * If no reply could be parsed, continue at *2*
+2. The input buffer is empty:
+    * Write the **entire** output buffer to the socket
+    * Read from the socket until a single reply could be parsed
+
+The function `redisGetReply` is exported as part of the Hiredis API and can be used when a reply
+is expected on the socket. To pipeline commands, the only things that needs to be done is
+filling up the output buffer. For this cause, two commands can be used that are identical
+to the `redisCommand` family, apart from not returning a reply:
+
+    void redisAppendCommand(redisContext *c, const char *format, ...);
+    void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
+
+After calling either function one or more times, `redisGetReply` can be used to receive the
+subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where
+the latter means an error occurred while reading a reply. Just as with the other commands,
+the `err` field in the context can be used to find out what the cause of this error is.
+
+The following examples shows a simple pipeline (resulting in only a single call to `write(2)` and
+a single call to `read(2)`):
+
+    redisReply *reply;
+    redisAppendCommand(context,"SET foo bar");
+    redisAppendCommand(context,"GET foo");
+    redisGetReply(context,&reply); // reply for SET
+    freeReplyObject(reply);
+    redisGetReply(context,&reply); // reply for GET
+    freeReplyObject(reply);
+
+This API can also be used to implement a blocking subscriber:
+
+    reply = redisCommand(context,"SUBSCRIBE foo");
+    freeReplyObject(reply);
+    while(redisGetReply(context,&reply) == REDIS_OK) {
+        // consume message
+        freeReplyObject(reply);
+    }
+
+### Errors
+
+When a function call is not successful, depending on the function either `NULL` or `REDIS_ERR` is
+returned. The `err` field inside the context will be non-zero and set to one of the
+following constants:
+
+* **`REDIS_ERR_IO`**:
+    There was an I/O error while creating the connection, trying to write
+    to the socket or read from the socket. If you included `errno.h` in your
+    application, you can use the global `errno` variable to find out what is
+    wrong.
+
+* **`REDIS_ERR_EOF`**:
+    The server closed the connection which resulted in an empty read.
+
+* **`REDIS_ERR_PROTOCOL`**:
+    There was an error while parsing the protocol.
+
+* **`REDIS_ERR_OTHER`**:
+    Any other error. Currently, it is only used when a specified hostname to connect
+    to cannot be resolved.
+
+In every case, the `errstr` field in the context will be set to hold a string representation
+of the error.
+
+## Asynchronous API
+
+Hiredis comes with an asynchronous API that works easily with any event library.
+Examples are bundled that show using Hiredis with [libev](http://software.schmorp.de/pkg/libev.html)
+and [libevent](http://monkey.org/~provos/libevent/).
+
+### Connecting
+
+The function `redisAsyncConnect` can be used to establish a non-blocking connection to
+Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field
+should be checked after creation to see if there were errors creating the connection.
+Because the connection that will be created is non-blocking, the kernel is not able to
+instantly return if the specified host and port is able to accept a connection.
+
+    redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+    if (c->err) {
+        printf("Error: %s\n", c->errstr);
+        // handle error
+    }
+
+The asynchronous context can hold a disconnect callback function that is called when the
+connection is disconnected (either because of an error or per user request). This function should
+have the following prototype:
+
+    void(const redisAsyncContext *c, int status);
+
+On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the
+user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err`
+field in the context can be accessed to find out the cause of the error.
+
+The context object is always free'd after the disconnect callback fired. When a reconnect is needed,
+the disconnect callback is a good point to do so.
+
+Setting the disconnect callback can only be done once per context. For subsequent calls it will
+return `REDIS_ERR`. The function to set the disconnect callback has the following prototype:
+
+    int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
+
+### Sending commands and their callbacks
+
+In an asynchronous context, commands are automatically pipelined due to the nature of an event loop.
+Therefore, unlike the synchronous API, there is only a single way to send commands.
+Because commands are sent to Redis asynchronously, issuing a command requires a callback function
+that is called when the reply is received. Reply callbacks should have the following prototype:
+
+    void(redisAsyncContext *c, void *reply, void *privdata);
+
+The `privdata` argument can be used to curry arbitrary data to the callback from the point where
+the command is initially queued for execution.
+
+The functions that can be used to issue commands in an asynchronous context are:
+
+    int redisAsyncCommand(
+      redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,
+      const char *format, ...);
+    int redisAsyncCommandArgv(
+      redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,
+      int argc, const char **argv, const size_t *argvlen);
+
+Both functions work like their blocking counterparts. The return value is `REDIS_OK` when the command
+was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection
+is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is
+returned on calls to the `redisAsyncCommand` family.
+
+If the reply for a command with a `NULL` callback is read, it is immediately free'd. When the callback
+for a command is non-`NULL`, the memory is free'd immediately following the callback: the reply is only
+valid for the duration of the callback.
+
+All pending callbacks are called with a `NULL` reply when the context encountered an error.
+
+### Disconnecting
+
+An asynchronous connection can be terminated using:
+
+    void redisAsyncDisconnect(redisAsyncContext *ac);
+
+When this function is called, the connection is **not** immediately terminated. Instead, new
+commands are no longer accepted and the connection is only terminated when all pending commands
+have been written to the socket, their respective replies have been read and their respective
+callbacks have been executed. After this, the disconnection callback is executed with the
+`REDIS_OK` status and the context object is free'd.
+
+### Hooking it up to event library *X*
+
+There are a few hooks that need to be set on the context object after it is created.
+See the `adapters/` directory for bindings to *libev* and *libevent*.
+
+## Reply parsing API
+
+Hiredis comes with a reply parsing API that makes it easy for writing higher
+level language bindings.
+
+The reply parsing API consists of the following functions:
+
+    redisReader *redisReaderCreate(void);
+    void redisReaderFree(redisReader *reader);
+    int redisReaderFeed(redisReader *reader, const char *buf, size_t len);
+    int redisReaderGetReply(redisReader *reader, void **reply);
+
+### Usage
+
+The function `redisReaderCreate` creates a `redisReader` structure that holds a
+buffer with unparsed data and state for the protocol parser.
+
+Incoming data -- most likely from a socket -- can be placed in the internal
+buffer of the `redisReader` using `redisReaderFeed`. This function will make a
+copy of the buffer pointed to by `buf` for `len` bytes. This data is parsed
+when `redisReaderGetReply` is called. This function returns an integer status
+and a reply object (as described above) via `void **reply`. The returned status
+can be either `REDIS_OK` or `REDIS_ERR`, where the latter means something went
+wrong (either a protocol error, or an out of memory error).
+
+### Customizing replies
+
+The function `redisReaderGetReply` creates `redisReply` and makes the function
+argument `reply` point to the created `redisReply` variable. For instance, if
+the response of type `REDIS_REPLY_STATUS` then the `str` field of `redisReply`
+will hold the status as a vanilla C string. However, the functions that are
+responsible for creating instances of the `redisReply` can be customized by
+setting the `fn` field on the `redisReader` struct. This should be done
+immediately after creating the `redisReader`.
+
+For example, [hiredis-rb](https://github.com/pietern/hiredis-rb/blob/master/ext/hiredis_ext/reader.c)
+uses customized reply object functions to create Ruby objects.
+
+## AUTHORS
+
+Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and
+Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license.
diff --git a/hiredis/TODO b/hiredis/TODO
new file mode 100644 (file)
index 0000000..de70b94
--- /dev/null
@@ -0,0 +1,2 @@
+- add redisCommandVector()
+- add support for pipelining
diff --git a/hiredis/adapters/ae.h b/hiredis/adapters/ae.h
new file mode 100644 (file)
index 0000000..65235f8
--- /dev/null
@@ -0,0 +1,97 @@
+#ifndef __HIREDIS_AE_H__
+#define __HIREDIS_AE_H__
+#include <sys/types.h>
+#include <ae.h>
+#include "../hiredis.h"
+#include "../async.h"
+
+typedef struct redisAeEvents {
+    redisAsyncContext *context;
+    aeEventLoop *loop;
+    int fd;
+    int reading, writing;
+} redisAeEvents;
+
+static void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) {
+    ((void)el); ((void)fd); ((void)mask);
+
+    redisAeEvents *e = (redisAeEvents*)privdata;
+    redisAsyncHandleRead(e->context);
+}
+
+static void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) {
+    ((void)el); ((void)fd); ((void)mask);
+
+    redisAeEvents *e = (redisAeEvents*)privdata;
+    redisAsyncHandleWrite(e->context);
+}
+
+static void redisAeAddRead(void *privdata) {
+    redisAeEvents *e = (redisAeEvents*)privdata;
+    aeEventLoop *loop = e->loop;
+    if (!e->reading) {
+        e->reading = 1;
+        aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e);
+    }
+}
+
+static void redisAeDelRead(void *privdata) {
+    redisAeEvents *e = (redisAeEvents*)privdata;
+    aeEventLoop *loop = e->loop;
+    if (e->reading) {
+        e->reading = 0;
+        aeDeleteFileEvent(loop,e->fd,AE_READABLE);
+    }
+}
+
+static void redisAeAddWrite(void *privdata) {
+    redisAeEvents *e = (redisAeEvents*)privdata;
+    aeEventLoop *loop = e->loop;
+    if (!e->writing) {
+        e->writing = 1;
+        aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e);
+    }
+}
+
+static void redisAeDelWrite(void *privdata) {
+    redisAeEvents *e = (redisAeEvents*)privdata;
+    aeEventLoop *loop = e->loop;
+    if (e->writing) {
+        e->writing = 0;
+        aeDeleteFileEvent(loop,e->fd,AE_WRITABLE);
+    }
+}
+
+static void redisAeCleanup(void *privdata) {
+    redisAeEvents *e = (redisAeEvents*)privdata;
+    redisAeDelRead(privdata);
+    redisAeDelWrite(privdata);
+    free(e);
+}
+
+static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) {
+    redisContext *c = &(ac->c);
+    redisAeEvents *e;
+
+    /* Nothing should be attached when something is already attached */
+    if (ac->ev.data != NULL)
+        return REDIS_ERR;
+
+    /* Create container for context and r/w events */
+    e = (redisAeEvents*)malloc(sizeof(*e));
+    e->context = ac;
+    e->loop = loop;
+    e->fd = c->fd;
+    e->reading = e->writing = 0;
+
+    /* Register functions to start/stop listening for events */
+    ac->ev.addRead = redisAeAddRead;
+    ac->ev.delRead = redisAeDelRead;
+    ac->ev.addWrite = redisAeAddWrite;
+    ac->ev.delWrite = redisAeDelWrite;
+    ac->ev.cleanup = redisAeCleanup;
+    ac->ev.data = e;
+
+    return REDIS_OK;
+}
+#endif
diff --git a/hiredis/adapters/libev.h b/hiredis/adapters/libev.h
new file mode 100644 (file)
index 0000000..534d743
--- /dev/null
@@ -0,0 +1,117 @@
+#ifndef __HIREDIS_LIBEV_H__
+#define __HIREDIS_LIBEV_H__
+#include <stdlib.h>
+#include <sys/types.h>
+#include <ev.h>
+#include "../hiredis.h"
+#include "../async.h"
+
+typedef struct redisLibevEvents {
+    redisAsyncContext *context;
+    struct ev_loop *loop;
+    int reading, writing;
+    ev_io rev, wev;
+} redisLibevEvents;
+
+static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) {
+#if EV_MULTIPLICITY
+    ((void)loop);
+#endif
+    ((void)revents);
+
+    redisLibevEvents *e = (redisLibevEvents*)watcher->data;
+    redisAsyncHandleRead(e->context);
+}
+
+static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) {
+#if EV_MULTIPLICITY
+    ((void)loop);
+#endif
+    ((void)revents);
+
+    redisLibevEvents *e = (redisLibevEvents*)watcher->data;
+    redisAsyncHandleWrite(e->context);
+}
+
+static void redisLibevAddRead(void *privdata) {
+    redisLibevEvents *e = (redisLibevEvents*)privdata;
+    struct ev_loop *loop = e->loop;
+    ((void)loop);
+    if (!e->reading) {
+        e->reading = 1;
+        ev_io_start(EV_A_ &e->rev);
+    }
+}
+
+static void redisLibevDelRead(void *privdata) {
+    redisLibevEvents *e = (redisLibevEvents*)privdata;
+    struct ev_loop *loop = e->loop;
+    ((void)loop);
+    if (e->reading) {
+        e->reading = 0;
+        ev_io_stop(EV_A_ &e->rev);
+    }
+}
+
+static void redisLibevAddWrite(void *privdata) {
+    redisLibevEvents *e = (redisLibevEvents*)privdata;
+    struct ev_loop *loop = e->loop;
+    ((void)loop);
+    if (!e->writing) {
+        e->writing = 1;
+        ev_io_start(EV_A_ &e->wev);
+    }
+}
+
+static void redisLibevDelWrite(void *privdata) {
+    redisLibevEvents *e = (redisLibevEvents*)privdata;
+    struct ev_loop *loop = e->loop;
+    ((void)loop);
+    if (e->writing) {
+        e->writing = 0;
+        ev_io_stop(EV_A_ &e->wev);
+    }
+}
+
+static void redisLibevCleanup(void *privdata) {
+    redisLibevEvents *e = (redisLibevEvents*)privdata;
+    redisLibevDelRead(privdata);
+    redisLibevDelWrite(privdata);
+    free(e);
+}
+
+static int redisLibevAttach(EV_P_ redisAsyncContext *ac) {
+    redisContext *c = &(ac->c);
+    redisLibevEvents *e;
+
+    /* Nothing should be attached when something is already attached */
+    if (ac->ev.data != NULL)
+        return REDIS_ERR;
+
+    /* Create container for context and r/w events */
+    e = (redisLibevEvents*)malloc(sizeof(*e));
+    e->context = ac;
+#if EV_MULTIPLICITY
+    e->loop = loop;
+#else
+    e->loop = NULL;
+#endif
+    e->reading = e->writing = 0;
+    e->rev.data = e;
+    e->wev.data = e;
+
+    /* Register functions to start/stop listening for events */
+    ac->ev.addRead = redisLibevAddRead;
+    ac->ev.delRead = redisLibevDelRead;
+    ac->ev.addWrite = redisLibevAddWrite;
+    ac->ev.delWrite = redisLibevDelWrite;
+    ac->ev.cleanup = redisLibevCleanup;
+    ac->ev.data = e;
+
+    /* Initialize read/write events */
+    ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ);
+    ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE);
+    return REDIS_OK;
+}
+
+#endif
diff --git a/hiredis/adapters/libevent.h b/hiredis/adapters/libevent.h
new file mode 100644 (file)
index 0000000..4055ec0
--- /dev/null
@@ -0,0 +1,78 @@
+#ifndef __HIREDIS_LIBEVENT_H__
+#define __HIREDIS_LIBEVENT_H__
+#include <event.h>
+#include "../hiredis.h"
+#include "../async.h"
+
+typedef struct redisLibeventEvents {
+    redisAsyncContext *context;
+    struct event rev, wev;
+} redisLibeventEvents;
+
+static void redisLibeventReadEvent(int fd, short event, void *arg) {
+    ((void)fd); ((void)event);
+    redisLibeventEvents *e = (redisLibeventEvents*)arg;
+    redisAsyncHandleRead(e->context);
+}
+
+static void redisLibeventWriteEvent(int fd, short event, void *arg) {
+    ((void)fd); ((void)event);
+    redisLibeventEvents *e = (redisLibeventEvents*)arg;
+    redisAsyncHandleWrite(e->context);
+}
+
+static void redisLibeventAddRead(void *privdata) {
+    redisLibeventEvents *e = (redisLibeventEvents*)privdata;
+    event_add(&e->rev,NULL);
+}
+
+static void redisLibeventDelRead(void *privdata) {
+    redisLibeventEvents *e = (redisLibeventEvents*)privdata;
+    event_del(&e->rev);
+}
+
+static void redisLibeventAddWrite(void *privdata) {
+    redisLibeventEvents *e = (redisLibeventEvents*)privdata;
+    event_add(&e->wev,NULL);
+}
+
+static void redisLibeventDelWrite(void *privdata) {
+    redisLibeventEvents *e = (redisLibeventEvents*)privdata;
+    event_del(&e->wev);
+}
+
+static void redisLibeventCleanup(void *privdata) {
+    redisLibeventEvents *e = (redisLibeventEvents*)privdata;
+    event_del(&e->rev);
+    event_del(&e->wev);
+    free(e);
+}
+
+static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
+    redisContext *c = &(ac->c);
+    redisLibeventEvents *e;
+
+    /* Nothing should be attached when something is already attached */
+    if (ac->ev.data != NULL)
+        return REDIS_ERR;
+
+    /* Create container for context and r/w events */
+    e = (redisLibeventEvents*)malloc(sizeof(*e));
+    e->context = ac;
+
+    /* Register functions to start/stop listening for events */
+    ac->ev.addRead = redisLibeventAddRead;
+    ac->ev.delRead = redisLibeventDelRead;
+    ac->ev.addWrite = redisLibeventAddWrite;
+    ac->ev.delWrite = redisLibeventDelWrite;
+    ac->ev.cleanup = redisLibeventCleanup;
+    ac->ev.data = e;
+
+    /* Initialize and install read/write events */
+    event_set(&e->rev,c->fd,EV_READ,redisLibeventReadEvent,e);
+    event_set(&e->wev,c->fd,EV_WRITE,redisLibeventWriteEvent,e);
+    event_base_set(base,&e->rev);
+    event_base_set(base,&e->wev);
+    return REDIS_OK;
+}
+#endif
diff --git a/hiredis/async.c b/hiredis/async.c
new file mode 100644 (file)
index 0000000..83d88ec
--- /dev/null
@@ -0,0 +1,647 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "fmacros.h"
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include "async.h"
+#include "net.h"
+#include "dict.c"
+#include "sds.h"
+
+#define _EL_ADD_READ(ctx) do { \
+        if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
+    } while(0)
+#define _EL_DEL_READ(ctx) do { \
+        if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
+    } while(0)
+#define _EL_ADD_WRITE(ctx) do { \
+        if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
+    } while(0)
+#define _EL_DEL_WRITE(ctx) do { \
+        if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
+    } while(0)
+#define _EL_CLEANUP(ctx) do { \
+        if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
+    } while(0);
+
+/* Forward declaration of function in hiredis.c */
+void __redisAppendCommand(redisContext *c, char *cmd, size_t len);
+
+/* Functions managing dictionary of callbacks for pub/sub. */
+static unsigned int callbackHash(const void *key) {
+    return dictGenHashFunction((unsigned char*)key,sdslen((char*)key));
+}
+
+static void *callbackValDup(void *privdata, const void *src) {
+    ((void) privdata);
+    redisCallback *dup = malloc(sizeof(*dup));
+    memcpy(dup,src,sizeof(*dup));
+    return dup;
+}
+
+static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) {
+    int l1, l2;
+    ((void) privdata);
+
+    l1 = sdslen((sds)key1);
+    l2 = sdslen((sds)key2);
+    if (l1 != l2) return 0;
+    return memcmp(key1,key2,l1) == 0;
+}
+
+static void callbackKeyDestructor(void *privdata, void *key) {
+    ((void) privdata);
+    sdsfree((sds)key);
+}
+
+static void callbackValDestructor(void *privdata, void *val) {
+    ((void) privdata);
+    free(val);
+}
+
+static dictType callbackDict = {
+    callbackHash,
+    NULL,
+    callbackValDup,
+    callbackKeyCompare,
+    callbackKeyDestructor,
+    callbackValDestructor
+};
+
+static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
+    redisAsyncContext *ac;
+
+    ac = realloc(c,sizeof(redisAsyncContext));
+    if (ac == NULL)
+        return NULL;
+
+    c = &(ac->c);
+
+    /* The regular connect functions will always set the flag REDIS_CONNECTED.
+     * For the async API, we want to wait until the first write event is
+     * received up before setting this flag, so reset it here. */
+    c->flags &= ~REDIS_CONNECTED;
+
+    ac->err = 0;
+    ac->errstr = NULL;
+    ac->data = NULL;
+
+    ac->ev.data = NULL;
+    ac->ev.addRead = NULL;
+    ac->ev.delRead = NULL;
+    ac->ev.addWrite = NULL;
+    ac->ev.delWrite = NULL;
+    ac->ev.cleanup = NULL;
+
+    ac->onConnect = NULL;
+    ac->onDisconnect = NULL;
+
+    ac->replies.head = NULL;
+    ac->replies.tail = NULL;
+    ac->sub.invalid.head = NULL;
+    ac->sub.invalid.tail = NULL;
+    ac->sub.channels = dictCreate(&callbackDict,NULL);
+    ac->sub.patterns = dictCreate(&callbackDict,NULL);
+    return ac;
+}
+
+/* We want the error field to be accessible directly instead of requiring
+ * an indirection to the redisContext struct. */
+static void __redisAsyncCopyError(redisAsyncContext *ac) {
+    redisContext *c = &(ac->c);
+    ac->err = c->err;
+    ac->errstr = c->errstr;
+}
+
+redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
+    redisContext *c;
+    redisAsyncContext *ac;
+
+    c = redisConnectNonBlock(ip,port);
+    if (c == NULL)
+        return NULL;
+
+    ac = redisAsyncInitialize(c);
+    if (ac == NULL) {
+        redisFree(c);
+        return NULL;
+    }
+
+    __redisAsyncCopyError(ac);
+    return ac;
+}
+
+redisAsyncContext *redisAsyncConnectUnix(const char *path) {
+    redisContext *c;
+    redisAsyncContext *ac;
+
+    c = redisConnectUnixNonBlock(path);
+    if (c == NULL)
+        return NULL;
+
+    ac = redisAsyncInitialize(c);
+    __redisAsyncCopyError(ac);
+    return ac;
+}
+
+int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
+    if (ac->onConnect == NULL) {
+        ac->onConnect = fn;
+
+        /* The common way to detect an established connection is to wait for
+         * the first write event to be fired. This assumes the related event
+         * library functions are already set. */
+        _EL_ADD_WRITE(ac);
+        return REDIS_OK;
+    }
+    return REDIS_ERR;
+}
+
+int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) {
+    if (ac->onDisconnect == NULL) {
+        ac->onDisconnect = fn;
+        return REDIS_OK;
+    }
+    return REDIS_ERR;
+}
+
+/* Helper functions to push/shift callbacks */
+static int __redisPushCallback(redisCallbackList *list, redisCallback *source) {
+    redisCallback *cb;
+
+    /* Copy callback from stack to heap */
+    cb = malloc(sizeof(*cb));
+    if (cb == NULL)
+        return REDIS_ERR_OOM;
+
+    if (source != NULL) {
+        memcpy(cb,source,sizeof(*cb));
+        cb->next = NULL;
+    }
+
+    /* Store callback in list */
+    if (list->head == NULL)
+        list->head = cb;
+    if (list->tail != NULL)
+        list->tail->next = cb;
+    list->tail = cb;
+    return REDIS_OK;
+}
+
+static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) {
+    redisCallback *cb = list->head;
+    if (cb != NULL) {
+        list->head = cb->next;
+        if (cb == list->tail)
+            list->tail = NULL;
+
+        /* Copy callback from heap to stack */
+        if (target != NULL)
+            memcpy(target,cb,sizeof(*cb));
+        free(cb);
+        return REDIS_OK;
+    }
+    return REDIS_ERR;
+}
+
+static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) {
+    redisContext *c = &(ac->c);
+    if (cb->fn != NULL) {
+        c->flags |= REDIS_IN_CALLBACK;
+        cb->fn(ac,reply,cb->privdata);
+        c->flags &= ~REDIS_IN_CALLBACK;
+    }
+}
+
+/* Helper function to free the context. */
+static void __redisAsyncFree(redisAsyncContext *ac) {
+    redisContext *c = &(ac->c);
+    redisCallback cb;
+    dictIterator *it;
+    dictEntry *de;
+
+    /* Execute pending callbacks with NULL reply. */
+    while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK)
+        __redisRunCallback(ac,&cb,NULL);
+
+    /* Execute callbacks for invalid commands */
+    while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK)
+        __redisRunCallback(ac,&cb,NULL);
+
+    /* Run subscription callbacks callbacks with NULL reply */
+    it = dictGetIterator(ac->sub.channels);
+    while ((de = dictNext(it)) != NULL)
+        __redisRunCallback(ac,dictGetEntryVal(de),NULL);
+    dictReleaseIterator(it);
+    dictRelease(ac->sub.channels);
+
+    it = dictGetIterator(ac->sub.patterns);
+    while ((de = dictNext(it)) != NULL)
+        __redisRunCallback(ac,dictGetEntryVal(de),NULL);
+    dictReleaseIterator(it);
+    dictRelease(ac->sub.patterns);
+
+    /* Signal event lib to clean up */
+    _EL_CLEANUP(ac);
+
+    /* Execute disconnect callback. When redisAsyncFree() initiated destroying
+     * this context, the status will always be REDIS_OK. */
+    if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) {
+        if (c->flags & REDIS_FREEING) {
+            ac->onDisconnect(ac,REDIS_OK);
+        } else {
+            ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR);
+        }
+    }
+
+    /* Cleanup self */
+    redisFree(c);
+}
+
+/* Free the async context. When this function is called from a callback,
+ * control needs to be returned to redisProcessCallbacks() before actual
+ * free'ing. To do so, a flag is set on the context which is picked up by
+ * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */
+void redisAsyncFree(redisAsyncContext *ac) {
+    redisContext *c = &(ac->c);
+    c->flags |= REDIS_FREEING;
+    if (!(c->flags & REDIS_IN_CALLBACK))
+        __redisAsyncFree(ac);
+}
+
+/* Helper function to make the disconnect happen and clean up. */
+static void __redisAsyncDisconnect(redisAsyncContext *ac) {
+    redisContext *c = &(ac->c);
+
+    /* Make sure error is accessible if there is any */
+    __redisAsyncCopyError(ac);
+
+    if (ac->err == 0) {
+        /* For clean disconnects, there should be no pending callbacks. */
+        assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR);
+    } else {
+        /* Disconnection is caused by an error, make sure that pending
+         * callbacks cannot call new commands. */
+        c->flags |= REDIS_DISCONNECTING;
+    }
+
+    /* For non-clean disconnects, __redisAsyncFree() will execute pending
+     * callbacks with a NULL-reply. */
+    __redisAsyncFree(ac);
+}
+
+/* Tries to do a clean disconnect from Redis, meaning it stops new commands
+ * from being issued, but tries to flush the output buffer and execute
+ * callbacks for all remaining replies. When this function is called from a
+ * callback, there might be more replies and we can safely defer disconnecting
+ * to redisProcessCallbacks(). Otherwise, we can only disconnect immediately
+ * when there are no pending callbacks. */
+void redisAsyncDisconnect(redisAsyncContext *ac) {
+    redisContext *c = &(ac->c);
+    c->flags |= REDIS_DISCONNECTING;
+    if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL)
+        __redisAsyncDisconnect(ac);
+}
+
+static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) {
+    redisContext *c = &(ac->c);
+    dict *callbacks;
+    dictEntry *de;
+    int pvariant;
+    char *stype;
+    sds sname;
+
+    /* Custom reply functions are not supported for pub/sub. This will fail
+     * very hard when they are used... */
+    if (reply->type == REDIS_REPLY_ARRAY) {
+        assert(reply->elements >= 2);
+        assert(reply->element[0]->type == REDIS_REPLY_STRING);
+        stype = reply->element[0]->str;
+        pvariant = (tolower(stype[0]) == 'p') ? 1 : 0;
+
+        if (pvariant)
+            callbacks = ac->sub.patterns;
+        else
+            callbacks = ac->sub.channels;
+
+        /* Locate the right callback */
+        assert(reply->element[1]->type == REDIS_REPLY_STRING);
+        sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len);
+        de = dictFind(callbacks,sname);
+        if (de != NULL) {
+            memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb));
+
+            /* If this is an unsubscribe message, remove it. */
+            if (strcasecmp(stype+pvariant,"unsubscribe") == 0) {
+                dictDelete(callbacks,sname);
+
+                /* If this was the last unsubscribe message, revert to
+                 * non-subscribe mode. */
+                assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
+                if (reply->element[2]->integer == 0)
+                    c->flags &= ~REDIS_SUBSCRIBED;
+            }
+        }
+        sdsfree(sname);
+    } else {
+        /* Shift callback for invalid commands. */
+        __redisShiftCallback(&ac->sub.invalid,dstcb);
+    }
+    return REDIS_OK;
+}
+
+void redisProcessCallbacks(redisAsyncContext *ac) {
+    redisContext *c = &(ac->c);
+    redisCallback cb;
+    void *reply = NULL;
+    int status;
+
+    while((status = redisGetReply(c,&reply)) == REDIS_OK) {
+        if (reply == NULL) {
+            /* When the connection is being disconnected and there are
+             * no more replies, this is the cue to really disconnect. */
+            if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0) {
+                __redisAsyncDisconnect(ac);
+                return;
+            }
+            
+            /* If monitor mode, repush callback */
+            if(c->flags & REDIS_MONITORING) {
+                __redisPushCallback(&ac->replies,&cb);
+            }
+
+            /* When the connection is not being disconnected, simply stop
+             * trying to get replies and wait for the next loop tick. */
+            break;
+        }
+
+        /* Even if the context is subscribed, pending regular callbacks will
+         * get a reply before pub/sub messages arrive. */
+        if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) {
+            /*
+             * A spontaneous reply in a not-subscribed context can be the error
+             * reply that is sent when a new connection exceeds the maximum
+             * number of allowed connections on the server side.
+             *
+             * This is seen as an error instead of a regular reply because the
+             * server closes the connection after sending it.
+             *
+             * To prevent the error from being overwritten by an EOF error the
+             * connection is closed here. See issue #43.
+             *
+             * Another possibility is that the server is loading its dataset.
+             * In this case we also want to close the connection, and have the
+             * user wait until the server is ready to take our request.
+             */
+            if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) {
+                c->err = REDIS_ERR_OTHER;
+                snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str);
+                __redisAsyncDisconnect(ac);
+                return;
+            }
+            /* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */
+            assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING));
+            if(c->flags & REDIS_SUBSCRIBED)
+                __redisGetSubscribeCallback(ac,reply,&cb);
+        }
+
+        if (cb.fn != NULL) {
+            __redisRunCallback(ac,&cb,reply);
+            c->reader->fn->freeObject(reply);
+
+            /* Proceed with free'ing when redisAsyncFree() was called. */
+            if (c->flags & REDIS_FREEING) {
+                __redisAsyncFree(ac);
+                return;
+            }
+        } else {
+            /* No callback for this reply. This can either be a NULL callback,
+             * or there were no callbacks to begin with. Either way, don't
+             * abort with an error, but simply ignore it because the client
+             * doesn't know what the server will spit out over the wire. */
+            c->reader->fn->freeObject(reply);
+        }
+    }
+
+    /* Disconnect when there was an error reading the reply */
+    if (status != REDIS_OK)
+        __redisAsyncDisconnect(ac);
+}
+
+/* Internal helper function to detect socket status the first time a read or
+ * write event fires. When connecting was not succesful, the connect callback
+ * is called with a REDIS_ERR status and the context is free'd. */
+static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
+    redisContext *c = &(ac->c);
+
+    if (redisCheckSocketError(c,c->fd) == REDIS_ERR) {
+        /* Try again later when connect(2) is still in progress. */
+        if (errno == EINPROGRESS)
+            return REDIS_OK;
+
+        if (ac->onConnect) ac->onConnect(ac,REDIS_ERR);
+        __redisAsyncDisconnect(ac);
+        return REDIS_ERR;
+    }
+
+    /* Mark context as connected. */
+    c->flags |= REDIS_CONNECTED;
+    if (ac->onConnect) ac->onConnect(ac,REDIS_OK);
+    return REDIS_OK;
+}
+
+/* This function should be called when the socket is readable.
+ * It processes all replies that can be read and executes their callbacks.
+ */
+void redisAsyncHandleRead(redisAsyncContext *ac) {
+    redisContext *c = &(ac->c);
+
+    if (!(c->flags & REDIS_CONNECTED)) {
+        /* Abort connect was not successful. */
+        if (__redisAsyncHandleConnect(ac) != REDIS_OK)
+            return;
+        /* Try again later when the context is still not connected. */
+        if (!(c->flags & REDIS_CONNECTED))
+            return;
+    }
+
+    if (redisBufferRead(c) == REDIS_ERR) {
+        __redisAsyncDisconnect(ac);
+    } else {
+        /* Always re-schedule reads */
+        _EL_ADD_READ(ac);
+        redisProcessCallbacks(ac);
+    }
+}
+
+void redisAsyncHandleWrite(redisAsyncContext *ac) {
+    redisContext *c = &(ac->c);
+    int done = 0;
+
+    if (!(c->flags & REDIS_CONNECTED)) {
+        /* Abort connect was not successful. */
+        if (__redisAsyncHandleConnect(ac) != REDIS_OK)
+            return;
+        /* Try again later when the context is still not connected. */
+        if (!(c->flags & REDIS_CONNECTED))
+            return;
+    }
+
+    if (redisBufferWrite(c,&done) == REDIS_ERR) {
+        __redisAsyncDisconnect(ac);
+    } else {
+        /* Continue writing when not done, stop writing otherwise */
+        if (!done)
+            _EL_ADD_WRITE(ac);
+        else
+            _EL_DEL_WRITE(ac);
+
+        /* Always schedule reads after writes */
+        _EL_ADD_READ(ac);
+    }
+}
+
+/* Sets a pointer to the first argument and its length starting at p. Returns
+ * the number of bytes to skip to get to the following argument. */
+static char *nextArgument(char *start, char **str, size_t *len) {
+    char *p = start;
+    if (p[0] != '$') {
+        p = strchr(p,'$');
+        if (p == NULL) return NULL;
+    }
+
+    *len = (int)strtol(p+1,NULL,10);
+    p = strchr(p,'\r');
+    assert(p);
+    *str = p+2;
+    return p+2+(*len)+2;
+}
+
+/* Helper function for the redisAsyncCommand* family of functions. Writes a
+ * formatted command to the output buffer and registers the provided callback
+ * function with the context. */
+static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, char *cmd, size_t len) {
+    redisContext *c = &(ac->c);
+    redisCallback cb;
+    int pvariant, hasnext;
+    char *cstr, *astr;
+    size_t clen, alen;
+    char *p;
+    sds sname;
+
+    /* Don't accept new commands when the connection is about to be closed. */
+    if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR;
+
+    /* Setup callback */
+    cb.fn = fn;
+    cb.privdata = privdata;
+
+    /* Find out which command will be appended. */
+    p = nextArgument(cmd,&cstr,&clen);
+    assert(p != NULL);
+    hasnext = (p[0] == '$');
+    pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0;
+    cstr += pvariant;
+    clen -= pvariant;
+
+    if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) {
+        c->flags |= REDIS_SUBSCRIBED;
+
+        /* Add every channel/pattern to the list of subscription callbacks. */
+        while ((p = nextArgument(p,&astr,&alen)) != NULL) {
+            sname = sdsnewlen(astr,alen);
+            if (pvariant)
+                dictReplace(ac->sub.patterns,sname,&cb);
+            else
+                dictReplace(ac->sub.channels,sname,&cb);
+        }
+    } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) {
+        /* It is only useful to call (P)UNSUBSCRIBE when the context is
+         * subscribed to one or more channels or patterns. */
+        if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR;
+
+        /* (P)UNSUBSCRIBE does not have its own response: every channel or
+         * pattern that is unsubscribed will receive a message. This means we
+         * should not append a callback function for this command. */
+     } else if(strncasecmp(cstr,"monitor\r\n",9) == 0) {
+         /* Set monitor flag and push callback */
+         c->flags |= REDIS_MONITORING;
+         __redisPushCallback(&ac->replies,&cb);
+    } else {
+        if (c->flags & REDIS_SUBSCRIBED)
+            /* This will likely result in an error reply, but it needs to be
+             * received and passed to the callback. */
+            __redisPushCallback(&ac->sub.invalid,&cb);
+        else
+            __redisPushCallback(&ac->replies,&cb);
+    }
+
+    __redisAppendCommand(c,cmd,len);
+
+    /* Always schedule a write when the write buffer is non-empty */
+    _EL_ADD_WRITE(ac);
+
+    return REDIS_OK;
+}
+
+int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) {
+    char *cmd;
+    int len;
+    int status;
+    len = redisvFormatCommand(&cmd,format,ap);
+    status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
+    free(cmd);
+    return status;
+}
+
+int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) {
+    va_list ap;
+    int status;
+    va_start(ap,format);
+    status = redisvAsyncCommand(ac,fn,privdata,format,ap);
+    va_end(ap);
+    return status;
+}
+
+int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) {
+    char *cmd;
+    int len;
+    int status;
+    len = redisFormatCommandArgv(&cmd,argc,argv,argvlen);
+    status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
+    free(cmd);
+    return status;
+}
diff --git a/hiredis/async.h b/hiredis/async.h
new file mode 100644 (file)
index 0000000..268274e
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __HIREDIS_ASYNC_H
+#define __HIREDIS_ASYNC_H
+#include "hiredis.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct redisAsyncContext; /* need forward declaration of redisAsyncContext */
+struct dict; /* dictionary header is included in async.c */
+
+/* Reply callback prototype and container */
+typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*);
+typedef struct redisCallback {
+    struct redisCallback *next; /* simple singly linked list */
+    redisCallbackFn *fn;
+    void *privdata;
+} redisCallback;
+
+/* List of callbacks for either regular replies or pub/sub */
+typedef struct redisCallbackList {
+    redisCallback *head, *tail;
+} redisCallbackList;
+
+/* Connection callback prototypes */
+typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
+typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status);
+
+/* Context for an async connection to Redis */
+typedef struct redisAsyncContext {
+    /* Hold the regular context, so it can be realloc'ed. */
+    redisContext c;
+
+    /* Setup error flags so they can be used directly. */
+    int err;
+    char *errstr;
+
+    /* Not used by hiredis */
+    void *data;
+
+    /* Event library data and hooks */
+    struct {
+        void *data;
+
+        /* Hooks that are called when the library expects to start
+         * reading/writing. These functions should be idempotent. */
+        void (*addRead)(void *privdata);
+        void (*delRead)(void *privdata);
+        void (*addWrite)(void *privdata);
+        void (*delWrite)(void *privdata);
+        void (*cleanup)(void *privdata);
+    } ev;
+
+    /* Called when either the connection is terminated due to an error or per
+     * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */
+    redisDisconnectCallback *onDisconnect;
+
+    /* Called when the first write event was received. */
+    redisConnectCallback *onConnect;
+
+    /* Regular command callbacks */
+    redisCallbackList replies;
+
+    /* Subscription callbacks */
+    struct {
+        redisCallbackList invalid;
+        struct dict *channels;
+        struct dict *patterns;
+    } sub;
+} redisAsyncContext;
+
+/* Functions that proxy to hiredis */
+redisAsyncContext *redisAsyncConnect(const char *ip, int port);
+redisAsyncContext *redisAsyncConnectUnix(const char *path);
+int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
+int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
+void redisAsyncDisconnect(redisAsyncContext *ac);
+void redisAsyncFree(redisAsyncContext *ac);
+
+/* Handle read/write events */
+void redisAsyncHandleRead(redisAsyncContext *ac);
+void redisAsyncHandleWrite(redisAsyncContext *ac);
+
+/* Command functions for an async context. Write the command to the
+ * output buffer and register the provided callback. */
+int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap);
+int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);
+int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/hiredis/dict.c b/hiredis/dict.c
new file mode 100644 (file)
index 0000000..79b1041
--- /dev/null
@@ -0,0 +1,338 @@
+/* Hash table implementation.
+ *
+ * This file implements in memory hash tables with insert/del/replace/find/
+ * get-random-element operations. Hash tables will auto resize if needed
+ * tables of power of two in size are used, collisions are handled by
+ * chaining. See the source code for more information... :)
+ *
+ * Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "fmacros.h"
+#include <stdlib.h>
+#include <assert.h>
+#include <limits.h>
+#include "dict.h"
+
+/* -------------------------- private prototypes ---------------------------- */
+
+static int _dictExpandIfNeeded(dict *ht);
+static unsigned long _dictNextPower(unsigned long size);
+static int _dictKeyIndex(dict *ht, const void *key);
+static int _dictInit(dict *ht, dictType *type, void *privDataPtr);
+
+/* -------------------------- hash functions -------------------------------- */
+
+/* Generic hash function (a popular one from Bernstein).
+ * I tested a few and this was the best. */
+static unsigned int dictGenHashFunction(const unsigned char *buf, int len) {
+    unsigned int hash = 5381;
+
+    while (len--)
+        hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */
+    return hash;
+}
+
+/* ----------------------------- API implementation ------------------------- */
+
+/* Reset an hashtable already initialized with ht_init().
+ * NOTE: This function should only called by ht_destroy(). */
+static void _dictReset(dict *ht) {
+    ht->table = NULL;
+    ht->size = 0;
+    ht->sizemask = 0;
+    ht->used = 0;
+}
+
+/* Create a new hash table */
+static dict *dictCreate(dictType *type, void *privDataPtr) {
+    dict *ht = malloc(sizeof(*ht));
+    _dictInit(ht,type,privDataPtr);
+    return ht;
+}
+
+/* Initialize the hash table */
+static int _dictInit(dict *ht, dictType *type, void *privDataPtr) {
+    _dictReset(ht);
+    ht->type = type;
+    ht->privdata = privDataPtr;
+    return DICT_OK;
+}
+
+/* Expand or create the hashtable */
+static int dictExpand(dict *ht, unsigned long size) {
+    dict n; /* the new hashtable */
+    unsigned long realsize = _dictNextPower(size), i;
+
+    /* the size is invalid if it is smaller than the number of
+     * elements already inside the hashtable */
+    if (ht->used > size)
+        return DICT_ERR;
+
+    _dictInit(&n, ht->type, ht->privdata);
+    n.size = realsize;
+    n.sizemask = realsize-1;
+    n.table = calloc(realsize,sizeof(dictEntry*));
+
+    /* Copy all the elements from the old to the new table:
+     * note that if the old hash table is empty ht->size is zero,
+     * so dictExpand just creates an hash table. */
+    n.used = ht->used;
+    for (i = 0; i < ht->size && ht->used > 0; i++) {
+        dictEntry *he, *nextHe;
+
+        if (ht->table[i] == NULL) continue;
+
+        /* For each hash entry on this slot... */
+        he = ht->table[i];
+        while(he) {
+            unsigned int h;
+
+            nextHe = he->next;
+            /* Get the new element index */
+            h = dictHashKey(ht, he->key) & n.sizemask;
+            he->next = n.table[h];
+            n.table[h] = he;
+            ht->used--;
+            /* Pass to the next element */
+            he = nextHe;
+        }
+    }
+    assert(ht->used == 0);
+    free(ht->table);
+
+    /* Remap the new hashtable in the old */
+    *ht = n;
+    return DICT_OK;
+}
+
+/* Add an element to the target hash table */
+static int dictAdd(dict *ht, void *key, void *val) {
+    int index;
+    dictEntry *entry;
+
+    /* Get the index of the new element, or -1 if
+     * the element already exists. */
+    if ((index = _dictKeyIndex(ht, key)) == -1)
+        return DICT_ERR;
+
+    /* Allocates the memory and stores key */
+    entry = malloc(sizeof(*entry));
+    entry->next = ht->table[index];
+    ht->table[index] = entry;
+
+    /* Set the hash entry fields. */
+    dictSetHashKey(ht, entry, key);
+    dictSetHashVal(ht, entry, val);
+    ht->used++;
+    return DICT_OK;
+}
+
+/* Add an element, discarding the old if the key already exists.
+ * Return 1 if the key was added from scratch, 0 if there was already an
+ * element with such key and dictReplace() just performed a value update
+ * operation. */
+static int dictReplace(dict *ht, void *key, void *val) {
+    dictEntry *entry, auxentry;
+
+    /* Try to add the element. If the key
+     * does not exists dictAdd will suceed. */
+    if (dictAdd(ht, key, val) == DICT_OK)
+        return 1;
+    /* It already exists, get the entry */
+    entry = dictFind(ht, key);
+    /* Free the old value and set the new one */
+    /* Set the new value and free the old one. Note that it is important
+     * to do that in this order, as the value may just be exactly the same
+     * as the previous one. In this context, think to reference counting,
+     * you want to increment (set), and then decrement (free), and not the
+     * reverse. */
+    auxentry = *entry;
+    dictSetHashVal(ht, entry, val);
+    dictFreeEntryVal(ht, &auxentry);
+    return 0;
+}
+
+/* Search and remove an element */
+static int dictDelete(dict *ht, const void *key) {
+    unsigned int h;
+    dictEntry *de, *prevde;
+
+    if (ht->size == 0)
+        return DICT_ERR;
+    h = dictHashKey(ht, key) & ht->sizemask;
+    de = ht->table[h];
+
+    prevde = NULL;
+    while(de) {
+        if (dictCompareHashKeys(ht,key,de->key)) {
+            /* Unlink the element from the list */
+            if (prevde)
+                prevde->next = de->next;
+            else
+                ht->table[h] = de->next;
+
+            dictFreeEntryKey(ht,de);
+            dictFreeEntryVal(ht,de);
+            free(de);
+            ht->used--;
+            return DICT_OK;
+        }
+        prevde = de;
+        de = de->next;
+    }
+    return DICT_ERR; /* not found */
+}
+
+/* Destroy an entire hash table */
+static int _dictClear(dict *ht) {
+    unsigned long i;
+
+    /* Free all the elements */
+    for (i = 0; i < ht->size && ht->used > 0; i++) {
+        dictEntry *he, *nextHe;
+
+        if ((he = ht->table[i]) == NULL) continue;
+        while(he) {
+            nextHe = he->next;
+            dictFreeEntryKey(ht, he);
+            dictFreeEntryVal(ht, he);
+            free(he);
+            ht->used--;
+            he = nextHe;
+        }
+    }
+    /* Free the table and the allocated cache structure */
+    free(ht->table);
+    /* Re-initialize the table */
+    _dictReset(ht);
+    return DICT_OK; /* never fails */
+}
+
+/* Clear & Release the hash table */
+static void dictRelease(dict *ht) {
+    _dictClear(ht);
+    free(ht);
+}
+
+static dictEntry *dictFind(dict *ht, const void *key) {
+    dictEntry *he;
+    unsigned int h;
+
+    if (ht->size == 0) return NULL;
+    h = dictHashKey(ht, key) & ht->sizemask;
+    he = ht->table[h];
+    while(he) {
+        if (dictCompareHashKeys(ht, key, he->key))
+            return he;
+        he = he->next;
+    }
+    return NULL;
+}
+
+static dictIterator *dictGetIterator(dict *ht) {
+    dictIterator *iter = malloc(sizeof(*iter));
+
+    iter->ht = ht;
+    iter->index = -1;
+    iter->entry = NULL;
+    iter->nextEntry = NULL;
+    return iter;
+}
+
+static dictEntry *dictNext(dictIterator *iter) {
+    while (1) {
+        if (iter->entry == NULL) {
+            iter->index++;
+            if (iter->index >=
+                    (signed)iter->ht->size) break;
+            iter->entry = iter->ht->table[iter->index];
+        } else {
+            iter->entry = iter->nextEntry;
+        }
+        if (iter->entry) {
+            /* We need to save the 'next' here, the iterator user
+             * may delete the entry we are returning. */
+            iter->nextEntry = iter->entry->next;
+            return iter->entry;
+        }
+    }
+    return NULL;
+}
+
+static void dictReleaseIterator(dictIterator *iter) {
+    free(iter);
+}
+
+/* ------------------------- private functions ------------------------------ */
+
+/* Expand the hash table if needed */
+static int _dictExpandIfNeeded(dict *ht) {
+    /* If the hash table is empty expand it to the intial size,
+     * if the table is "full" dobule its size. */
+    if (ht->size == 0)
+        return dictExpand(ht, DICT_HT_INITIAL_SIZE);
+    if (ht->used == ht->size)
+        return dictExpand(ht, ht->size*2);
+    return DICT_OK;
+}
+
+/* Our hash table capability is a power of two */
+static unsigned long _dictNextPower(unsigned long size) {
+    unsigned long i = DICT_HT_INITIAL_SIZE;
+
+    if (size >= LONG_MAX) return LONG_MAX;
+    while(1) {
+        if (i >= size)
+            return i;
+        i *= 2;
+    }
+}
+
+/* Returns the index of a free slot that can be populated with
+ * an hash entry for the given 'key'.
+ * If the key already exists, -1 is returned. */
+static int _dictKeyIndex(dict *ht, const void *key) {
+    unsigned int h;
+    dictEntry *he;
+
+    /* Expand the hashtable if needed */
+    if (_dictExpandIfNeeded(ht) == DICT_ERR)
+        return -1;
+    /* Compute the key hash value */
+    h = dictHashKey(ht, key) & ht->sizemask;
+    /* Search if this slot does not already contain the given key */
+    he = ht->table[h];
+    while(he) {
+        if (dictCompareHashKeys(ht, key, he->key))
+            return -1;
+        he = he->next;
+    }
+    return h;
+}
+
diff --git a/hiredis/dict.h b/hiredis/dict.h
new file mode 100644 (file)
index 0000000..95fcd28
--- /dev/null
@@ -0,0 +1,126 @@
+/* Hash table implementation.
+ *
+ * This file implements in memory hash tables with insert/del/replace/find/
+ * get-random-element operations. Hash tables will auto resize if needed
+ * tables of power of two in size are used, collisions are handled by
+ * chaining. See the source code for more information... :)
+ *
+ * Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __DICT_H
+#define __DICT_H
+
+#define DICT_OK 0
+#define DICT_ERR 1
+
+/* Unused arguments generate annoying warnings... */
+#define DICT_NOTUSED(V) ((void) V)
+
+typedef struct dictEntry {
+    void *key;
+    void *val;
+    struct dictEntry *next;
+} dictEntry;
+
+typedef struct dictType {
+    unsigned int (*hashFunction)(const void *key);
+    void *(*keyDup)(void *privdata, const void *key);
+    void *(*valDup)(void *privdata, const void *obj);
+    int (*keyCompare)(void *privdata, const void *key1, const void *key2);
+    void (*keyDestructor)(void *privdata, void *key);
+    void (*valDestructor)(void *privdata, void *obj);
+} dictType;
+
+typedef struct dict {
+    dictEntry **table;
+    dictType *type;
+    unsigned long size;
+    unsigned long sizemask;
+    unsigned long used;
+    void *privdata;
+} dict;
+
+typedef struct dictIterator {
+    dict *ht;
+    int index;
+    dictEntry *entry, *nextEntry;
+} dictIterator;
+
+/* This is the initial size of every hash table */
+#define DICT_HT_INITIAL_SIZE     4
+
+/* ------------------------------- Macros ------------------------------------*/
+#define dictFreeEntryVal(ht, entry) \
+    if ((ht)->type->valDestructor) \
+        (ht)->type->valDestructor((ht)->privdata, (entry)->val)
+
+#define dictSetHashVal(ht, entry, _val_) do { \
+    if ((ht)->type->valDup) \
+        entry->val = (ht)->type->valDup((ht)->privdata, _val_); \
+    else \
+        entry->val = (_val_); \
+} while(0)
+
+#define dictFreeEntryKey(ht, entry) \
+    if ((ht)->type->keyDestructor) \
+        (ht)->type->keyDestructor((ht)->privdata, (entry)->key)
+
+#define dictSetHashKey(ht, entry, _key_) do { \
+    if ((ht)->type->keyDup) \
+        entry->key = (ht)->type->keyDup((ht)->privdata, _key_); \
+    else \
+        entry->key = (_key_); \
+} while(0)
+
+#define dictCompareHashKeys(ht, key1, key2) \
+    (((ht)->type->keyCompare) ? \
+        (ht)->type->keyCompare((ht)->privdata, key1, key2) : \
+        (key1) == (key2))
+
+#define dictHashKey(ht, key) (ht)->type->hashFunction(key)
+
+#define dictGetEntryKey(he) ((he)->key)
+#define dictGetEntryVal(he) ((he)->val)
+#define dictSlots(ht) ((ht)->size)
+#define dictSize(ht) ((ht)->used)
+
+/* API */
+static unsigned int dictGenHashFunction(const unsigned char *buf, int len);
+static dict *dictCreate(dictType *type, void *privDataPtr);
+static int dictExpand(dict *ht, unsigned long size);
+static int dictAdd(dict *ht, void *key, void *val);
+static int dictReplace(dict *ht, void *key, void *val);
+static int dictDelete(dict *ht, const void *key);
+static void dictRelease(dict *ht);
+static dictEntry * dictFind(dict *ht, const void *key);
+static dictIterator *dictGetIterator(dict *ht);
+static dictEntry *dictNext(dictIterator *iter);
+static void dictReleaseIterator(dictIterator *iter);
+
+#endif /* __DICT_H */
diff --git a/hiredis/example-ae.c b/hiredis/example-ae.c
new file mode 100644 (file)
index 0000000..5ed34a3
--- /dev/null
@@ -0,0 +1,56 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include "hiredis.h"
+#include "async.h"
+#include "adapters/ae.h"
+
+/* Put event loop in the global scope, so it can be explicitly stopped */
+static aeEventLoop *loop;
+
+void getCallback(redisAsyncContext *c, void *r, void *privdata) {
+    redisReply *reply = r;
+    if (reply == NULL) return;
+    printf("argv[%s]: %s\n", (char*)privdata, reply->str);
+
+    /* Disconnect after receiving the reply to GET */
+    redisAsyncDisconnect(c);
+}
+
+void connectCallback(const redisAsyncContext *c, int status) {
+    if (status != REDIS_OK) {
+        printf("Error: %s\n", c->errstr);
+        return;
+    }
+    printf("Connected...\n");
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+    if (status != REDIS_OK) {
+        printf("Error: %s\n", c->errstr);
+        return;
+    }
+    printf("Disconnected...\n");
+}
+
+int main (int argc, char **argv) {
+    signal(SIGPIPE, SIG_IGN);
+
+    redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+    if (c->err) {
+        /* Let *c leak for now... */
+        printf("Error: %s\n", c->errstr);
+        return 1;
+    }
+
+    loop = aeCreateEventLoop();
+    redisAeAttach(loop, c);
+    redisAsyncSetConnectCallback(c,connectCallback);
+    redisAsyncSetDisconnectCallback(c,disconnectCallback);
+    redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
+    redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
+    aeMain(loop);
+    return 0;
+}
+
diff --git a/hiredis/example-libev.c b/hiredis/example-libev.c
new file mode 100644 (file)
index 0000000..7894f1f
--- /dev/null
@@ -0,0 +1,51 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include "hiredis.h"
+#include "async.h"
+#include "adapters/libev.h"
+
+void getCallback(redisAsyncContext *c, void *r, void *privdata) {
+    redisReply *reply = r;
+    if (reply == NULL) return;
+    printf("argv[%s]: %s\n", (char*)privdata, reply->str);
+
+    /* Disconnect after receiving the reply to GET */
+    redisAsyncDisconnect(c);
+}
+
+void connectCallback(const redisAsyncContext *c, int status) {
+    if (status != REDIS_OK) {
+        printf("Error: %s\n", c->errstr);
+        return;
+    }
+    printf("Connected...\n");
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+    if (status != REDIS_OK) {
+        printf("Error: %s\n", c->errstr);
+        return;
+    }
+    printf("Disconnected...\n");
+}
+
+int main (int argc, char **argv) {
+    signal(SIGPIPE, SIG_IGN);
+
+    redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+    if (c->err) {
+        /* Let *c leak for now... */
+        printf("Error: %s\n", c->errstr);
+        return 1;
+    }
+
+    redisLibevAttach(EV_DEFAULT_ c);
+    redisAsyncSetConnectCallback(c,connectCallback);
+    redisAsyncSetDisconnectCallback(c,disconnectCallback);
+    redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
+    redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
+    ev_loop(EV_DEFAULT_ 0);
+    return 0;
+}
diff --git a/hiredis/example-libevent.c b/hiredis/example-libevent.c
new file mode 100644 (file)
index 0000000..9da8e02
--- /dev/null
@@ -0,0 +1,52 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include "hiredis.h"
+#include "async.h"
+#include "adapters/libevent.h"
+
+void getCallback(redisAsyncContext *c, void *r, void *privdata) {
+    redisReply *reply = r;
+    if (reply == NULL) return;
+    printf("argv[%s]: %s\n", (char*)privdata, reply->str);
+
+    /* Disconnect after receiving the reply to GET */
+    redisAsyncDisconnect(c);
+}
+
+void connectCallback(const redisAsyncContext *c, int status) {
+    if (status != REDIS_OK) {
+        printf("Error: %s\n", c->errstr);
+        return;
+    }
+    printf("Connected...\n");
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+    if (status != REDIS_OK) {
+        printf("Error: %s\n", c->errstr);
+        return;
+    }
+    printf("Disconnected...\n");
+}
+
+int main (int argc, char **argv) {
+    signal(SIGPIPE, SIG_IGN);
+    struct event_base *base = event_base_new();
+
+    redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+    if (c->err) {
+        /* Let *c leak for now... */
+        printf("Error: %s\n", c->errstr);
+        return 1;
+    }
+
+    redisLibeventAttach(c,base);
+    redisAsyncSetConnectCallback(c,connectCallback);
+    redisAsyncSetDisconnectCallback(c,disconnectCallback);
+    redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
+    redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
+    event_base_dispatch(base);
+    return 0;
+}
diff --git a/hiredis/example.c b/hiredis/example.c
new file mode 100644 (file)
index 0000000..d9d7271
--- /dev/null
@@ -0,0 +1,73 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "hiredis.h"
+
+int main(void) {
+    unsigned int j;
+    redisContext *c;
+    redisReply *reply;
+
+    struct timeval timeout = { 1, 500000 }; // 1.5 seconds
+    c = redisConnectWithTimeout((char*)"127.0.0.1", 6379, timeout);
+    if (c == NULL || c->err) {
+        if (c) {
+            printf("Connection error: %s\n", c->errstr);
+            redisFree(c);
+        } else {
+            printf("Connection error: can't allocate redis context\n");
+        }
+        exit(1);
+    }
+
+    /* PING server */
+    reply = redisCommand(c,"PING");
+    printf("PING: %s\n", reply->str);
+    freeReplyObject(reply);
+
+    /* Set a key */
+    reply = redisCommand(c,"SET %s %s", "foo", "hello world");
+    printf("SET: %s\n", reply->str);
+    freeReplyObject(reply);
+
+    /* Set a key using binary safe API */
+    reply = redisCommand(c,"SET %b %b", "bar", 3, "hello", 5);
+    printf("SET (binary API): %s\n", reply->str);
+    freeReplyObject(reply);
+
+    /* Try a GET and two INCR */
+    reply = redisCommand(c,"GET foo");
+    printf("GET foo: %s\n", reply->str);
+    freeReplyObject(reply);
+
+    reply = redisCommand(c,"INCR counter");
+    printf("INCR counter: %lld\n", reply->integer);
+    freeReplyObject(reply);
+    /* again ... */
+    reply = redisCommand(c,"INCR counter");
+    printf("INCR counter: %lld\n", reply->integer);
+    freeReplyObject(reply);
+
+    /* Create a list of numbers, from 0 to 9 */
+    reply = redisCommand(c,"DEL mylist");
+    freeReplyObject(reply);
+    for (j = 0; j < 10; j++) {
+        char buf[64];
+
+        snprintf(buf,64,"%d",j);
+        reply = redisCommand(c,"LPUSH mylist element-%s", buf);
+        freeReplyObject(reply);
+    }
+
+    /* Let's check what we have inside the list */
+    reply = redisCommand(c,"LRANGE mylist 0 -1");
+    if (reply->type == REDIS_REPLY_ARRAY) {
+        for (j = 0; j < reply->elements; j++) {
+            printf("%u) %s\n", j, reply->element[j]->str);
+        }
+    }
+    freeReplyObject(reply);
+
+    return 0;
+}
diff --git a/hiredis/fmacros.h b/hiredis/fmacros.h
new file mode 100644 (file)
index 0000000..21cd9cf
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef __HIREDIS_FMACRO_H
+#define __HIREDIS_FMACRO_H
+
+#if !defined(_BSD_SOURCE)
+#define _BSD_SOURCE
+#endif
+
+#if defined(__sun__)
+#define _POSIX_C_SOURCE 200112L
+#elif defined(__linux__)
+#define _XOPEN_SOURCE 600
+#else
+#define _XOPEN_SOURCE
+#endif
+
+#endif
diff --git a/hiredis/hiredis.c b/hiredis/hiredis.c
new file mode 100644 (file)
index 0000000..958f58b
--- /dev/null
@@ -0,0 +1,1315 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "fmacros.h"
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <assert.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "hiredis.h"
+#include "net.h"
+#include "sds.h"
+
+static redisReply *createReplyObject(int type);
+static void *createStringObject(const redisReadTask *task, char *str, size_t len);
+static void *createArrayObject(const redisReadTask *task, int elements);
+static void *createIntegerObject(const redisReadTask *task, long long value);
+static void *createNilObject(const redisReadTask *task);
+
+/* Default set of functions to build the reply. Keep in mind that such a
+ * function returning NULL is interpreted as OOM. */
+static redisReplyObjectFunctions defaultFunctions = {
+    createStringObject,
+    createArrayObject,
+    createIntegerObject,
+    createNilObject,
+    freeReplyObject
+};
+
+/* Create a reply object */
+static redisReply *createReplyObject(int type) {
+    redisReply *r = calloc(1,sizeof(*r));
+
+    if (r == NULL)
+        return NULL;
+
+    r->type = type;
+    return r;
+}
+
+/* Free a reply object */
+void freeReplyObject(void *reply) {
+    redisReply *r = reply;
+    size_t j;
+
+    switch(r->type) {
+    case REDIS_REPLY_INTEGER:
+        break; /* Nothing to free */
+    case REDIS_REPLY_ARRAY:
+        if (r->element != NULL) {
+            for (j = 0; j < r->elements; j++)
+                if (r->element[j] != NULL)
+                    freeReplyObject(r->element[j]);
+            free(r->element);
+        }
+        break;
+    case REDIS_REPLY_ERROR:
+    case REDIS_REPLY_STATUS:
+    case REDIS_REPLY_STRING:
+        if (r->str != NULL)
+            free(r->str);
+        break;
+    }
+    free(r);
+}
+
+static void *createStringObject(const redisReadTask *task, char *str, size_t len) {
+    redisReply *r, *parent;
+    char *buf;
+
+    r = createReplyObject(task->type);
+    if (r == NULL)
+        return NULL;
+
+    buf = malloc(len+1);
+    if (buf == NULL) {
+        freeReplyObject(r);
+        return NULL;
+    }
+
+    assert(task->type == REDIS_REPLY_ERROR  ||
+           task->type == REDIS_REPLY_STATUS ||
+           task->type == REDIS_REPLY_STRING);
+
+    /* Copy string value */
+    memcpy(buf,str,len);
+    buf[len] = '\0';
+    r->str = buf;
+    r->len = len;
+
+    if (task->parent) {
+        parent = task->parent->obj;
+        assert(parent->type == REDIS_REPLY_ARRAY);
+        parent->element[task->idx] = r;
+    }
+    return r;
+}
+
+static void *createArrayObject(const redisReadTask *task, int elements) {
+    redisReply *r, *parent;
+
+    r = createReplyObject(REDIS_REPLY_ARRAY);
+    if (r == NULL)
+        return NULL;
+
+    if (elements > 0) {
+        r->element = calloc(elements,sizeof(redisReply*));
+        if (r->element == NULL) {
+            freeReplyObject(r);
+            return NULL;
+        }
+    }
+
+    r->elements = elements;
+
+    if (task->parent) {
+        parent = task->parent->obj;
+        assert(parent->type == REDIS_REPLY_ARRAY);
+        parent->element[task->idx] = r;
+    }
+    return r;
+}
+
+static void *createIntegerObject(const redisReadTask *task, long long value) {
+    redisReply *r, *parent;
+
+    r = createReplyObject(REDIS_REPLY_INTEGER);
+    if (r == NULL)
+        return NULL;
+
+    r->integer = value;
+
+    if (task->parent) {
+        parent = task->parent->obj;
+        assert(parent->type == REDIS_REPLY_ARRAY);
+        parent->element[task->idx] = r;
+    }
+    return r;
+}
+
+static void *createNilObject(const redisReadTask *task) {
+    redisReply *r, *parent;
+
+    r = createReplyObject(REDIS_REPLY_NIL);
+    if (r == NULL)
+        return NULL;
+
+    if (task->parent) {
+        parent = task->parent->obj;
+        assert(parent->type == REDIS_REPLY_ARRAY);
+        parent->element[task->idx] = r;
+    }
+    return r;
+}
+
+static void __redisReaderSetError(redisReader *r, int type, const char *str) {
+    size_t len;
+
+    if (r->reply != NULL && r->fn && r->fn->freeObject) {
+        r->fn->freeObject(r->reply);
+        r->reply = NULL;
+    }
+
+    /* Clear input buffer on errors. */
+    if (r->buf != NULL) {
+        sdsfree(r->buf);
+        r->buf = NULL;
+        r->pos = r->len = 0;
+    }
+
+    /* Reset task stack. */
+    r->ridx = -1;
+
+    /* Set error. */
+    r->err = type;
+    len = strlen(str);
+    len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1);
+    memcpy(r->errstr,str,len);
+    r->errstr[len] = '\0';
+}
+
+static size_t chrtos(char *buf, size_t size, char byte) {
+    size_t len = 0;
+
+    switch(byte) {
+    case '\\':
+    case '"':
+        len = snprintf(buf,size,"\"\\%c\"",byte);
+        break;
+    case '\n': len = snprintf(buf,size,"\"\\n\""); break;
+    case '\r': len = snprintf(buf,size,"\"\\r\""); break;
+    case '\t': len = snprintf(buf,size,"\"\\t\""); break;
+    case '\a': len = snprintf(buf,size,"\"\\a\""); break;
+    case '\b': len = snprintf(buf,size,"\"\\b\""); break;
+    default:
+        if (isprint(byte))
+            len = snprintf(buf,size,"\"%c\"",byte);
+        else
+            len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte);
+        break;
+    }
+
+    return len;
+}
+
+static void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) {
+    char cbuf[8], sbuf[128];
+
+    chrtos(cbuf,sizeof(cbuf),byte);
+    snprintf(sbuf,sizeof(sbuf),
+        "Protocol error, got %s as reply type byte", cbuf);
+    __redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf);
+}
+
+static void __redisReaderSetErrorOOM(redisReader *r) {
+    __redisReaderSetError(r,REDIS_ERR_OOM,"Out of memory");
+}
+
+static char *readBytes(redisReader *r, unsigned int bytes) {
+    char *p;
+    if (r->len-r->pos >= bytes) {
+        p = r->buf+r->pos;
+        r->pos += bytes;
+        return p;
+    }
+    return NULL;
+}
+
+/* Find pointer to \r\n. */
+static char *seekNewline(char *s, size_t len) {
+    int pos = 0;
+    int _len = len-1;
+
+    /* Position should be < len-1 because the character at "pos" should be
+     * followed by a \n. Note that strchr cannot be used because it doesn't
+     * allow to search a limited length and the buffer that is being searched
+     * might not have a trailing NULL character. */
+    while (pos < _len) {
+        while(pos < _len && s[pos] != '\r') pos++;
+        if (s[pos] != '\r') {
+            /* Not found. */
+            return NULL;
+        } else {
+            if (s[pos+1] == '\n') {
+                /* Found. */
+                return s+pos;
+            } else {
+                /* Continue searching. */
+                pos++;
+            }
+        }
+    }
+    return NULL;
+}
+
+/* Read a long long value starting at *s, under the assumption that it will be
+ * terminated by \r\n. Ambiguously returns -1 for unexpected input. */
+static long long readLongLong(char *s) {
+    long long v = 0;
+    int dec, mult = 1;
+    char c;
+
+    if (*s == '-') {
+        mult = -1;
+        s++;
+    } else if (*s == '+') {
+        mult = 1;
+        s++;
+    }
+
+    while ((c = *(s++)) != '\r') {
+        dec = c - '0';
+        if (dec >= 0 && dec < 10) {
+            v *= 10;
+            v += dec;
+        } else {
+            /* Should not happen... */
+            return -1;
+        }
+    }
+
+    return mult*v;
+}
+
+static char *readLine(redisReader *r, int *_len) {
+    char *p, *s;
+    int len;
+
+    p = r->buf+r->pos;
+    s = seekNewline(p,(r->len-r->pos));
+    if (s != NULL) {
+        len = s-(r->buf+r->pos);
+        r->pos += len+2; /* skip \r\n */
+        if (_len) *_len = len;
+        return p;
+    }
+    return NULL;
+}
+
+static void moveToNextTask(redisReader *r) {
+    redisReadTask *cur, *prv;
+    while (r->ridx >= 0) {
+        /* Return a.s.a.p. when the stack is now empty. */
+        if (r->ridx == 0) {
+            r->ridx--;
+            return;
+        }
+
+        cur = &(r->rstack[r->ridx]);
+        prv = &(r->rstack[r->ridx-1]);
+        assert(prv->type == REDIS_REPLY_ARRAY);
+        if (cur->idx == prv->elements-1) {
+            r->ridx--;
+        } else {
+            /* Reset the type because the next item can be anything */
+            assert(cur->idx < prv->elements);
+            cur->type = -1;
+            cur->elements = -1;
+            cur->idx++;
+            return;
+        }
+    }
+}
+
+static int processLineItem(redisReader *r) {
+    redisReadTask *cur = &(r->rstack[r->ridx]);
+    void *obj;
+    char *p;
+    int len;
+
+    if ((p = readLine(r,&len)) != NULL) {
+        if (cur->type == REDIS_REPLY_INTEGER) {
+            if (r->fn && r->fn->createInteger)
+                obj = r->fn->createInteger(cur,readLongLong(p));
+            else
+                obj = (void*)REDIS_REPLY_INTEGER;
+        } else {
+            /* Type will be error or status. */
+            if (r->fn && r->fn->createString)
+                obj = r->fn->createString(cur,p,len);
+            else
+                obj = (void*)(size_t)(cur->type);
+        }
+
+        if (obj == NULL) {
+            __redisReaderSetErrorOOM(r);
+            return REDIS_ERR;
+        }
+
+        /* Set reply if this is the root object. */
+        if (r->ridx == 0) r->reply = obj;
+        moveToNextTask(r);
+        return REDIS_OK;
+    }
+
+    return REDIS_ERR;
+}
+
+static int processBulkItem(redisReader *r) {
+    redisReadTask *cur = &(r->rstack[r->ridx]);
+    void *obj = NULL;
+    char *p, *s;
+    long len;
+    unsigned long bytelen;
+    int success = 0;
+
+    p = r->buf+r->pos;
+    s = seekNewline(p,r->len-r->pos);
+    if (s != NULL) {
+        p = r->buf+r->pos;
+        bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
+        len = readLongLong(p);
+
+        if (len < 0) {
+            /* The nil object can always be created. */
+            if (r->fn && r->fn->createNil)
+                obj = r->fn->createNil(cur);
+            else
+                obj = (void*)REDIS_REPLY_NIL;
+            success = 1;
+        } else {
+            /* Only continue when the buffer contains the entire bulk item. */
+            bytelen += len+2; /* include \r\n */
+            if (r->pos+bytelen <= r->len) {
+                if (r->fn && r->fn->createString)
+                    obj = r->fn->createString(cur,s+2,len);
+                else
+                    obj = (void*)REDIS_REPLY_STRING;
+                success = 1;
+            }
+        }
+
+        /* Proceed when obj was created. */
+        if (success) {
+            if (obj == NULL) {
+                __redisReaderSetErrorOOM(r);
+                return REDIS_ERR;
+            }
+
+            r->pos += bytelen;
+
+            /* Set reply if this is the root object. */
+            if (r->ridx == 0) r->reply = obj;
+            moveToNextTask(r);
+            return REDIS_OK;
+        }
+    }
+
+    return REDIS_ERR;
+}
+
+static int processMultiBulkItem(redisReader *r) {
+    redisReadTask *cur = &(r->rstack[r->ridx]);
+    void *obj;
+    char *p;
+    long elements;
+    int root = 0;
+
+    /* Set error for nested multi bulks with depth > 7 */
+    if (r->ridx == 8) {
+        __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+            "No support for nested multi bulk replies with depth > 7");
+        return REDIS_ERR;
+    }
+
+    if ((p = readLine(r,NULL)) != NULL) {
+        elements = readLongLong(p);
+        root = (r->ridx == 0);
+
+        if (elements == -1) {
+            if (r->fn && r->fn->createNil)
+                obj = r->fn->createNil(cur);
+            else
+                obj = (void*)REDIS_REPLY_NIL;
+
+            if (obj == NULL) {
+                __redisReaderSetErrorOOM(r);
+                return REDIS_ERR;
+            }
+
+            moveToNextTask(r);
+        } else {
+            if (r->fn && r->fn->createArray)
+                obj = r->fn->createArray(cur,elements);
+            else
+                obj = (void*)REDIS_REPLY_ARRAY;
+
+            if (obj == NULL) {
+                __redisReaderSetErrorOOM(r);
+                return REDIS_ERR;
+            }
+
+            /* Modify task stack when there are more than 0 elements. */
+            if (elements > 0) {
+                cur->elements = elements;
+                cur->obj = obj;
+                r->ridx++;
+                r->rstack[r->ridx].type = -1;
+                r->rstack[r->ridx].elements = -1;
+                r->rstack[r->ridx].idx = 0;
+                r->rstack[r->ridx].obj = NULL;
+                r->rstack[r->ridx].parent = cur;
+                r->rstack[r->ridx].privdata = r->privdata;
+            } else {
+                moveToNextTask(r);
+            }
+        }
+
+        /* Set reply if this is the root object. */
+        if (root) r->reply = obj;
+        return REDIS_OK;
+    }
+
+    return REDIS_ERR;
+}
+
+static int processItem(redisReader *r) {
+    redisReadTask *cur = &(r->rstack[r->ridx]);
+    char *p;
+
+    /* check if we need to read type */
+    if (cur->type < 0) {
+        if ((p = readBytes(r,1)) != NULL) {
+            switch (p[0]) {
+            case '-':
+                cur->type = REDIS_REPLY_ERROR;
+                break;
+            case '+':
+                cur->type = REDIS_REPLY_STATUS;
+                break;
+            case ':':
+                cur->type = REDIS_REPLY_INTEGER;
+                break;
+            case '$':
+                cur->type = REDIS_REPLY_STRING;
+                break;
+            case '*':
+                cur->type = REDIS_REPLY_ARRAY;
+                break;
+            default:
+                __redisReaderSetErrorProtocolByte(r,*p);
+                return REDIS_ERR;
+            }
+        } else {
+            /* could not consume 1 byte */
+            return REDIS_ERR;
+        }
+    }
+
+    /* process typed item */
+    switch(cur->type) {
+    case REDIS_REPLY_ERROR:
+    case REDIS_REPLY_STATUS:
+    case REDIS_REPLY_INTEGER:
+        return processLineItem(r);
+    case REDIS_REPLY_STRING:
+        return processBulkItem(r);
+    case REDIS_REPLY_ARRAY:
+        return processMultiBulkItem(r);
+    default:
+        assert(NULL);
+        return REDIS_ERR; /* Avoid warning. */
+    }
+}
+
+redisReader *redisReaderCreate(void) {
+    redisReader *r;
+
+    r = calloc(sizeof(redisReader),1);
+    if (r == NULL)
+        return NULL;
+
+    r->err = 0;
+    r->errstr[0] = '\0';
+    r->fn = &defaultFunctions;
+    r->buf = sdsempty();
+    r->maxbuf = REDIS_READER_MAX_BUF;
+    if (r->buf == NULL) {
+        free(r);
+        return NULL;
+    }
+
+    r->ridx = -1;
+    return r;
+}
+
+void redisReaderFree(redisReader *r) {
+    if (r->reply != NULL && r->fn && r->fn->freeObject)
+        r->fn->freeObject(r->reply);
+    if (r->buf != NULL)
+        sdsfree(r->buf);
+    free(r);
+}
+
+int redisReaderFeed(redisReader *r, const char *buf, size_t len) {
+    sds newbuf;
+
+    /* Return early when this reader is in an erroneous state. */
+    if (r->err)
+        return REDIS_ERR;
+
+    /* Copy the provided buffer. */
+    if (buf != NULL && len >= 1) {
+        /* Destroy internal buffer when it is empty and is quite large. */
+        if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) {
+            sdsfree(r->buf);
+            r->buf = sdsempty();
+            r->pos = 0;
+
+            /* r->buf should not be NULL since we just free'd a larger one. */
+            assert(r->buf != NULL);
+        }
+
+        newbuf = sdscatlen(r->buf,buf,len);
+        if (newbuf == NULL) {
+            __redisReaderSetErrorOOM(r);
+            return REDIS_ERR;
+        }
+
+        r->buf = newbuf;
+        r->len = sdslen(r->buf);
+    }
+
+    return REDIS_OK;
+}
+
+int redisReaderGetReply(redisReader *r, void **reply) {
+    /* Default target pointer to NULL. */
+    if (reply != NULL)
+        *reply = NULL;
+
+    /* Return early when this reader is in an erroneous state. */
+    if (r->err)
+        return REDIS_ERR;
+
+    /* When the buffer is empty, there will never be a reply. */
+    if (r->len == 0)
+        return REDIS_OK;
+
+    /* Set first item to process when the stack is empty. */
+    if (r->ridx == -1) {
+        r->rstack[0].type = -1;
+        r->rstack[0].elements = -1;
+        r->rstack[0].idx = -1;
+        r->rstack[0].obj = NULL;
+        r->rstack[0].parent = NULL;
+        r->rstack[0].privdata = r->privdata;
+        r->ridx = 0;
+    }
+
+    /* Process items in reply. */
+    while (r->ridx >= 0)
+        if (processItem(r) != REDIS_OK)
+            break;
+
+    /* Return ASAP when an error occurred. */
+    if (r->err)
+        return REDIS_ERR;
+
+    /* Discard part of the buffer when we've consumed at least 1k, to avoid
+     * doing unnecessary calls to memmove() in sds.c. */
+    if (r->pos >= 1024) {
+        r->buf = sdsrange(r->buf,r->pos,-1);
+        r->pos = 0;
+        r->len = sdslen(r->buf);
+    }
+
+    /* Emit a reply when there is one. */
+    if (r->ridx == -1) {
+        if (reply != NULL)
+            *reply = r->reply;
+        r->reply = NULL;
+    }
+    return REDIS_OK;
+}
+
+/* Calculate the number of bytes needed to represent an integer as string. */
+static int intlen(int i) {
+    int len = 0;
+    if (i < 0) {
+        len++;
+        i = -i;
+    }
+    do {
+        len++;
+        i /= 10;
+    } while(i);
+    return len;
+}
+
+/* Helper that calculates the bulk length given a certain string length. */
+static size_t bulklen(size_t len) {
+    return 1+intlen(len)+2+len+2;
+}
+
+int redisvFormatCommand(char **target, const char *format, va_list ap) {
+    const char *c = format;
+    char *cmd = NULL; /* final command */
+    int pos; /* position in final command */
+    sds curarg, newarg; /* current argument */
+    int touched = 0; /* was the current argument touched? */
+    char **curargv = NULL, **newargv = NULL;
+    int argc = 0;
+    int totlen = 0;
+    int j;
+
+    /* Abort if there is not target to set */
+    if (target == NULL)
+        return -1;
+
+    /* Build the command string accordingly to protocol */
+    curarg = sdsempty();
+    if (curarg == NULL)
+        return -1;
+
+    while(*c != '\0') {
+        if (*c != '%' || c[1] == '\0') {
+            if (*c == ' ') {
+                if (touched) {
+                    newargv = realloc(curargv,sizeof(char*)*(argc+1));
+                    if (newargv == NULL) goto err;
+                    curargv = newargv;
+                    curargv[argc++] = curarg;
+                    totlen += bulklen(sdslen(curarg));
+
+                    /* curarg is put in argv so it can be overwritten. */
+                    curarg = sdsempty();
+                    if (curarg == NULL) goto err;
+                    touched = 0;
+                }
+            } else {
+                newarg = sdscatlen(curarg,c,1);
+                if (newarg == NULL) goto err;
+                curarg = newarg;
+                touched = 1;
+            }
+        } else {
+            char *arg;
+            size_t size;
+
+            /* Set newarg so it can be checked even if it is not touched. */
+            newarg = curarg;
+
+            switch(c[1]) {
+            case 's':
+                arg = va_arg(ap,char*);
+                size = strlen(arg);
+                if (size > 0)
+                    newarg = sdscatlen(curarg,arg,size);
+                break;
+            case 'b':
+                arg = va_arg(ap,char*);
+                size = va_arg(ap,size_t);
+                if (size > 0)
+                    newarg = sdscatlen(curarg,arg,size);
+                break;
+            case '%':
+                newarg = sdscat(curarg,"%");
+                break;
+            default:
+                /* Try to detect printf format */
+                {
+                    static const char intfmts[] = "diouxX";
+                    char _format[16];
+                    const char *_p = c+1;
+                    size_t _l = 0;
+                    va_list _cpy;
+
+                    /* Flags */
+                    if (*_p != '\0' && *_p == '#') _p++;
+                    if (*_p != '\0' && *_p == '0') _p++;
+                    if (*_p != '\0' && *_p == '-') _p++;
+                    if (*_p != '\0' && *_p == ' ') _p++;
+                    if (*_p != '\0' && *_p == '+') _p++;
+
+                    /* Field width */
+                    while (*_p != '\0' && isdigit(*_p)) _p++;
+
+                    /* Precision */
+                    if (*_p == '.') {
+                        _p++;
+                        while (*_p != '\0' && isdigit(*_p)) _p++;
+                    }
+
+                    /* Copy va_list before consuming with va_arg */
+                    va_copy(_cpy,ap);
+
+                    /* Integer conversion (without modifiers) */
+                    if (strchr(intfmts,*_p) != NULL) {
+                        va_arg(ap,int);
+                        goto fmt_valid;
+                    }
+
+                    /* Double conversion (without modifiers) */
+                    if (strchr("eEfFgGaA",*_p) != NULL) {
+                        va_arg(ap,double);
+                        goto fmt_valid;
+                    }
+
+                    /* Size: char */
+                    if (_p[0] == 'h' && _p[1] == 'h') {
+                        _p += 2;
+                        if (*_p != '\0' && strchr(intfmts,*_p) != NULL) {
+                            va_arg(ap,int); /* char gets promoted to int */
+                            goto fmt_valid;
+                        }
+                        goto fmt_invalid;
+                    }
+
+                    /* Size: short */
+                    if (_p[0] == 'h') {
+                        _p += 1;
+                        if (*_p != '\0' && strchr(intfmts,*_p) != NULL) {
+                            va_arg(ap,int); /* short gets promoted to int */
+                            goto fmt_valid;
+                        }
+                        goto fmt_invalid;
+                    }
+
+                    /* Size: long long */
+                    if (_p[0] == 'l' && _p[1] == 'l') {
+                        _p += 2;
+                        if (*_p != '\0' && strchr(intfmts,*_p) != NULL) {
+                            va_arg(ap,long long);
+                            goto fmt_valid;
+                        }
+                        goto fmt_invalid;
+                    }
+
+                    /* Size: long */
+                    if (_p[0] == 'l') {
+                        _p += 1;
+                        if (*_p != '\0' && strchr(intfmts,*_p) != NULL) {
+                            va_arg(ap,long);
+                            goto fmt_valid;
+                        }
+                        goto fmt_invalid;
+                    }
+
+                fmt_invalid:
+                    va_end(_cpy);
+                    goto err;
+
+                fmt_valid:
+                    _l = (_p+1)-c;
+                    if (_l < sizeof(_format)-2) {
+                        memcpy(_format,c,_l);
+                        _format[_l] = '\0';
+                        newarg = sdscatvprintf(curarg,_format,_cpy);
+
+                        /* Update current position (note: outer blocks
+                         * increment c twice so compensate here) */
+                        c = _p-1;
+                    }
+
+                    va_end(_cpy);
+                    break;
+                }
+            }
+
+            if (newarg == NULL) goto err;
+            curarg = newarg;
+
+            touched = 1;
+            c++;
+        }
+        c++;
+    }
+
+    /* Add the last argument if needed */
+    if (touched) {
+        newargv = realloc(curargv,sizeof(char*)*(argc+1));
+        if (newargv == NULL) goto err;
+        curargv = newargv;
+        curargv[argc++] = curarg;
+        totlen += bulklen(sdslen(curarg));
+    } else {
+        sdsfree(curarg);
+    }
+
+    /* Clear curarg because it was put in curargv or was free'd. */
+    curarg = NULL;
+
+    /* Add bytes needed to hold multi bulk count */
+    totlen += 1+intlen(argc)+2;
+
+    /* Build the command at protocol level */
+    cmd = malloc(totlen+1);
+    if (cmd == NULL) goto err;
+
+    pos = sprintf(cmd,"*%d\r\n",argc);
+    for (j = 0; j < argc; j++) {
+        pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(curargv[j]));
+        memcpy(cmd+pos,curargv[j],sdslen(curargv[j]));
+        pos += sdslen(curargv[j]);
+        sdsfree(curargv[j]);
+        cmd[pos++] = '\r';
+        cmd[pos++] = '\n';
+    }
+    assert(pos == totlen);
+    cmd[pos] = '\0';
+
+    free(curargv);
+    *target = cmd;
+    return totlen;
+
+err:
+    while(argc--)
+        sdsfree(curargv[argc]);
+    free(curargv);
+
+    if (curarg != NULL)
+        sdsfree(curarg);
+
+    /* No need to check cmd since it is the last statement that can fail,
+     * but do it anyway to be as defensive as possible. */
+    if (cmd != NULL)
+        free(cmd);
+
+    return -1;
+}
+
+/* Format a command according to the Redis protocol. This function
+ * takes a format similar to printf:
+ *
+ * %s represents a C null terminated string you want to interpolate
+ * %b represents a binary safe string
+ *
+ * When using %b you need to provide both the pointer to the string
+ * and the length in bytes. Examples:
+ *
+ * len = redisFormatCommand(target, "GET %s", mykey);
+ * len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen);
+ */
+int redisFormatCommand(char **target, const char *format, ...) {
+    va_list ap;
+    int len;
+    va_start(ap,format);
+    len = redisvFormatCommand(target,format,ap);
+    va_end(ap);
+    return len;
+}
+
+/* Format a command according to the Redis protocol. This function takes the
+ * number of arguments, an array with arguments and an array with their
+ * lengths. If the latter is set to NULL, strlen will be used to compute the
+ * argument lengths.
+ */
+int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) {
+    char *cmd = NULL; /* final command */
+    int pos; /* position in final command */
+    size_t len;
+    int totlen, j;
+
+    /* Calculate number of bytes needed for the command */
+    totlen = 1+intlen(argc)+2;
+    for (j = 0; j < argc; j++) {
+        len = argvlen ? argvlen[j] : strlen(argv[j]);
+        totlen += bulklen(len);
+    }
+
+    /* Build the command at protocol level */
+    cmd = malloc(totlen+1);
+    if (cmd == NULL)
+        return -1;
+
+    pos = sprintf(cmd,"*%d\r\n",argc);
+    for (j = 0; j < argc; j++) {
+        len = argvlen ? argvlen[j] : strlen(argv[j]);
+        pos += sprintf(cmd+pos,"$%zu\r\n",len);
+        memcpy(cmd+pos,argv[j],len);
+        pos += len;
+        cmd[pos++] = '\r';
+        cmd[pos++] = '\n';
+    }
+    assert(pos == totlen);
+    cmd[pos] = '\0';
+
+    *target = cmd;
+    return totlen;
+}
+
+void __redisSetError(redisContext *c, int type, const char *str) {
+    size_t len;
+
+    c->err = type;
+    if (str != NULL) {
+        len = strlen(str);
+        len = len < (sizeof(c->errstr)-1) ? len : (sizeof(c->errstr)-1);
+        memcpy(c->errstr,str,len);
+        c->errstr[len] = '\0';
+    } else {
+        /* Only REDIS_ERR_IO may lack a description! */
+        assert(type == REDIS_ERR_IO);
+        strerror_r(errno,c->errstr,sizeof(c->errstr));
+    }
+}
+
+static redisContext *redisContextInit(void) {
+    redisContext *c;
+
+    c = calloc(1,sizeof(redisContext));
+    if (c == NULL)
+        return NULL;
+
+    c->err = 0;
+    c->errstr[0] = '\0';
+    c->obuf = sdsempty();
+    c->reader = redisReaderCreate();
+    return c;
+}
+
+void redisFree(redisContext *c) {
+    if (c->fd > 0)
+        close(c->fd);
+    if (c->obuf != NULL)
+        sdsfree(c->obuf);
+    if (c->reader != NULL)
+        redisReaderFree(c->reader);
+    free(c);
+}
+
+/* Connect to a Redis instance. On error the field error in the returned
+ * context will be set to the return value of the error function.
+ * When no set of reply functions is given, the default set will be used. */
+redisContext *redisConnect(const char *ip, int port) {
+    redisContext *c;
+
+    c = redisContextInit();
+    if (c == NULL)
+        return NULL;
+
+    c->flags |= REDIS_BLOCK;
+    redisContextConnectTcp(c,ip,port,NULL);
+    return c;
+}
+
+redisContext *redisConnectWithTimeout(const char *ip, int port, struct timeval tv) {
+    redisContext *c;
+
+    c = redisContextInit();
+    if (c == NULL)
+        return NULL;
+
+    c->flags |= REDIS_BLOCK;
+    redisContextConnectTcp(c,ip,port,&tv);
+    return c;
+}
+
+redisContext *redisConnectNonBlock(const char *ip, int port) {
+    redisContext *c;
+
+    c = redisContextInit();
+    if (c == NULL)
+        return NULL;
+
+    c->flags &= ~REDIS_BLOCK;
+    redisContextConnectTcp(c,ip,port,NULL);
+    return c;
+}
+
+redisContext *redisConnectUnix(const char *path) {
+    redisContext *c;
+
+    c = redisContextInit();
+    if (c == NULL)
+        return NULL;
+
+    c->flags |= REDIS_BLOCK;
+    redisContextConnectUnix(c,path,NULL);
+    return c;
+}
+
+redisContext *redisConnectUnixWithTimeout(const char *path, struct timeval tv) {
+    redisContext *c;
+
+    c = redisContextInit();
+    if (c == NULL)
+        return NULL;
+
+    c->flags |= REDIS_BLOCK;
+    redisContextConnectUnix(c,path,&tv);
+    return c;
+}
+
+redisContext *redisConnectUnixNonBlock(const char *path) {
+    redisContext *c;
+
+    c = redisContextInit();
+    if (c == NULL)
+        return NULL;
+
+    c->flags &= ~REDIS_BLOCK;
+    redisContextConnectUnix(c,path,NULL);
+    return c;
+}
+
+/* Set read/write timeout on a blocking socket. */
+int redisSetTimeout(redisContext *c, struct timeval tv) {
+    if (c->flags & REDIS_BLOCK)
+        return redisContextSetTimeout(c,tv);
+    return REDIS_ERR;
+}
+
+/* Use this function to handle a read event on the descriptor. It will try
+ * and read some bytes from the socket and feed them to the reply parser.
+ *
+ * After this function is called, you may use redisContextReadReply to
+ * see if there is a reply available. */
+int redisBufferRead(redisContext *c) {
+    char buf[1024*16];
+    int nread;
+
+    /* Return early when the context has seen an error. */
+    if (c->err)
+        return REDIS_ERR;
+
+    nread = read(c->fd,buf,sizeof(buf));
+    if (nread == -1) {
+        if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
+            /* Try again later */
+        } else {
+            __redisSetError(c,REDIS_ERR_IO,NULL);
+            return REDIS_ERR;
+        }
+    } else if (nread == 0) {
+        __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection");
+        return REDIS_ERR;
+    } else {
+        if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) {
+            __redisSetError(c,c->reader->err,c->reader->errstr);
+            return REDIS_ERR;
+        }
+    }
+    return REDIS_OK;
+}
+
+/* Write the output buffer to the socket.
+ *
+ * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was
+ * succesfully written to the socket. When the buffer is empty after the
+ * write operation, "done" is set to 1 (if given).
+ *
+ * Returns REDIS_ERR if an error occured trying to write and sets
+ * c->errstr to hold the appropriate error string.
+ */
+int redisBufferWrite(redisContext *c, int *done) {
+    int nwritten;
+
+    /* Return early when the context has seen an error. */
+    if (c->err)
+        return REDIS_ERR;
+
+    if (sdslen(c->obuf) > 0) {
+        nwritten = write(c->fd,c->obuf,sdslen(c->obuf));
+        if (nwritten == -1) {
+            if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
+                /* Try again later */
+            } else {
+                __redisSetError(c,REDIS_ERR_IO,NULL);
+                return REDIS_ERR;
+            }
+        } else if (nwritten > 0) {
+            if (nwritten == (signed)sdslen(c->obuf)) {
+                sdsfree(c->obuf);
+                c->obuf = sdsempty();
+            } else {
+                c->obuf = sdsrange(c->obuf,nwritten,-1);
+            }
+        }
+    }
+    if (done != NULL) *done = (sdslen(c->obuf) == 0);
+    return REDIS_OK;
+}
+
+/* Internal helper function to try and get a reply from the reader,
+ * or set an error in the context otherwise. */
+int redisGetReplyFromReader(redisContext *c, void **reply) {
+    if (redisReaderGetReply(c->reader,reply) == REDIS_ERR) {
+        __redisSetError(c,c->reader->err,c->reader->errstr);
+        return REDIS_ERR;
+    }
+    return REDIS_OK;
+}
+
+int redisGetReply(redisContext *c, void **reply) {
+    int wdone = 0;
+    void *aux = NULL;
+
+    /* Try to read pending replies */
+    if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)
+        return REDIS_ERR;
+
+    /* For the blocking context, flush output buffer and read reply */
+    if (aux == NULL && c->flags & REDIS_BLOCK) {
+        /* Write until done */
+        do {
+            if (redisBufferWrite(c,&wdone) == REDIS_ERR)
+                return REDIS_ERR;
+        } while (!wdone);
+
+        /* Read until there is a reply */
+        do {
+            if (redisBufferRead(c) == REDIS_ERR)
+                return REDIS_ERR;
+            if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)
+                return REDIS_ERR;
+        } while (aux == NULL);
+    }
+
+    /* Set reply object */
+    if (reply != NULL) *reply = aux;
+    return REDIS_OK;
+}
+
+
+/* Helper function for the redisAppendCommand* family of functions.
+ *
+ * Write a formatted command to the output buffer. When this family
+ * is used, you need to call redisGetReply yourself to retrieve
+ * the reply (or replies in pub/sub).
+ */
+int __redisAppendCommand(redisContext *c, char *cmd, size_t len) {
+    sds newbuf;
+
+    newbuf = sdscatlen(c->obuf,cmd,len);
+    if (newbuf == NULL) {
+        __redisSetError(c,REDIS_ERR_OOM,"Out of memory");
+        return REDIS_ERR;
+    }
+
+    c->obuf = newbuf;
+    return REDIS_OK;
+}
+
+int redisvAppendCommand(redisContext *c, const char *format, va_list ap) {
+    char *cmd;
+    int len;
+
+    len = redisvFormatCommand(&cmd,format,ap);
+    if (len == -1) {
+        __redisSetError(c,REDIS_ERR_OOM,"Out of memory");
+        return REDIS_ERR;
+    }
+
+    if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
+        free(cmd);
+        return REDIS_ERR;
+    }
+
+    free(cmd);
+    return REDIS_OK;
+}
+
+int redisAppendCommand(redisContext *c, const char *format, ...) {
+    va_list ap;
+    int ret;
+
+    va_start(ap,format);
+    ret = redisvAppendCommand(c,format,ap);
+    va_end(ap);
+    return ret;
+}
+
+int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) {
+    char *cmd;
+    int len;
+
+    len = redisFormatCommandArgv(&cmd,argc,argv,argvlen);
+    if (len == -1) {
+        __redisSetError(c,REDIS_ERR_OOM,"Out of memory");
+        return REDIS_ERR;
+    }
+
+    if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
+        free(cmd);
+        return REDIS_ERR;
+    }
+
+    free(cmd);
+    return REDIS_OK;
+}
+
+/* Helper function for the redisCommand* family of functions.
+ *
+ * Write a formatted command to the output buffer. If the given context is
+ * blocking, immediately read the reply into the "reply" pointer. When the
+ * context is non-blocking, the "reply" pointer will not be used and the
+ * command is simply appended to the write buffer.
+ *
+ * Returns the reply when a reply was succesfully retrieved. Returns NULL
+ * otherwise. When NULL is returned in a blocking context, the error field
+ * in the context will be set.
+ */
+static void *__redisBlockForReply(redisContext *c) {
+    void *reply;
+
+    if (c->flags & REDIS_BLOCK) {
+        if (redisGetReply(c,&reply) != REDIS_OK)
+            return NULL;
+        return reply;
+    }
+    return NULL;
+}
+
+void *redisvCommand(redisContext *c, const char *format, va_list ap) {
+    if (redisvAppendCommand(c,format,ap) != REDIS_OK)
+        return NULL;
+    return __redisBlockForReply(c);
+}
+
+void *redisCommand(redisContext *c, const char *format, ...) {
+    va_list ap;
+    void *reply = NULL;
+    va_start(ap,format);
+    reply = redisvCommand(c,format,ap);
+    va_end(ap);
+    return reply;
+}
+
+void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) {
+    if (redisAppendCommandArgv(c,argc,argv,argvlen) != REDIS_OK)
+        return NULL;
+    return __redisBlockForReply(c);
+}
diff --git a/hiredis/hiredis.h b/hiredis/hiredis.h
new file mode 100644 (file)
index 0000000..aadcf35
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __HIREDIS_H
+#define __HIREDIS_H
+#include <stdio.h> /* for size_t */
+#include <stdarg.h> /* for va_list */
+#include <sys/time.h> /* for struct timeval */
+
+#define HIREDIS_MAJOR 0
+#define HIREDIS_MINOR 11
+#define HIREDIS_PATCH 0
+
+#define REDIS_ERR -1
+#define REDIS_OK 0
+
+/* When an error occurs, the err flag in a context is set to hold the type of
+ * error that occured. REDIS_ERR_IO means there was an I/O error and you
+ * should use the "errno" variable to find out what is wrong.
+ * For other values, the "errstr" field will hold a description. */
+#define REDIS_ERR_IO 1 /* Error in read or write */
+#define REDIS_ERR_EOF 3 /* End of file */
+#define REDIS_ERR_PROTOCOL 4 /* Protocol error */
+#define REDIS_ERR_OOM 5 /* Out of memory */
+#define REDIS_ERR_OTHER 2 /* Everything else... */
+
+/* Connection type can be blocking or non-blocking and is set in the
+ * least significant bit of the flags field in redisContext. */
+#define REDIS_BLOCK 0x1
+
+/* Connection may be disconnected before being free'd. The second bit
+ * in the flags field is set when the context is connected. */
+#define REDIS_CONNECTED 0x2
+
+/* The async API might try to disconnect cleanly and flush the output
+ * buffer and read all subsequent replies before disconnecting.
+ * This flag means no new commands can come in and the connection
+ * should be terminated once all replies have been read. */
+#define REDIS_DISCONNECTING 0x4
+
+/* Flag specific to the async API which means that the context should be clean
+ * up as soon as possible. */
+#define REDIS_FREEING 0x8
+
+/* Flag that is set when an async callback is executed. */
+#define REDIS_IN_CALLBACK 0x10
+
+/* Flag that is set when the async context has one or more subscriptions. */
+#define REDIS_SUBSCRIBED 0x20
+
+/* Flag that is set when monitor mode is active */
+#define REDIS_MONITORING 0x40
+
+#define REDIS_REPLY_STRING 1
+#define REDIS_REPLY_ARRAY 2
+#define REDIS_REPLY_INTEGER 3
+#define REDIS_REPLY_NIL 4
+#define REDIS_REPLY_STATUS 5
+#define REDIS_REPLY_ERROR 6
+
+#define REDIS_READER_MAX_BUF (1024*16)  /* Default max unused reader buffer. */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This is the reply object returned by redisCommand() */
+typedef struct redisReply {
+    int type; /* REDIS_REPLY_* */
+    long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
+    int len; /* Length of string */
+    char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */
+    size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
+    struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
+} redisReply;
+
+typedef struct redisReadTask {
+    int type;
+    int elements; /* number of elements in multibulk container */
+    int idx; /* index in parent (array) object */
+    void *obj; /* holds user-generated value for a read task */
+    struct redisReadTask *parent; /* parent task */
+    void *privdata; /* user-settable arbitrary field */
+} redisReadTask;
+
+typedef struct redisReplyObjectFunctions {
+    void *(*createString)(const redisReadTask*, char*, size_t);
+    void *(*createArray)(const redisReadTask*, int);
+    void *(*createInteger)(const redisReadTask*, long long);
+    void *(*createNil)(const redisReadTask*);
+    void (*freeObject)(void*);
+} redisReplyObjectFunctions;
+
+/* State for the protocol parser */
+typedef struct redisReader {
+    int err; /* Error flags, 0 when there is no error */
+    char errstr[128]; /* String representation of error when applicable */
+
+    char *buf; /* Read buffer */
+    size_t pos; /* Buffer cursor */
+    size_t len; /* Buffer length */
+    size_t maxbuf; /* Max length of unused buffer */
+
+    redisReadTask rstack[9];
+    int ridx; /* Index of current read task */
+    void *reply; /* Temporary reply pointer */
+
+    redisReplyObjectFunctions *fn;
+    void *privdata;
+} redisReader;
+
+/* Public API for the protocol parser. */
+redisReader *redisReaderCreate(void);
+void redisReaderFree(redisReader *r);
+int redisReaderFeed(redisReader *r, const char *buf, size_t len);
+int redisReaderGetReply(redisReader *r, void **reply);
+
+/* Backwards compatibility, can be removed on big version bump. */
+#define redisReplyReaderCreate redisReaderCreate
+#define redisReplyReaderFree redisReaderFree
+#define redisReplyReaderFeed redisReaderFeed
+#define redisReplyReaderGetReply redisReaderGetReply
+#define redisReplyReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p))
+#define redisReplyReaderGetObject(_r) (((redisReader*)(_r))->reply)
+#define redisReplyReaderGetError(_r) (((redisReader*)(_r))->errstr)
+
+/* Function to free the reply objects hiredis returns by default. */
+void freeReplyObject(void *reply);
+
+/* Functions to format a command according to the protocol. */
+int redisvFormatCommand(char **target, const char *format, va_list ap);
+int redisFormatCommand(char **target, const char *format, ...);
+int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen);
+
+/* Context for a connection to Redis */
+typedef struct redisContext {
+    int err; /* Error flags, 0 when there is no error */
+    char errstr[128]; /* String representation of error when applicable */
+    int fd;
+    int flags;
+    char *obuf; /* Write buffer */
+    redisReader *reader; /* Protocol reader */
+} redisContext;
+
+redisContext *redisConnect(const char *ip, int port);
+redisContext *redisConnectWithTimeout(const char *ip, int port, struct timeval tv);
+redisContext *redisConnectNonBlock(const char *ip, int port);
+redisContext *redisConnectUnix(const char *path);
+redisContext *redisConnectUnixWithTimeout(const char *path, struct timeval tv);
+redisContext *redisConnectUnixNonBlock(const char *path);
+int redisSetTimeout(redisContext *c, struct timeval tv);
+void redisFree(redisContext *c);
+int redisBufferRead(redisContext *c);
+int redisBufferWrite(redisContext *c, int *done);
+
+/* In a blocking context, this function first checks if there are unconsumed
+ * replies to return and returns one if so. Otherwise, it flushes the output
+ * buffer to the socket and reads until it has a reply. In a non-blocking
+ * context, it will return unconsumed replies until there are no more. */
+int redisGetReply(redisContext *c, void **reply);
+int redisGetReplyFromReader(redisContext *c, void **reply);
+
+/* Write a command to the output buffer. Use these functions in blocking mode
+ * to get a pipeline of commands. */
+int redisvAppendCommand(redisContext *c, const char *format, va_list ap);
+int redisAppendCommand(redisContext *c, const char *format, ...);
+int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
+
+/* Issue a command to Redis. In a blocking context, it is identical to calling
+ * redisAppendCommand, followed by redisGetReply. The function will return
+ * NULL if there was an error in performing the request, otherwise it will
+ * return the reply. In a non-blocking context, it is identical to calling
+ * only redisAppendCommand and will always return NULL. */
+void *redisvCommand(redisContext *c, const char *format, va_list ap);
+void *redisCommand(redisContext *c, const char *format, ...);
+void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/hiredis/net.c b/hiredis/net.c
new file mode 100644 (file)
index 0000000..82ab2b4
--- /dev/null
@@ -0,0 +1,291 @@
+/* Extracted from anet.c to work properly with Hiredis error reporting.
+ *
+ * Copyright (c) 2006-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "fmacros.h"
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <netdb.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <poll.h>
+#include <limits.h>
+
+#include "net.h"
+#include "sds.h"
+
+/* Defined in hiredis.c */
+void __redisSetError(redisContext *c, int type, const char *str);
+
+static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {
+    char buf[128];
+    size_t len = 0;
+
+    if (prefix != NULL)
+        len = snprintf(buf,sizeof(buf),"%s: ",prefix);
+    strerror_r(errno,buf+len,sizeof(buf)-len);
+    __redisSetError(c,type,buf);
+}
+
+static int redisSetReuseAddr(redisContext *c, int fd) {
+    int on = 1;
+    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
+        __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
+        close(fd);
+        return REDIS_ERR;
+    }
+    return REDIS_OK;
+}
+
+static int redisCreateSocket(redisContext *c, int type) {
+    int s;
+    if ((s = socket(type, SOCK_STREAM, 0)) == -1) {
+        __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
+        return REDIS_ERR;
+    }
+    if (type == AF_INET) {
+        if (redisSetReuseAddr(c,s) == REDIS_ERR) {
+            return REDIS_ERR;
+        }
+    }
+    return s;
+}
+
+static int redisSetBlocking(redisContext *c, int fd, int blocking) {
+    int flags;
+
+    /* Set the socket nonblocking.
+     * Note that fcntl(2) for F_GETFL and F_SETFL can't be
+     * interrupted by a signal. */
+    if ((flags = fcntl(fd, F_GETFL)) == -1) {
+        __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)");
+        close(fd);
+        return REDIS_ERR;
+    }
+
+    if (blocking)
+        flags &= ~O_NONBLOCK;
+    else
+        flags |= O_NONBLOCK;
+
+    if (fcntl(fd, F_SETFL, flags) == -1) {
+        __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)");
+        close(fd);
+        return REDIS_ERR;
+    }
+    return REDIS_OK;
+}
+
+static int redisSetTcpNoDelay(redisContext *c, int fd) {
+    int yes = 1;
+    if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
+        __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
+        close(fd);
+        return REDIS_ERR;
+    }
+    return REDIS_OK;
+}
+
+#define __MAX_MSEC (((LONG_MAX) - 999) / 1000)
+
+static int redisContextWaitReady(redisContext *c, int fd, const struct timeval *timeout) {
+    struct pollfd   wfd[1];
+    long msec;
+
+    msec          = -1;
+    wfd[0].fd     = fd;
+    wfd[0].events = POLLOUT;
+
+    /* Only use timeout when not NULL. */
+    if (timeout != NULL) {
+        if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) {
+            close(fd);
+            return REDIS_ERR;
+        }
+
+        msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000);
+
+        if (msec < 0 || msec > INT_MAX) {
+            msec = INT_MAX;
+        }
+    }
+
+    if (errno == EINPROGRESS) {
+        int res;
+
+        if ((res = poll(wfd, 1, msec)) == -1) {
+            __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
+            close(fd);
+            return REDIS_ERR;
+        } else if (res == 0) {
+            errno = ETIMEDOUT;
+            __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
+            close(fd);
+            return REDIS_ERR;
+        }
+
+        if (redisCheckSocketError(c, fd) != REDIS_OK)
+            return REDIS_ERR;
+
+        return REDIS_OK;
+    }
+
+    __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
+    close(fd);
+    return REDIS_ERR;
+}
+
+int redisCheckSocketError(redisContext *c, int fd) {
+    int err = 0;
+    socklen_t errlen = sizeof(err);
+
+    if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
+        __redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)");
+        close(fd);
+        return REDIS_ERR;
+    }
+
+    if (err) {
+        errno = err;
+        __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
+        close(fd);
+        return REDIS_ERR;
+    }
+
+    return REDIS_OK;
+}
+
+int redisContextSetTimeout(redisContext *c, struct timeval tv) {
+    if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) {
+        __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
+        return REDIS_ERR;
+    }
+    if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) {
+        __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)");
+        return REDIS_ERR;
+    }
+    return REDIS_OK;
+}
+
+int redisContextConnectTcp(redisContext *c, const char *addr, int port, struct timeval *timeout) {
+    int s, rv;
+    char _port[6];  /* strlen("65535"); */
+    struct addrinfo hints, *servinfo, *p;
+    int blocking = (c->flags & REDIS_BLOCK);
+
+    snprintf(_port, 6, "%d", port);
+    memset(&hints,0,sizeof(hints));
+    hints.ai_family = AF_INET;
+    hints.ai_socktype = SOCK_STREAM;
+
+    if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
+        __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv));
+        return REDIS_ERR;
+    }
+    for (p = servinfo; p != NULL; p = p->ai_next) {
+        if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
+            continue;
+
+        if (redisSetBlocking(c,s,0) != REDIS_OK)
+            goto error;
+        if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
+            if (errno == EHOSTUNREACH) {
+                close(s);
+                continue;
+            } else if (errno == EINPROGRESS && !blocking) {
+                /* This is ok. */
+            } else {
+                if (redisContextWaitReady(c,s,timeout) != REDIS_OK)
+                    goto error;
+            }
+        }
+        if (blocking && redisSetBlocking(c,s,1) != REDIS_OK)
+            goto error;
+        if (redisSetTcpNoDelay(c,s) != REDIS_OK)
+            goto error;
+
+        c->fd = s;
+        c->flags |= REDIS_CONNECTED;
+        rv = REDIS_OK;
+        goto end;
+    }
+    if (p == NULL) {
+        char buf[128];
+        snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno));
+        __redisSetError(c,REDIS_ERR_OTHER,buf);
+        goto error;
+    }
+
+error:
+    rv = REDIS_ERR;
+end:
+    freeaddrinfo(servinfo);
+    return rv;  // Need to return REDIS_OK if alright
+}
+
+int redisContextConnectUnix(redisContext *c, const char *path, struct timeval *timeout) {
+    int s;
+    int blocking = (c->flags & REDIS_BLOCK);
+    struct sockaddr_un sa;
+
+    if ((s = redisCreateSocket(c,AF_LOCAL)) < 0)
+        return REDIS_ERR;
+    if (redisSetBlocking(c,s,0) != REDIS_OK)
+        return REDIS_ERR;
+
+    sa.sun_family = AF_LOCAL;
+    strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
+    if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
+        if (errno == EINPROGRESS && !blocking) {
+            /* This is ok. */
+        } else {
+            if (redisContextWaitReady(c,s,timeout) != REDIS_OK)
+                return REDIS_ERR;
+        }
+    }
+
+    /* Reset socket to be blocking after connect(2). */
+    if (blocking && redisSetBlocking(c,s,1) != REDIS_OK)
+        return REDIS_ERR;
+
+    c->fd = s;
+    c->flags |= REDIS_CONNECTED;
+    return REDIS_OK;
+}
diff --git a/hiredis/net.h b/hiredis/net.h
new file mode 100644 (file)
index 0000000..eb8a0a1
--- /dev/null
@@ -0,0 +1,47 @@
+/* Extracted from anet.c to work properly with Hiredis error reporting.
+ *
+ * Copyright (c) 2006-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __NET_H
+#define __NET_H
+
+#include "hiredis.h"
+
+#if defined(__sun)
+#define AF_LOCAL AF_UNIX
+#endif
+
+int redisCheckSocketError(redisContext *c, int fd);
+int redisContextSetTimeout(redisContext *c, struct timeval tv);
+int redisContextConnectTcp(redisContext *c, const char *addr, int port, struct timeval *timeout);
+int redisContextConnectUnix(redisContext *c, const char *path, struct timeval *timeout);
+
+#endif
diff --git a/hiredis/sds.c b/hiredis/sds.c
new file mode 100644 (file)
index 0000000..0af9c67
--- /dev/null
@@ -0,0 +1,605 @@
+/* SDSLib, A C dynamic strings library
+ *
+ * Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include "sds.h"
+
+#ifdef SDS_ABORT_ON_OOM
+static void sdsOomAbort(void) {
+    fprintf(stderr,"SDS: Out Of Memory (SDS_ABORT_ON_OOM defined)\n");
+    abort();
+}
+#endif
+
+sds sdsnewlen(const void *init, size_t initlen) {
+    struct sdshdr *sh;
+
+    sh = malloc(sizeof(struct sdshdr)+initlen+1);
+#ifdef SDS_ABORT_ON_OOM
+    if (sh == NULL) sdsOomAbort();
+#else
+    if (sh == NULL) return NULL;
+#endif
+    sh->len = initlen;
+    sh->free = 0;
+    if (initlen) {
+        if (init) memcpy(sh->buf, init, initlen);
+        else memset(sh->buf,0,initlen);
+    }
+    sh->buf[initlen] = '\0';
+    return (char*)sh->buf;
+}
+
+sds sdsempty(void) {
+    return sdsnewlen("",0);
+}
+
+sds sdsnew(const char *init) {
+    size_t initlen = (init == NULL) ? 0 : strlen(init);
+    return sdsnewlen(init, initlen);
+}
+
+sds sdsdup(const sds s) {
+    return sdsnewlen(s, sdslen(s));
+}
+
+void sdsfree(sds s) {
+    if (s == NULL) return;
+    free(s-sizeof(struct sdshdr));
+}
+
+void sdsupdatelen(sds s) {
+    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
+    int reallen = strlen(s);
+    sh->free += (sh->len-reallen);
+    sh->len = reallen;
+}
+
+static sds sdsMakeRoomFor(sds s, size_t addlen) {
+    struct sdshdr *sh, *newsh;
+    size_t free = sdsavail(s);
+    size_t len, newlen;
+
+    if (free >= addlen) return s;
+    len = sdslen(s);
+    sh = (void*) (s-(sizeof(struct sdshdr)));
+    newlen = (len+addlen)*2;
+    newsh = realloc(sh, sizeof(struct sdshdr)+newlen+1);
+#ifdef SDS_ABORT_ON_OOM
+    if (newsh == NULL) sdsOomAbort();
+#else
+    if (newsh == NULL) return NULL;
+#endif
+
+    newsh->free = newlen - len;
+    return newsh->buf;
+}
+
+/* Grow the sds to have the specified length. Bytes that were not part of
+ * the original length of the sds will be set to zero. */
+sds sdsgrowzero(sds s, size_t len) {
+    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
+    size_t totlen, curlen = sh->len;
+
+    if (len <= curlen) return s;
+    s = sdsMakeRoomFor(s,len-curlen);
+    if (s == NULL) return NULL;
+
+    /* Make sure added region doesn't contain garbage */
+    sh = (void*)(s-(sizeof(struct sdshdr)));
+    memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */
+    totlen = sh->len+sh->free;
+    sh->len = len;
+    sh->free = totlen-sh->len;
+    return s;
+}
+
+sds sdscatlen(sds s, const void *t, size_t len) {
+    struct sdshdr *sh;
+    size_t curlen = sdslen(s);
+
+    s = sdsMakeRoomFor(s,len);
+    if (s == NULL) return NULL;
+    sh = (void*) (s-(sizeof(struct sdshdr)));
+    memcpy(s+curlen, t, len);
+    sh->len = curlen+len;
+    sh->free = sh->free-len;
+    s[curlen+len] = '\0';
+    return s;
+}
+
+sds sdscat(sds s, const char *t) {
+    return sdscatlen(s, t, strlen(t));
+}
+
+sds sdscpylen(sds s, char *t, size_t len) {
+    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
+    size_t totlen = sh->free+sh->len;
+
+    if (totlen < len) {
+        s = sdsMakeRoomFor(s,len-sh->len);
+        if (s == NULL) return NULL;
+        sh = (void*) (s-(sizeof(struct sdshdr)));
+        totlen = sh->free+sh->len;
+    }
+    memcpy(s, t, len);
+    s[len] = '\0';
+    sh->len = len;
+    sh->free = totlen-len;
+    return s;
+}
+
+sds sdscpy(sds s, char *t) {
+    return sdscpylen(s, t, strlen(t));
+}
+
+sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
+    va_list cpy;
+    char *buf, *t;
+    size_t buflen = 16;
+
+    while(1) {
+        buf = malloc(buflen);
+#ifdef SDS_ABORT_ON_OOM
+        if (buf == NULL) sdsOomAbort();
+#else
+        if (buf == NULL) return NULL;
+#endif
+        buf[buflen-2] = '\0';
+        va_copy(cpy,ap);
+        vsnprintf(buf, buflen, fmt, cpy);
+        if (buf[buflen-2] != '\0') {
+            free(buf);
+            buflen *= 2;
+            continue;
+        }
+        break;
+    }
+    t = sdscat(s, buf);
+    free(buf);
+    return t;
+}
+
+sds sdscatprintf(sds s, const char *fmt, ...) {
+    va_list ap;
+    char *t;
+    va_start(ap, fmt);
+    t = sdscatvprintf(s,fmt,ap);
+    va_end(ap);
+    return t;
+}
+
+sds sdstrim(sds s, const char *cset) {
+    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
+    char *start, *end, *sp, *ep;
+    size_t len;
+
+    sp = start = s;
+    ep = end = s+sdslen(s)-1;
+    while(sp <= end && strchr(cset, *sp)) sp++;
+    while(ep > start && strchr(cset, *ep)) ep--;
+    len = (sp > ep) ? 0 : ((ep-sp)+1);
+    if (sh->buf != sp) memmove(sh->buf, sp, len);
+    sh->buf[len] = '\0';
+    sh->free = sh->free+(sh->len-len);
+    sh->len = len;
+    return s;
+}
+
+sds sdsrange(sds s, int start, int end) {
+    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
+    size_t newlen, len = sdslen(s);
+
+    if (len == 0) return s;
+    if (start < 0) {
+        start = len+start;
+        if (start < 0) start = 0;
+    }
+    if (end < 0) {
+        end = len+end;
+        if (end < 0) end = 0;
+    }
+    newlen = (start > end) ? 0 : (end-start)+1;
+    if (newlen != 0) {
+        if (start >= (signed)len) {
+            newlen = 0;
+        } else if (end >= (signed)len) {
+            end = len-1;
+            newlen = (start > end) ? 0 : (end-start)+1;
+        }
+    } else {
+        start = 0;
+    }
+    if (start && newlen) memmove(sh->buf, sh->buf+start, newlen);
+    sh->buf[newlen] = 0;
+    sh->free = sh->free+(sh->len-newlen);
+    sh->len = newlen;
+    return s;
+}
+
+void sdstolower(sds s) {
+    int len = sdslen(s), j;
+
+    for (j = 0; j < len; j++) s[j] = tolower(s[j]);
+}
+
+void sdstoupper(sds s) {
+    int len = sdslen(s), j;
+
+    for (j = 0; j < len; j++) s[j] = toupper(s[j]);
+}
+
+int sdscmp(sds s1, sds s2) {
+    size_t l1, l2, minlen;
+    int cmp;
+
+    l1 = sdslen(s1);
+    l2 = sdslen(s2);
+    minlen = (l1 < l2) ? l1 : l2;
+    cmp = memcmp(s1,s2,minlen);
+    if (cmp == 0) return l1-l2;
+    return cmp;
+}
+
+/* Split 's' with separator in 'sep'. An array
+ * of sds strings is returned. *count will be set
+ * by reference to the number of tokens returned.
+ *
+ * On out of memory, zero length string, zero length
+ * separator, NULL is returned.
+ *
+ * Note that 'sep' is able to split a string using
+ * a multi-character separator. For example
+ * sdssplit("foo_-_bar","_-_"); will return two
+ * elements "foo" and "bar".
+ *
+ * This version of the function is binary-safe but
+ * requires length arguments. sdssplit() is just the
+ * same function but for zero-terminated strings.
+ */
+sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) {
+    int elements = 0, slots = 5, start = 0, j;
+
+    sds *tokens = malloc(sizeof(sds)*slots);
+#ifdef SDS_ABORT_ON_OOM
+    if (tokens == NULL) sdsOomAbort();
+#endif
+    if (seplen < 1 || len < 0 || tokens == NULL) return NULL;
+    if (len == 0) {
+        *count = 0;
+        return tokens;
+    }
+    for (j = 0; j < (len-(seplen-1)); j++) {
+        /* make sure there is room for the next element and the final one */
+        if (slots < elements+2) {
+            sds *newtokens;
+
+            slots *= 2;
+            newtokens = realloc(tokens,sizeof(sds)*slots);
+            if (newtokens == NULL) {
+#ifdef SDS_ABORT_ON_OOM
+                sdsOomAbort();
+#else
+                goto cleanup;
+#endif
+            }
+            tokens = newtokens;
+        }
+        /* search the separator */
+        if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) {
+            tokens[elements] = sdsnewlen(s+start,j-start);
+            if (tokens[elements] == NULL) {
+#ifdef SDS_ABORT_ON_OOM
+                sdsOomAbort();
+#else
+                goto cleanup;
+#endif
+            }
+            elements++;
+            start = j+seplen;
+            j = j+seplen-1; /* skip the separator */
+        }
+    }
+    /* Add the final element. We are sure there is room in the tokens array. */
+    tokens[elements] = sdsnewlen(s+start,len-start);
+    if (tokens[elements] == NULL) {
+#ifdef SDS_ABORT_ON_OOM
+                sdsOomAbort();
+#else
+                goto cleanup;
+#endif
+    }
+    elements++;
+    *count = elements;
+    return tokens;
+
+#ifndef SDS_ABORT_ON_OOM
+cleanup:
+    {
+        int i;
+        for (i = 0; i < elements; i++) sdsfree(tokens[i]);
+        free(tokens);
+        return NULL;
+    }
+#endif
+}
+
+void sdsfreesplitres(sds *tokens, int count) {
+    if (!tokens) return;
+    while(count--)
+        sdsfree(tokens[count]);
+    free(tokens);
+}
+
+sds sdsfromlonglong(long long value) {
+    char buf[32], *p;
+    unsigned long long v;
+
+    v = (value < 0) ? -value : value;
+    p = buf+31; /* point to the last character */
+    do {
+        *p-- = '0'+(v%10);
+        v /= 10;
+    } while(v);
+    if (value < 0) *p-- = '-';
+    p++;
+    return sdsnewlen(p,32-(p-buf));
+}
+
+sds sdscatrepr(sds s, char *p, size_t len) {
+    s = sdscatlen(s,"\"",1);
+    if (s == NULL) return NULL;
+
+    while(len--) {
+        switch(*p) {
+        case '\\':
+        case '"':
+            s = sdscatprintf(s,"\\%c",*p);
+            break;
+        case '\n': s = sdscatlen(s,"\\n",2); break;
+        case '\r': s = sdscatlen(s,"\\r",2); break;
+        case '\t': s = sdscatlen(s,"\\t",2); break;
+        case '\a': s = sdscatlen(s,"\\a",2); break;
+        case '\b': s = sdscatlen(s,"\\b",2); break;
+        default:
+            if (isprint(*p))
+                s = sdscatprintf(s,"%c",*p);
+            else
+                s = sdscatprintf(s,"\\x%02x",(unsigned char)*p);
+            break;
+        }
+        p++;
+        if (s == NULL) return NULL;
+    }
+    return sdscatlen(s,"\"",1);
+}
+
+/* Split a line into arguments, where every argument can be in the
+ * following programming-language REPL-alike form:
+ *
+ * foo bar "newline are supported\n" and "\xff\x00otherstuff"
+ *
+ * The number of arguments is stored into *argc, and an array
+ * of sds is returned. The caller should sdsfree() all the returned
+ * strings and finally free() the array itself.
+ *
+ * Note that sdscatrepr() is able to convert back a string into
+ * a quoted string in the same format sdssplitargs() is able to parse.
+ */
+sds *sdssplitargs(char *line, int *argc) {
+    char *p = line;
+    char *current = NULL;
+    char **vector = NULL, **_vector = NULL;
+
+    *argc = 0;
+    while(1) {
+        /* skip blanks */
+        while(*p && isspace(*p)) p++;
+        if (*p) {
+            /* get a token */
+            int inq=0; /* set to 1 if we are in "quotes" */
+            int done=0;
+
+            if (current == NULL) {
+                current = sdsempty();
+                if (current == NULL) goto err;
+            }
+
+            while(!done) {
+                if (inq) {
+                    if (*p == '\\' && *(p+1)) {
+                        char c;
+
+                        p++;
+                        switch(*p) {
+                        case 'n': c = '\n'; break;
+                        case 'r': c = '\r'; break;
+                        case 't': c = '\t'; break;
+                        case 'b': c = '\b'; break;
+                        case 'a': c = '\a'; break;
+                        default: c = *p; break;
+                        }
+                        current = sdscatlen(current,&c,1);
+                    } else if (*p == '"') {
+                        /* closing quote must be followed by a space */
+                        if (*(p+1) && !isspace(*(p+1))) goto err;
+                        done=1;
+                    } else if (!*p) {
+                        /* unterminated quotes */
+                        goto err;
+                    } else {
+                        current = sdscatlen(current,p,1);
+                    }
+                } else {
+                    switch(*p) {
+                    case ' ':
+                    case '\n':
+                    case '\r':
+                    case '\t':
+                    case '\0':
+                        done=1;
+                        break;
+                    case '"':
+                        inq=1;
+                        break;
+                    default:
+                        current = sdscatlen(current,p,1);
+                        break;
+                    }
+                }
+                if (*p) p++;
+                if (current == NULL) goto err;
+            }
+            /* add the token to the vector */
+            _vector = realloc(vector,((*argc)+1)*sizeof(char*));
+            if (_vector == NULL) goto err;
+
+            vector = _vector;
+            vector[*argc] = current;
+            (*argc)++;
+            current = NULL;
+        } else {
+            return vector;
+        }
+    }
+
+err:
+    while((*argc)--)
+        sdsfree(vector[*argc]);
+    if (vector != NULL) free(vector);
+    if (current != NULL) sdsfree(current);
+    return NULL;
+}
+
+#ifdef SDS_TEST_MAIN
+#include <stdio.h>
+
+int __failed_tests = 0;
+int __test_num = 0;
+#define test_cond(descr,_c) do { \
+    __test_num++; printf("%d - %s: ", __test_num, descr); \
+    if(_c) printf("PASSED\n"); else {printf("FAILED\n"); __failed_tests++;} \
+} while(0);
+#define test_report() do { \
+    printf("%d tests, %d passed, %d failed\n", __test_num, \
+                    __test_num-__failed_tests, __failed_tests); \
+    if (__failed_tests) { \
+        printf("=== WARNING === We have failed tests here...\n"); \
+    } \
+} while(0);
+
+int main(void) {
+    {
+        sds x = sdsnew("foo"), y;
+
+        test_cond("Create a string and obtain the length",
+            sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0)
+
+        sdsfree(x);
+        x = sdsnewlen("foo",2);
+        test_cond("Create a string with specified length",
+            sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0)
+
+        x = sdscat(x,"bar");
+        test_cond("Strings concatenation",
+            sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0);
+
+        x = sdscpy(x,"a");
+        test_cond("sdscpy() against an originally longer string",
+            sdslen(x) == 1 && memcmp(x,"a\0",2) == 0)
+
+        x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk");
+        test_cond("sdscpy() against an originally shorter string",
+            sdslen(x) == 33 &&
+            memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0)
+
+        sdsfree(x);
+        x = sdscatprintf(sdsempty(),"%d",123);
+        test_cond("sdscatprintf() seems working in the base case",
+            sdslen(x) == 3 && memcmp(x,"123\0",4) ==0)
+
+        sdsfree(x);
+        x = sdstrim(sdsnew("xxciaoyyy"),"xy");
+        test_cond("sdstrim() correctly trims characters",
+            sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0)
+
+        y = sdsrange(sdsdup(x),1,1);
+        test_cond("sdsrange(...,1,1)",
+            sdslen(y) == 1 && memcmp(y,"i\0",2) == 0)
+
+        sdsfree(y);
+        y = sdsrange(sdsdup(x),1,-1);
+        test_cond("sdsrange(...,1,-1)",
+            sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
+
+        sdsfree(y);
+        y = sdsrange(sdsdup(x),-2,-1);
+        test_cond("sdsrange(...,-2,-1)",
+            sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0)
+
+        sdsfree(y);
+        y = sdsrange(sdsdup(x),2,1);
+        test_cond("sdsrange(...,2,1)",
+            sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
+
+        sdsfree(y);
+        y = sdsrange(sdsdup(x),1,100);
+        test_cond("sdsrange(...,1,100)",
+            sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
+
+        sdsfree(y);
+        y = sdsrange(sdsdup(x),100,100);
+        test_cond("sdsrange(...,100,100)",
+            sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
+
+        sdsfree(y);
+        sdsfree(x);
+        x = sdsnew("foo");
+        y = sdsnew("foa");
+        test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0)
+
+        sdsfree(y);
+        sdsfree(x);
+        x = sdsnew("bar");
+        y = sdsnew("bar");
+        test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0)
+
+        sdsfree(y);
+        sdsfree(x);
+        x = sdsnew("aar");
+        y = sdsnew("bar");
+        test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0)
+    }
+    test_report()
+}
+#endif
diff --git a/hiredis/sds.h b/hiredis/sds.h
new file mode 100644 (file)
index 0000000..94f5871
--- /dev/null
@@ -0,0 +1,88 @@
+/* SDSLib, A C dynamic strings library
+ *
+ * Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __SDS_H
+#define __SDS_H
+
+#include <sys/types.h>
+#include <stdarg.h>
+
+typedef char *sds;
+
+struct sdshdr {
+    int len;
+    int free;
+    char buf[];
+};
+
+static inline size_t sdslen(const sds s) {
+    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
+    return sh->len;
+}
+
+static inline size_t sdsavail(const sds s) {
+    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
+    return sh->free;
+}
+
+sds sdsnewlen(const void *init, size_t initlen);
+sds sdsnew(const char *init);
+sds sdsempty(void);
+size_t sdslen(const sds s);
+sds sdsdup(const sds s);
+void sdsfree(sds s);
+size_t sdsavail(sds s);
+sds sdsgrowzero(sds s, size_t len);
+sds sdscatlen(sds s, const void *t, size_t len);
+sds sdscat(sds s, const char *t);
+sds sdscpylen(sds s, char *t, size_t len);
+sds sdscpy(sds s, char *t);
+
+sds sdscatvprintf(sds s, const char *fmt, va_list ap);
+#ifdef __GNUC__
+sds sdscatprintf(sds s, const char *fmt, ...)
+    __attribute__((format(printf, 2, 3)));
+#else
+sds sdscatprintf(sds s, const char *fmt, ...);
+#endif
+
+sds sdstrim(sds s, const char *cset);
+sds sdsrange(sds s, int start, int end);
+void sdsupdatelen(sds s);
+int sdscmp(sds s1, sds s2);
+sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count);
+void sdsfreesplitres(sds *tokens, int count);
+void sdstolower(sds s);
+void sdstoupper(sds s);
+sds sdsfromlonglong(long long value);
+sds sdscatrepr(sds s, char *p, size_t len);
+sds *sdssplitargs(char *line, int *argc);
+
+#endif
diff --git a/hiredis/test.c b/hiredis/test.c
new file mode 100644 (file)
index 0000000..6786003
--- /dev/null
@@ -0,0 +1,659 @@
+#include "fmacros.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/time.h>
+#include <assert.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+
+#include "hiredis.h"
+
+enum connection_type {
+    CONN_TCP,
+    CONN_UNIX
+};
+
+struct config {
+    enum connection_type type;
+
+    struct {
+        const char *host;
+        int port;
+    } tcp;
+
+    struct {
+        const char *path;
+    } unix;
+};
+
+/* The following lines make up our testing "framework" :) */
+static int tests = 0, fails = 0;
+#define test(_s) { printf("#%02d ", ++tests); printf(_s); }
+#define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;}
+
+static long long usec(void) {
+    struct timeval tv;
+    gettimeofday(&tv,NULL);
+    return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
+}
+
+static redisContext *select_database(redisContext *c) {
+    redisReply *reply;
+
+    /* Switch to DB 9 for testing, now that we know we can chat. */
+    reply = redisCommand(c,"SELECT 9");
+    assert(reply != NULL);
+    freeReplyObject(reply);
+
+    /* Make sure the DB is emtpy */
+    reply = redisCommand(c,"DBSIZE");
+    assert(reply != NULL);
+    if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) {
+        /* Awesome, DB 9 is empty and we can continue. */
+        freeReplyObject(reply);
+    } else {
+        printf("Database #9 is not empty, test can not continue\n");
+        exit(1);
+    }
+
+    return c;
+}
+
+static void disconnect(redisContext *c) {
+    redisReply *reply;
+
+    /* Make sure we're on DB 9. */
+    reply = redisCommand(c,"SELECT 9");
+    assert(reply != NULL);
+    freeReplyObject(reply);
+    reply = redisCommand(c,"FLUSHDB");
+    assert(reply != NULL);
+    freeReplyObject(reply);
+
+    /* Free the context as well. */
+    redisFree(c);
+}
+
+static redisContext *connect(struct config config) {
+    redisContext *c = NULL;
+
+    if (config.type == CONN_TCP) {
+        c = redisConnect(config.tcp.host, config.tcp.port);
+    } else if (config.type == CONN_UNIX) {
+        c = redisConnectUnix(config.unix.path);
+    } else {
+        assert(NULL);
+    }
+
+    if (c == NULL) {
+        printf("Connection error: can't allocate redis context\n");
+        exit(1);
+    } else if (c->err) {
+        printf("Connection error: %s\n", c->errstr);
+        exit(1);
+    }
+
+    return select_database(c);
+}
+
+static void test_format_commands(void) {
+    char *cmd;
+    int len;
+
+    test("Format command without interpolation: ");
+    len = redisFormatCommand(&cmd,"SET foo bar");
+    test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
+        len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+    free(cmd);
+
+    test("Format command with %%s string interpolation: ");
+    len = redisFormatCommand(&cmd,"SET %s %s","foo","bar");
+    test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
+        len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+    free(cmd);
+
+    test("Format command with %%s and an empty string: ");
+    len = redisFormatCommand(&cmd,"SET %s %s","foo","");
+    test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
+        len == 4+4+(3+2)+4+(3+2)+4+(0+2));
+    free(cmd);
+
+    test("Format command with an empty string in between proper interpolations: ");
+    len = redisFormatCommand(&cmd,"SET %s %s","","foo");
+    test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 &&
+        len == 4+4+(3+2)+4+(0+2)+4+(3+2));
+    free(cmd);
+
+    test("Format command with %%b string interpolation: ");
+    len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"b\0r",3);
+    test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 &&
+        len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+    free(cmd);
+
+    test("Format command with %%b and an empty string: ");
+    len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"",0);
+    test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
+        len == 4+4+(3+2)+4+(3+2)+4+(0+2));
+    free(cmd);
+
+    test("Format command with literal %%: ");
+    len = redisFormatCommand(&cmd,"SET %% %%");
+    test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 &&
+        len == 4+4+(3+2)+4+(1+2)+4+(1+2));
+    free(cmd);
+
+    /* Vararg width depends on the type. These tests make sure that the
+     * width is correctly determined using the format and subsequent varargs
+     * can correctly be interpolated. */
+#define INTEGER_WIDTH_TEST(fmt, type) do {                                                \
+    type value = 123;                                                                     \
+    test("Format command with printf-delegation (" #type "): ");                          \
+    len = redisFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello");               \
+    test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \
+        len == 4+5+(12+2)+4+(9+2));                                                       \
+    free(cmd);                                                                            \
+} while(0)
+
+#define FLOAT_WIDTH_TEST(type) do {                                                       \
+    type value = 123.0;                                                                   \
+    test("Format command with printf-delegation (" #type "): ");                          \
+    len = redisFormatCommand(&cmd,"key:%08.3f str:%s", value, "hello");                   \
+    test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \
+        len == 4+5+(12+2)+4+(9+2));                                                       \
+    free(cmd);                                                                            \
+} while(0)
+
+    INTEGER_WIDTH_TEST("d", int);
+    INTEGER_WIDTH_TEST("hhd", char);
+    INTEGER_WIDTH_TEST("hd", short);
+    INTEGER_WIDTH_TEST("ld", long);
+    INTEGER_WIDTH_TEST("lld", long long);
+    INTEGER_WIDTH_TEST("u", unsigned int);
+    INTEGER_WIDTH_TEST("hhu", unsigned char);
+    INTEGER_WIDTH_TEST("hu", unsigned short);
+    INTEGER_WIDTH_TEST("lu", unsigned long);
+    INTEGER_WIDTH_TEST("llu", unsigned long long);
+    FLOAT_WIDTH_TEST(float);
+    FLOAT_WIDTH_TEST(double);
+
+    test("Format command with invalid printf format: ");
+    len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",3);
+    test_cond(len == -1);
+
+    const char *argv[3];
+    argv[0] = "SET";
+    argv[1] = "foo\0xxx";
+    argv[2] = "bar";
+    size_t lens[3] = { 3, 7, 3 };
+    int argc = 3;
+
+    test("Format command by passing argc/argv without lengths: ");
+    len = redisFormatCommandArgv(&cmd,argc,argv,NULL);
+    test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
+        len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+    free(cmd);
+
+    test("Format command by passing argc/argv with lengths: ");
+    len = redisFormatCommandArgv(&cmd,argc,argv,lens);
+    test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
+        len == 4+4+(3+2)+4+(7+2)+4+(3+2));
+    free(cmd);
+}
+
+static void test_reply_reader(void) {
+    redisReader *reader;
+    void *reply;
+    int ret;
+    int i;
+
+    test("Error handling in reply parser: ");
+    reader = redisReaderCreate();
+    redisReaderFeed(reader,(char*)"@foo\r\n",6);
+    ret = redisReaderGetReply(reader,NULL);
+    test_cond(ret == REDIS_ERR &&
+              strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
+    redisReaderFree(reader);
+
+    /* when the reply already contains multiple items, they must be free'd
+     * on an error. valgrind will bark when this doesn't happen. */
+    test("Memory cleanup in reply parser: ");
+    reader = redisReaderCreate();
+    redisReaderFeed(reader,(char*)"*2\r\n",4);
+    redisReaderFeed(reader,(char*)"$5\r\nhello\r\n",11);
+    redisReaderFeed(reader,(char*)"@foo\r\n",6);
+    ret = redisReaderGetReply(reader,NULL);
+    test_cond(ret == REDIS_ERR &&
+              strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
+    redisReaderFree(reader);
+
+    test("Set error on nested multi bulks with depth > 7: ");
+    reader = redisReaderCreate();
+
+    for (i = 0; i < 9; i++) {
+        redisReaderFeed(reader,(char*)"*1\r\n",4);
+    }
+
+    ret = redisReaderGetReply(reader,NULL);
+    test_cond(ret == REDIS_ERR &&
+              strncasecmp(reader->errstr,"No support for",14) == 0);
+    redisReaderFree(reader);
+
+    test("Works with NULL functions for reply: ");
+    reader = redisReaderCreate();
+    reader->fn = NULL;
+    redisReaderFeed(reader,(char*)"+OK\r\n",5);
+    ret = redisReaderGetReply(reader,&reply);
+    test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
+    redisReaderFree(reader);
+
+    test("Works when a single newline (\\r\\n) covers two calls to feed: ");
+    reader = redisReaderCreate();
+    reader->fn = NULL;
+    redisReaderFeed(reader,(char*)"+OK\r",4);
+    ret = redisReaderGetReply(reader,&reply);
+    assert(ret == REDIS_OK && reply == NULL);
+    redisReaderFeed(reader,(char*)"\n",1);
+    ret = redisReaderGetReply(reader,&reply);
+    test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
+    redisReaderFree(reader);
+
+    test("Don't reset state after protocol error: ");
+    reader = redisReaderCreate();
+    reader->fn = NULL;
+    redisReaderFeed(reader,(char*)"x",1);
+    ret = redisReaderGetReply(reader,&reply);
+    assert(ret == REDIS_ERR);
+    ret = redisReaderGetReply(reader,&reply);
+    test_cond(ret == REDIS_ERR && reply == NULL);
+    redisReaderFree(reader);
+
+    /* Regression test for issue #45 on GitHub. */
+    test("Don't do empty allocation for empty multi bulk: ");
+    reader = redisReaderCreate();
+    redisReaderFeed(reader,(char*)"*0\r\n",4);
+    ret = redisReaderGetReply(reader,&reply);
+    test_cond(ret == REDIS_OK &&
+        ((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
+        ((redisReply*)reply)->elements == 0);
+    freeReplyObject(reply);
+    redisReaderFree(reader);
+}
+
+static void test_blocking_connection_errors(void) {
+    redisContext *c;
+
+    test("Returns error when host cannot be resolved: ");
+    c = redisConnect((char*)"idontexist.local", 6379);
+    test_cond(c->err == REDIS_ERR_OTHER &&
+        (strcmp(c->errstr,"Name or service not known") == 0 ||
+         strcmp(c->errstr,"Can't resolve: idontexist.local") == 0));
+    redisFree(c);
+
+    test("Returns error when the port is not open: ");
+    c = redisConnect((char*)"localhost", 1);
+    test_cond(c->err == REDIS_ERR_IO &&
+        strcmp(c->errstr,"Connection refused") == 0);
+    redisFree(c);
+
+    test("Returns error when the unix socket path doesn't accept connections: ");
+    c = redisConnectUnix((char*)"/tmp/idontexist.sock");
+    test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */
+    redisFree(c);
+}
+
+static void test_blocking_connection(struct config config) {
+    redisContext *c;
+    redisReply *reply;
+
+    c = connect(config);
+
+    test("Is able to deliver commands: ");
+    reply = redisCommand(c,"PING");
+    test_cond(reply->type == REDIS_REPLY_STATUS &&
+        strcasecmp(reply->str,"pong") == 0)
+    freeReplyObject(reply);
+
+    test("Is a able to send commands verbatim: ");
+    reply = redisCommand(c,"SET foo bar");
+    test_cond (reply->type == REDIS_REPLY_STATUS &&
+        strcasecmp(reply->str,"ok") == 0)
+    freeReplyObject(reply);
+
+    test("%%s String interpolation works: ");
+    reply = redisCommand(c,"SET %s %s","foo","hello world");
+    freeReplyObject(reply);
+    reply = redisCommand(c,"GET foo");
+    test_cond(reply->type == REDIS_REPLY_STRING &&
+        strcmp(reply->str,"hello world") == 0);
+    freeReplyObject(reply);
+
+    test("%%b String interpolation works: ");
+    reply = redisCommand(c,"SET %b %b","foo",3,"hello\x00world",11);
+    freeReplyObject(reply);
+    reply = redisCommand(c,"GET foo");
+    test_cond(reply->type == REDIS_REPLY_STRING &&
+        memcmp(reply->str,"hello\x00world",11) == 0)
+
+    test("Binary reply length is correct: ");
+    test_cond(reply->len == 11)
+    freeReplyObject(reply);
+
+    test("Can parse nil replies: ");
+    reply = redisCommand(c,"GET nokey");
+    test_cond(reply->type == REDIS_REPLY_NIL)
+    freeReplyObject(reply);
+
+    /* test 7 */
+    test("Can parse integer replies: ");
+    reply = redisCommand(c,"INCR mycounter");
+    test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1)
+    freeReplyObject(reply);
+
+    test("Can parse multi bulk replies: ");
+    freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
+    freeReplyObject(redisCommand(c,"LPUSH mylist bar"));
+    reply = redisCommand(c,"LRANGE mylist 0 -1");
+    test_cond(reply->type == REDIS_REPLY_ARRAY &&
+              reply->elements == 2 &&
+              !memcmp(reply->element[0]->str,"bar",3) &&
+              !memcmp(reply->element[1]->str,"foo",3))
+    freeReplyObject(reply);
+
+    /* m/e with multi bulk reply *before* other reply.
+     * specifically test ordering of reply items to parse. */
+    test("Can handle nested multi bulk replies: ");
+    freeReplyObject(redisCommand(c,"MULTI"));
+    freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1"));
+    freeReplyObject(redisCommand(c,"PING"));
+    reply = (redisCommand(c,"EXEC"));
+    test_cond(reply->type == REDIS_REPLY_ARRAY &&
+              reply->elements == 2 &&
+              reply->element[0]->type == REDIS_REPLY_ARRAY &&
+              reply->element[0]->elements == 2 &&
+              !memcmp(reply->element[0]->element[0]->str,"bar",3) &&
+              !memcmp(reply->element[0]->element[1]->str,"foo",3) &&
+              reply->element[1]->type == REDIS_REPLY_STATUS &&
+              strcasecmp(reply->element[1]->str,"pong") == 0);
+    freeReplyObject(reply);
+
+    disconnect(c);
+}
+
+static void test_blocking_io_errors(struct config config) {
+    redisContext *c;
+    redisReply *reply;
+    void *_reply;
+    int major, minor;
+
+    /* Connect to target given by config. */
+    c = connect(config);
+    {
+        /* Find out Redis version to determine the path for the next test */
+        const char *field = "redis_version:";
+        char *p, *eptr;
+
+        reply = redisCommand(c,"INFO");
+        p = strstr(reply->str,field);
+        major = strtol(p+strlen(field),&eptr,10);
+        p = eptr+1; /* char next to the first "." */
+        minor = strtol(p,&eptr,10);
+        freeReplyObject(reply);
+    }
+
+    test("Returns I/O error when the connection is lost: ");
+    reply = redisCommand(c,"QUIT");
+    if (major >= 2 && minor > 0) {
+        /* > 2.0 returns OK on QUIT and read() should be issued once more
+         * to know the descriptor is at EOF. */
+        test_cond(strcasecmp(reply->str,"OK") == 0 &&
+            redisGetReply(c,&_reply) == REDIS_ERR);
+        freeReplyObject(reply);
+    } else {
+        test_cond(reply == NULL);
+    }
+
+    /* On 2.0, QUIT will cause the connection to be closed immediately and
+     * the read(2) for the reply on QUIT will set the error to EOF.
+     * On >2.0, QUIT will return with OK and another read(2) needed to be
+     * issued to find out the socket was closed by the server. In both
+     * conditions, the error will be set to EOF. */
+    assert(c->err == REDIS_ERR_EOF &&
+        strcmp(c->errstr,"Server closed the connection") == 0);
+    redisFree(c);
+
+    c = connect(config);
+    test("Returns I/O error on socket timeout: ");
+    struct timeval tv = { 0, 1000 };
+    assert(redisSetTimeout(c,tv) == REDIS_OK);
+    test_cond(redisGetReply(c,&_reply) == REDIS_ERR &&
+        c->err == REDIS_ERR_IO && errno == EAGAIN);
+    redisFree(c);
+}
+
+static void test_throughput(struct config config) {
+    redisContext *c = connect(config);
+    redisReply **replies;
+    int i, num;
+    long long t1, t2;
+
+    test("Throughput:\n");
+    for (i = 0; i < 500; i++)
+        freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
+
+    num = 1000;
+    replies = malloc(sizeof(redisReply*)*num);
+    t1 = usec();
+    for (i = 0; i < num; i++) {
+        replies[i] = redisCommand(c,"PING");
+        assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
+    }
+    t2 = usec();
+    for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+    free(replies);
+    printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0);
+
+    replies = malloc(sizeof(redisReply*)*num);
+    t1 = usec();
+    for (i = 0; i < num; i++) {
+        replies[i] = redisCommand(c,"LRANGE mylist 0 499");
+        assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
+        assert(replies[i] != NULL && replies[i]->elements == 500);
+    }
+    t2 = usec();
+    for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+    free(replies);
+    printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0);
+
+    num = 10000;
+    replies = malloc(sizeof(redisReply*)*num);
+    for (i = 0; i < num; i++)
+        redisAppendCommand(c,"PING");
+    t1 = usec();
+    for (i = 0; i < num; i++) {
+        assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
+        assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
+    }
+    t2 = usec();
+    for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+    free(replies);
+    printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
+
+    replies = malloc(sizeof(redisReply*)*num);
+    for (i = 0; i < num; i++)
+        redisAppendCommand(c,"LRANGE mylist 0 499");
+    t1 = usec();
+    for (i = 0; i < num; i++) {
+        assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
+        assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
+        assert(replies[i] != NULL && replies[i]->elements == 500);
+    }
+    t2 = usec();
+    for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+    free(replies);
+    printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
+
+    disconnect(c);
+}
+
+// static long __test_callback_flags = 0;
+// static void __test_callback(redisContext *c, void *privdata) {
+//     ((void)c);
+//     /* Shift to detect execution order */
+//     __test_callback_flags <<= 8;
+//     __test_callback_flags |= (long)privdata;
+// }
+//
+// static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) {
+//     ((void)c);
+//     /* Shift to detect execution order */
+//     __test_callback_flags <<= 8;
+//     __test_callback_flags |= (long)privdata;
+//     if (reply) freeReplyObject(reply);
+// }
+//
+// static redisContext *__connect_nonblock() {
+//     /* Reset callback flags */
+//     __test_callback_flags = 0;
+//     return redisConnectNonBlock("127.0.0.1", port, NULL);
+// }
+//
+// static void test_nonblocking_connection() {
+//     redisContext *c;
+//     int wdone = 0;
+//
+//     test("Calls command callback when command is issued: ");
+//     c = __connect_nonblock();
+//     redisSetCommandCallback(c,__test_callback,(void*)1);
+//     redisCommand(c,"PING");
+//     test_cond(__test_callback_flags == 1);
+//     redisFree(c);
+//
+//     test("Calls disconnect callback on redisDisconnect: ");
+//     c = __connect_nonblock();
+//     redisSetDisconnectCallback(c,__test_callback,(void*)2);
+//     redisDisconnect(c);
+//     test_cond(__test_callback_flags == 2);
+//     redisFree(c);
+//
+//     test("Calls disconnect callback and free callback on redisFree: ");
+//     c = __connect_nonblock();
+//     redisSetDisconnectCallback(c,__test_callback,(void*)2);
+//     redisSetFreeCallback(c,__test_callback,(void*)4);
+//     redisFree(c);
+//     test_cond(__test_callback_flags == ((2 << 8) | 4));
+//
+//     test("redisBufferWrite against empty write buffer: ");
+//     c = __connect_nonblock();
+//     test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1);
+//     redisFree(c);
+//
+//     test("redisBufferWrite against not yet connected fd: ");
+//     c = __connect_nonblock();
+//     redisCommand(c,"PING");
+//     test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
+//               strncmp(c->error,"write:",6) == 0);
+//     redisFree(c);
+//
+//     test("redisBufferWrite against closed fd: ");
+//     c = __connect_nonblock();
+//     redisCommand(c,"PING");
+//     redisDisconnect(c);
+//     test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
+//               strncmp(c->error,"write:",6) == 0);
+//     redisFree(c);
+//
+//     test("Process callbacks in the right sequence: ");
+//     c = __connect_nonblock();
+//     redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING");
+//     redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
+//     redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING");
+//
+//     /* Write output buffer */
+//     wdone = 0;
+//     while(!wdone) {
+//         usleep(500);
+//         redisBufferWrite(c,&wdone);
+//     }
+//
+//     /* Read until at least one callback is executed (the 3 replies will
+//      * arrive in a single packet, causing all callbacks to be executed in
+//      * a single pass). */
+//     while(__test_callback_flags == 0) {
+//         assert(redisBufferRead(c) == REDIS_OK);
+//         redisProcessCallbacks(c);
+//     }
+//     test_cond(__test_callback_flags == 0x010203);
+//     redisFree(c);
+//
+//     test("redisDisconnect executes pending callbacks with NULL reply: ");
+//     c = __connect_nonblock();
+//     redisSetDisconnectCallback(c,__test_callback,(void*)1);
+//     redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
+//     redisDisconnect(c);
+//     test_cond(__test_callback_flags == 0x0201);
+//     redisFree(c);
+// }
+
+int main(int argc, char **argv) {
+    struct config cfg = {
+        .tcp = {
+            .host = "127.0.0.1",
+            .port = 6379
+        },
+        .unix = {
+            .path = "/tmp/redis.sock"
+        }
+    };
+    int throughput = 1;
+
+    /* Ignore broken pipe signal (for I/O error tests). */
+    signal(SIGPIPE, SIG_IGN);
+
+    /* Parse command line options. */
+    argv++; argc--;
+    while (argc) {
+        if (argc >= 2 && !strcmp(argv[0],"-h")) {
+            argv++; argc--;
+            cfg.tcp.host = argv[0];
+        } else if (argc >= 2 && !strcmp(argv[0],"-p")) {
+            argv++; argc--;
+            cfg.tcp.port = atoi(argv[0]);
+        } else if (argc >= 2 && !strcmp(argv[0],"-s")) {
+            argv++; argc--;
+            cfg.unix.path = argv[0];
+        } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) {
+            throughput = 0;
+        } else {
+            fprintf(stderr, "Invalid argument: %s\n", argv[0]);
+            exit(1);
+        }
+        argv++; argc--;
+    }
+
+    test_format_commands();
+    test_reply_reader();
+    test_blocking_connection_errors();
+
+    printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
+    cfg.type = CONN_TCP;
+    test_blocking_connection(cfg);
+    test_blocking_io_errors(cfg);
+    if (throughput) test_throughput(cfg);
+
+    printf("\nTesting against Unix socket connection (%s):\n", cfg.unix.path);
+    cfg.type = CONN_UNIX;
+    test_blocking_connection(cfg);
+    test_blocking_io_errors(cfg);
+    if (throughput) test_throughput(cfg);
+
+    if (fails) {
+        printf("*** %d TESTS FAILED ***\n", fails);
+        return 1;
+    }
+
+    printf("ALL TESTS PASSED\n");
+    return 0;
+}
diff --git a/hiredis/util.h b/hiredis/util.h
new file mode 100644 (file)
index 0000000..f192990
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __UTIL_H
+#define __UTIL_H
+#include <stdlib.h>
+
+/* Abort on out of memory */
+static void redisOOM(void) {
+    fprintf(stderr,"Out of memory in hiredis");
+    exit(1);
+}
+
+#endif
diff --git a/http-parser/.gitignore b/http-parser/.gitignore
new file mode 100644 (file)
index 0000000..73fe6a4
--- /dev/null
@@ -0,0 +1,4 @@
+tags
+*.o
+test
+test_g
diff --git a/http-parser/CONTRIBUTIONS b/http-parser/CONTRIBUTIONS
new file mode 100644 (file)
index 0000000..11ba31e
--- /dev/null
@@ -0,0 +1,4 @@
+Contributors must agree to the Contributor License Agreement before patches
+can be accepted.
+
+http://spreadsheets2.google.com/viewform?hl=en&formkey=dDJXOGUwbzlYaWM4cHN1MERwQS1CSnc6MQ
diff --git a/http-parser/LICENSE-MIT b/http-parser/LICENSE-MIT
new file mode 100644 (file)
index 0000000..f30a31d
--- /dev/null
@@ -0,0 +1,19 @@
+Copyright 2009,2010 Ryan Dahl <ry@tinyclouds.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE. 
diff --git a/http-parser/README.md b/http-parser/README.md
new file mode 100644 (file)
index 0000000..72332fb
--- /dev/null
@@ -0,0 +1,171 @@
+HTTP Parser
+===========
+
+This is a parser for HTTP messages written in C. It parses both requests and
+responses. The parser is designed to be used in performance HTTP
+applications. It does not make any syscalls nor allocations, it does not
+buffer data, it can be interrupted at anytime. Depending on your
+architecture, it only requires about 40 bytes of data per message
+stream (in a web server that is per connection).
+
+Features:
+
+  * No dependencies
+  * Handles persistent streams (keep-alive).
+  * Decodes chunked encoding.
+  * Upgrade support
+  * Defends against buffer overflow attacks.
+
+The parser extracts the following information from HTTP messages:
+
+  * Header fields and values
+  * Content-Length
+  * Request method
+  * Response status code
+  * Transfer-Encoding
+  * HTTP version
+  * Request path, query string, fragment
+  * Message body
+
+
+Usage
+-----
+
+One `http_parser` object is used per TCP connection. Initialize the struct
+using `http_parser_init()` and set the callbacks. That might look something
+like this for a request parser:
+
+    http_parser_settings settings;
+    settings.on_path = my_path_callback;
+    settings.on_header_field = my_header_field_callback;
+    /* ... */
+
+    http_parser *parser = malloc(sizeof(http_parser));
+    http_parser_init(parser, HTTP_REQUEST);
+    parser->data = my_socket;
+
+When data is received on the socket execute the parser and check for errors.
+
+    size_t len = 80*1024, nparsed;
+    char buf[len];
+    ssize_t recved;
+
+    recved = recv(fd, buf, len, 0);
+
+    if (recved < 0) {
+      /* Handle error. */
+    }
+
+    /* Start up / continue the parser.
+     * Note we pass recved==0 to signal that EOF has been recieved.
+     */
+    nparsed = http_parser_execute(parser, &settings, buf, recved);
+
+    if (parser->upgrade) {
+      /* handle new protocol */
+    } else if (nparsed != recved) {
+      /* Handle error. Usually just close the connection. */
+    }
+
+HTTP needs to know where the end of the stream is. For example, sometimes
+servers send responses without Content-Length and expect the client to
+consume input (for the body) until EOF. To tell http_parser about EOF, give
+`0` as the forth parameter to `http_parser_execute()`. Callbacks and errors
+can still be encountered during an EOF, so one must still be prepared
+to receive them.
+
+Scalar valued message information such as `status_code`, `method`, and the
+HTTP version are stored in the parser structure. This data is only
+temporally stored in `http_parser` and gets reset on each new message. If
+this information is needed later, copy it out of the structure during the
+`headers_complete` callback.
+
+The parser decodes the transfer-encoding for both requests and responses
+transparently. That is, a chunked encoding is decoded before being sent to
+the on_body callback.
+
+
+The Special Problem of Upgrade
+------------------------------
+
+HTTP supports upgrading the connection to a different protocol. An
+increasingly common example of this is the Web Socket protocol which sends
+a request like
+
+        GET /demo HTTP/1.1
+        Upgrade: WebSocket
+        Connection: Upgrade
+        Host: example.com
+        Origin: http://example.com
+        WebSocket-Protocol: sample
+
+followed by non-HTTP data.
+
+(See http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 for more
+information the Web Socket protocol.)
+
+To support this, the parser will treat this as a normal HTTP message without a
+body. Issuing both on_headers_complete and on_message_complete callbacks. However
+http_parser_execute() will stop parsing at the end of the headers and return.
+
+The user is expected to check if `parser->upgrade` has been set to 1 after
+`http_parser_execute()` returns. Non-HTTP data begins at the buffer supplied
+offset by the return value of `http_parser_execute()`.
+
+
+Callbacks
+---------
+
+During the `http_parser_execute()` call, the callbacks set in
+`http_parser_settings` will be executed. The parser maintains state and
+never looks behind, so buffering the data is not necessary. If you need to
+save certain data for later usage, you can do that from the callbacks.
+
+There are two types of callbacks:
+
+* notification `typedef int (*http_cb) (http_parser*);`
+    Callbacks: on_message_begin, on_headers_complete, on_message_complete.
+* data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);`
+    Callbacks: (requests only) on_path, on_query_string, on_uri, on_fragment,
+               (common) on_header_field, on_header_value, on_body;
+
+Callbacks must return 0 on success. Returning a non-zero value indicates
+error to the parser, making it exit immediately.
+
+In case you parse HTTP message in chunks (i.e. `read()` request line
+from socket, parse, read half headers, parse, etc) your data callbacks
+may be called more than once. Http-parser guarantees that data pointer is only
+valid for the lifetime of callback. You can also `read()` into a heap allocated
+buffer to avoid copying memory around if this fits your application.
+
+Reading headers may be a tricky task if you read/parse headers partially.
+Basically, you need to remember whether last header callback was field or value
+and apply following logic:
+
+    (on_header_field and on_header_value shortened to on_h_*)
+     ------------------------ ------------ --------------------------------------------
+    | State (prev. callback) | Callback   | Description/action                         |
+     ------------------------ ------------ --------------------------------------------
+    | nothing (first call)   | on_h_field | Allocate new buffer and copy callback data |
+    |                        |            | into it                                    |
+     ------------------------ ------------ --------------------------------------------
+    | value                  | on_h_field | New header started.                        |
+    |                        |            | Copy current name,value buffers to headers |
+    |                        |            | list and allocate new buffer for new name  |
+     ------------------------ ------------ --------------------------------------------
+    | field                  | on_h_field | Previous name continues. Reallocate name   |
+    |                        |            | buffer and append callback data to it      |
+     ------------------------ ------------ --------------------------------------------
+    | field                  | on_h_value | Value for current header started. Allocate |
+    |                        |            | new buffer and copy callback data to it    |
+     ------------------------ ------------ --------------------------------------------
+    | value                  | on_h_value | Value continues. Reallocate value buffer   |
+    |                        |            | and append callback data to it             |
+     ------------------------ ------------ --------------------------------------------
+
+
+See examples of reading in headers:
+
+* [partial example](http://gist.github.com/155877) in C
+* [from http-parser tests](http://github.com/ry/http-parser/blob/37a0ff8928fb0d83cec0d0d8909c5a4abcd221af/test.c#L403) in C
+* [from Node library](http://github.com/ry/node/blob/842eaf446d2fdcb33b296c67c911c32a0dabc747/src/http.js#L284) in Javascript
diff --git a/http-parser/http_parser.c b/http-parser/http_parser.c
new file mode 100644 (file)
index 0000000..5a0972a
--- /dev/null
@@ -0,0 +1,1601 @@
+/* Copyright 2009,2010 Ryan Dahl <ry@tinyclouds.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <http_parser.h>
+#include <assert.h>
+#include <stddef.h>
+
+
+#ifndef MIN
+# define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+
+#define CALLBACK2(FOR)                                               \
+do {                                                                 \
+  if (settings->on_##FOR) {                                          \
+    if (0 != settings->on_##FOR(parser)) return (p - data);          \
+  }                                                                  \
+} while (0)
+
+
+#define MARK(FOR)                                                    \
+do {                                                                 \
+  FOR##_mark = p;                                                    \
+} while (0)
+
+#define CALLBACK_NOCLEAR(FOR)                                        \
+do {                                                                 \
+  if (FOR##_mark) {                                                  \
+    if (settings->on_##FOR) {                                        \
+      if (0 != settings->on_##FOR(parser,                            \
+                                 FOR##_mark,                         \
+                                 p - FOR##_mark))                    \
+      {                                                              \
+        return (p - data);                                           \
+      }                                                              \
+    }                                                                \
+  }                                                                  \
+} while (0)
+
+
+#define CALLBACK(FOR)                                                \
+do {                                                                 \
+  CALLBACK_NOCLEAR(FOR);                                             \
+  FOR##_mark = NULL;                                                 \
+} while (0)
+
+
+#define PROXY_CONNECTION "proxy-connection"
+#define CONNECTION "connection"
+#define CONTENT_LENGTH "content-length"
+#define TRANSFER_ENCODING "transfer-encoding"
+#define UPGRADE "upgrade"
+#define CHUNKED "chunked"
+#define KEEP_ALIVE "keep-alive"
+#define CLOSE "close"
+
+
+static const char *method_strings[] =
+  { "DELETE"
+  , "GET"
+  , "HEAD"
+  , "POST"
+  , "PUT"
+  , "CONNECT"
+  , "OPTIONS"
+  , "TRACE"
+  , "COPY"
+  , "LOCK"
+  , "MKCOL"
+  , "MOVE"
+  , "PROPFIND"
+  , "PROPPATCH"
+  , "UNLOCK"
+  , "REPORT"
+  , "MKACTIVITY"
+  , "CHECKOUT"
+  , "MERGE"
+  , "M-SEARCH"
+  , "NOTIFY"
+  , "SUBSCRIBE"
+  , "UNSUBSCRIBE"
+  };
+
+
+/* Tokens as defined by rfc 2616. Also lowercases them.
+ *        token       = 1*<any CHAR except CTLs or separators>
+ *     separators     = "(" | ")" | "<" | ">" | "@"
+ *                    | "," | ";" | ":" | "\" | <">
+ *                    | "/" | "[" | "]" | "?" | "="
+ *                    | "{" | "}" | SP | HT
+ */
+static const char tokens[256] = {
+/*   0 nul    1 soh    2 stx    3 etx    4 eot    5 enq    6 ack    7 bel  */
+        0,       0,       0,       0,       0,       0,       0,       0,
+/*   8 bs     9 ht    10 nl    11 vt    12 np    13 cr    14 so    15 si   */
+        0,       0,       0,       0,       0,       0,       0,       0,
+/*  16 dle   17 dc1   18 dc2   19 dc3   20 dc4   21 nak   22 syn   23 etb */
+        0,       0,       0,       0,       0,       0,       0,       0,
+/*  24 can   25 em    26 sub   27 esc   28 fs    29 gs    30 rs    31 us  */
+        0,       0,       0,       0,       0,       0,       0,       0,
+/*  32 sp    33  !    34  "    35  #    36  $    37  %    38  &    39  '  */
+       ' ',      '!',     '"',     '#',     '$',     '%',     '&',    '\'',
+/*  40  (    41  )    42  *    43  +    44  ,    45  -    46  .    47  /  */
+        0,       0,      '*',     '+',      0,      '-',     '.',     '/',
+/*  48  0    49  1    50  2    51  3    52  4    53  5    54  6    55  7  */
+       '0',     '1',     '2',     '3',     '4',     '5',     '6',     '7',
+/*  56  8    57  9    58  :    59  ;    60  <    61  =    62  >    63  ?  */
+       '8',     '9',      0,       0,       0,       0,       0,       0,
+/*  64  @    65  A    66  B    67  C    68  D    69  E    70  F    71  G  */
+        0,      'a',     'b',     'c',     'd',     'e',     'f',     'g',
+/*  72  H    73  I    74  J    75  K    76  L    77  M    78  N    79  O  */
+       'h',     'i',     'j',     'k',     'l',     'm',     'n',     'o',
+/*  80  P    81  Q    82  R    83  S    84  T    85  U    86  V    87  W  */
+       'p',     'q',     'r',     's',     't',     'u',     'v',     'w',
+/*  88  X    89  Y    90  Z    91  [    92  \    93  ]    94  ^    95  _  */
+       'x',     'y',     'z',      0,       0,       0,      '^',     '_',
+/*  96  `    97  a    98  b    99  c   100  d   101  e   102  f   103  g  */
+       '`',     'a',     'b',     'c',     'd',     'e',     'f',     'g',
+/* 104  h   105  i   106  j   107  k   108  l   109  m   110  n   111  o  */
+       'h',     'i',     'j',     'k',     'l',     'm',     'n',     'o',
+/* 112  p   113  q   114  r   115  s   116  t   117  u   118  v   119  w  */
+       'p',     'q',     'r',     's',     't',     'u',     'v',     'w',
+/* 120  x   121  y   122  z   123  {   124  |   125  }   126  ~   127 del */
+       'x',     'y',     'z',      0,      '|',     '}',     '~',       0 };
+
+
+static const int8_t unhex[256] =
+  {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+  , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1
+  ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
+  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+  ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
+  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+  };
+
+
+static const uint8_t normal_url_char[256] = {
+/*   0 nul    1 soh    2 stx    3 etx    4 eot    5 enq    6 ack    7 bel  */
+        0,       0,       0,       0,       0,       0,       0,       0,
+/*   8 bs     9 ht    10 nl    11 vt    12 np    13 cr    14 so    15 si   */
+        0,       0,       0,       0,       0,       0,       0,       0,
+/*  16 dle   17 dc1   18 dc2   19 dc3   20 dc4   21 nak   22 syn   23 etb */
+        0,       0,       0,       0,       0,       0,       0,       0,
+/*  24 can   25 em    26 sub   27 esc   28 fs    29 gs    30 rs    31 us  */
+        0,       0,       0,       0,       0,       0,       0,       0,
+/*  32 sp    33  !    34  "    35  #    36  $    37  %    38  &    39  '  */
+        0,       1,       1,       0,       1,       1,       1,       1,
+/*  40  (    41  )    42  *    43  +    44  ,    45  -    46  .    47  /  */
+        1,       1,       1,       1,       1,       1,       1,       1,
+/*  48  0    49  1    50  2    51  3    52  4    53  5    54  6    55  7  */
+        1,       1,       1,       1,       1,       1,       1,       1,
+/*  56  8    57  9    58  :    59  ;    60  <    61  =    62  >    63  ?  */
+        1,       1,       1,       1,       1,       1,       1,       0,
+/*  64  @    65  A    66  B    67  C    68  D    69  E    70  F    71  G  */
+        1,       1,       1,       1,       1,       1,       1,       1,
+/*  72  H    73  I    74  J    75  K    76  L    77  M    78  N    79  O  */
+        1,       1,       1,       1,       1,       1,       1,       1,
+/*  80  P    81  Q    82  R    83  S    84  T    85  U    86  V    87  W  */
+        1,       1,       1,       1,       1,       1,       1,       1,
+/*  88  X    89  Y    90  Z    91  [    92  \    93  ]    94  ^    95  _  */
+        1,       1,       1,       1,       1,       1,       1,       1,
+/*  96  `    97  a    98  b    99  c   100  d   101  e   102  f   103  g  */
+        1,       1,       1,       1,       1,       1,       1,       1,
+/* 104  h   105  i   106  j   107  k   108  l   109  m   110  n   111  o  */
+        1,       1,       1,       1,       1,       1,       1,       1,
+/* 112  p   113  q   114  r   115  s   116  t   117  u   118  v   119  w  */
+        1,       1,       1,       1,       1,       1,       1,       1,
+/* 120  x   121  y   122  z   123  {   124  |   125  }   126  ~   127 del */
+        1,       1,       1,       1,       1,       1,       1,       0 };
+
+
+enum state
+  { s_dead = 1 /* important that this is > 0 */
+
+  , s_start_req_or_res
+  , s_res_or_resp_H
+  , s_start_res
+  , s_res_H
+  , s_res_HT
+  , s_res_HTT
+  , s_res_HTTP
+  , s_res_first_http_major
+  , s_res_http_major
+  , s_res_first_http_minor
+  , s_res_http_minor
+  , s_res_first_status_code
+  , s_res_status_code
+  , s_res_status
+  , s_res_line_almost_done
+
+  , s_start_req
+
+  , s_req_method
+  , s_req_spaces_before_url
+  , s_req_schema
+  , s_req_schema_slash
+  , s_req_schema_slash_slash
+  , s_req_host
+  , s_req_port
+  , s_req_path
+  , s_req_query_string_start
+  , s_req_query_string
+  , s_req_fragment_start
+  , s_req_fragment
+  , s_req_http_start
+  , s_req_http_H
+  , s_req_http_HT
+  , s_req_http_HTT
+  , s_req_http_HTTP
+  , s_req_first_http_major
+  , s_req_http_major
+  , s_req_first_http_minor
+  , s_req_http_minor
+  , s_req_line_almost_done
+
+  , s_header_field_start
+  , s_header_field
+  , s_header_value_start
+  , s_header_value
+
+  , s_header_almost_done
+
+  , s_headers_almost_done
+  /* Important: 's_headers_almost_done' must be the last 'header' state. All
+   * states beyond this must be 'body' states. It is used for overflow
+   * checking. See the PARSING_HEADER() macro.
+   */
+  , s_chunk_size_start
+  , s_chunk_size
+  , s_chunk_size_almost_done
+  , s_chunk_parameters
+  , s_chunk_data
+  , s_chunk_data_almost_done
+  , s_chunk_data_done
+
+  , s_body_identity
+  , s_body_identity_eof
+  };
+
+
+#define PARSING_HEADER(state) (state <= s_headers_almost_done && 0 == (parser->flags & F_TRAILING))
+
+
+enum header_states
+  { h_general = 0
+  , h_C
+  , h_CO
+  , h_CON
+
+  , h_matching_connection
+  , h_matching_proxy_connection
+  , h_matching_content_length
+  , h_matching_transfer_encoding
+  , h_matching_upgrade
+
+  , h_connection
+  , h_content_length
+  , h_transfer_encoding
+  , h_upgrade
+
+  , h_matching_transfer_encoding_chunked
+  , h_matching_connection_keep_alive
+  , h_matching_connection_close
+
+  , h_transfer_encoding_chunked
+  , h_connection_keep_alive
+  , h_connection_close
+  };
+
+
+enum flags
+  { F_CHUNKED               = 1 << 0
+  , F_CONNECTION_KEEP_ALIVE = 1 << 1
+  , F_CONNECTION_CLOSE      = 1 << 2
+  , F_TRAILING              = 1 << 3
+  , F_UPGRADE               = 1 << 4
+  , F_SKIPBODY              = 1 << 5
+  };
+
+
+#define CR '\r'
+#define LF '\n'
+#define LOWER(c) (unsigned char)(c | 0x20)
+#define TOKEN(c) tokens[(unsigned char)c]
+
+
+#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res)
+
+
+#if HTTP_PARSER_STRICT
+# define STRICT_CHECK(cond) if (cond) goto error
+# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead)
+#else
+# define STRICT_CHECK(cond)
+# define NEW_MESSAGE() start_state
+#endif
+
+
+size_t http_parser_execute (http_parser *parser,
+                            const http_parser_settings *settings,
+                            const char *data,
+                            size_t len)
+{
+  char c, ch;
+  const char *p = data, *pe;
+  int64_t to_read;
+
+  enum state state = (enum state) parser->state;
+  enum header_states header_state = (enum header_states) parser->header_state;
+  uint64_t index = parser->index;
+  uint64_t nread = parser->nread;
+
+  if (len == 0) {
+    if (state == s_body_identity_eof) {
+      CALLBACK2(message_complete);
+    }
+    return 0;
+  }
+
+  /* technically we could combine all of these (except for url_mark) into one
+     variable, saving stack space, but it seems more clear to have them
+     separated. */
+  const char *header_field_mark = 0;
+  const char *header_value_mark = 0;
+  const char *fragment_mark = 0;
+  const char *query_string_mark = 0;
+  const char *path_mark = 0;
+  const char *url_mark = 0;
+
+  if (state == s_header_field)
+    header_field_mark = data;
+  if (state == s_header_value)
+    header_value_mark = data;
+  if (state == s_req_fragment)
+    fragment_mark = data;
+  if (state == s_req_query_string)
+    query_string_mark = data;
+  if (state == s_req_path)
+    path_mark = data;
+  if (state == s_req_path || state == s_req_schema || state == s_req_schema_slash
+      || state == s_req_schema_slash_slash || state == s_req_port
+      || state == s_req_query_string_start || state == s_req_query_string
+      || state == s_req_host
+      || state == s_req_fragment_start || state == s_req_fragment)
+    url_mark = data;
+
+  for (p=data, pe=data+len; p != pe; p++) {
+    ch = *p;
+
+    if (PARSING_HEADER(state)) {
+      ++nread;
+      /* Buffer overflow attack */
+      if (nread > HTTP_MAX_HEADER_SIZE) goto error;
+    }
+
+    switch (state) {
+
+      case s_dead:
+        /* this state is used after a 'Connection: close' message
+         * the parser will error out if it reads another message
+         */
+        goto error;
+
+      case s_start_req_or_res:
+      {
+        if (ch == CR || ch == LF)
+          break;
+        parser->flags = 0;
+        parser->content_length = -1;
+
+        CALLBACK2(message_begin);
+
+        if (ch == 'H')
+          state = s_res_or_resp_H;
+        else {
+          parser->type = HTTP_REQUEST;
+          goto start_req_method_assign;
+        }
+        break;
+      }
+
+      case s_res_or_resp_H:
+        if (ch == 'T') {
+          parser->type = HTTP_RESPONSE;
+          state = s_res_HT;
+        } else {
+          if (ch != 'E') goto error;
+          parser->type = HTTP_REQUEST;
+          parser->method = HTTP_HEAD;
+          index = 2;
+          state = s_req_method;
+        }
+        break;
+
+      case s_start_res:
+      {
+        parser->flags = 0;
+        parser->content_length = -1;
+
+        CALLBACK2(message_begin);
+
+        switch (ch) {
+          case 'H':
+            state = s_res_H;
+            break;
+
+          case CR:
+          case LF:
+            break;
+
+          default:
+            goto error;
+        }
+        break;
+      }
+
+      case s_res_H:
+        STRICT_CHECK(ch != 'T');
+        state = s_res_HT;
+        break;
+
+      case s_res_HT:
+        STRICT_CHECK(ch != 'T');
+        state = s_res_HTT;
+        break;
+
+      case s_res_HTT:
+        STRICT_CHECK(ch != 'P');
+        state = s_res_HTTP;
+        break;
+
+      case s_res_HTTP:
+        STRICT_CHECK(ch != '/');
+        state = s_res_first_http_major;
+        break;
+
+      case s_res_first_http_major:
+        if (ch < '1' || ch > '9') goto error;
+        parser->http_major = ch - '0';
+        state = s_res_http_major;
+        break;
+
+      /* major HTTP version or dot */
+      case s_res_http_major:
+      {
+        if (ch == '.') {
+          state = s_res_first_http_minor;
+          break;
+        }
+
+        if (ch < '0' || ch > '9') goto error;
+
+        parser->http_major *= 10;
+        parser->http_major += ch - '0';
+
+        if (parser->http_major > 999) goto error;
+        break;
+      }
+
+      /* first digit of minor HTTP version */
+      case s_res_first_http_minor:
+        if (ch < '0' || ch > '9') goto error;
+        parser->http_minor = ch - '0';
+        state = s_res_http_minor;
+        break;
+
+      /* minor HTTP version or end of request line */
+      case s_res_http_minor:
+      {
+        if (ch == ' ') {
+          state = s_res_first_status_code;
+          break;
+        }
+
+        if (ch < '0' || ch > '9') goto error;
+
+        parser->http_minor *= 10;
+        parser->http_minor += ch - '0';
+
+        if (parser->http_minor > 999) goto error;
+        break;
+      }
+
+      case s_res_first_status_code:
+      {
+        if (ch < '0' || ch > '9') {
+          if (ch == ' ') {
+            break;
+          }
+          goto error;
+        }
+        parser->status_code = ch - '0';
+        state = s_res_status_code;
+        break;
+      }
+
+      case s_res_status_code:
+      {
+        if (ch < '0' || ch > '9') {
+          switch (ch) {
+            case ' ':
+              state = s_res_status;
+              break;
+            case CR:
+              state = s_res_line_almost_done;
+              break;
+            case LF:
+              state = s_header_field_start;
+              break;
+            default:
+              goto error;
+          }
+          break;
+        }
+
+        parser->status_code *= 10;
+        parser->status_code += ch - '0';
+
+        if (parser->status_code > 999) goto error;
+        break;
+      }
+
+      case s_res_status:
+        /* the human readable status. e.g. "NOT FOUND"
+         * we are not humans so just ignore this */
+        if (ch == CR) {
+          state = s_res_line_almost_done;
+          break;
+        }
+
+        if (ch == LF) {
+          state = s_header_field_start;
+          break;
+        }
+        break;
+
+      case s_res_line_almost_done:
+        STRICT_CHECK(ch != LF);
+        state = s_header_field_start;
+        break;
+
+      case s_start_req:
+      {
+        if (ch == CR || ch == LF)
+          break;
+        parser->flags = 0;
+        parser->content_length = -1;
+
+        CALLBACK2(message_begin);
+
+        if (ch < 'A' || 'Z' < ch) goto error;
+
+      start_req_method_assign:
+        parser->method = (enum http_method) 0;
+        index = 1;
+        switch (ch) {
+          case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break;
+          case 'D': parser->method = HTTP_DELETE; break;
+          case 'G': parser->method = HTTP_GET; break;
+          case 'H': parser->method = HTTP_HEAD; break;
+          case 'L': parser->method = HTTP_LOCK; break;
+          case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH */ break;
+          case 'N': parser->method = HTTP_NOTIFY; break;
+          case 'O': parser->method = HTTP_OPTIONS; break;
+          case 'P': parser->method = HTTP_POST; /* or PROPFIND or PROPPATCH or PUT */ break;
+          case 'R': parser->method = HTTP_REPORT; break;
+          case 'S': parser->method = HTTP_SUBSCRIBE; break;
+          case 'T': parser->method = HTTP_TRACE; break;
+          case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break;
+          default: goto error;
+        }
+        state = s_req_method;
+        break;
+      }
+
+      case s_req_method:
+      {
+        if (ch == '\0')
+          goto error;
+
+        const char *matcher = method_strings[parser->method];
+        if (ch == ' ' && matcher[index] == '\0') {
+          state = s_req_spaces_before_url;
+        } else if (ch == matcher[index]) {
+          ; /* nada */
+        } else if (parser->method == HTTP_CONNECT) {
+          if (index == 1 && ch == 'H') {
+            parser->method = HTTP_CHECKOUT;
+          } else if (index == 2  && ch == 'P') {
+            parser->method = HTTP_COPY;
+          }
+        } else if (parser->method == HTTP_MKCOL) {
+          if (index == 1 && ch == 'O') {
+            parser->method = HTTP_MOVE;
+          } else if (index == 1 && ch == 'E') {
+            parser->method = HTTP_MERGE;
+          } else if (index == 1 && ch == '-') {
+            parser->method = HTTP_MSEARCH;
+          } else if (index == 2 && ch == 'A') {
+            parser->method = HTTP_MKACTIVITY;
+          }
+        } else if (index == 1 && parser->method == HTTP_POST && ch == 'R') {
+          parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */
+        } else if (index == 1 && parser->method == HTTP_POST && ch == 'U') {
+          parser->method = HTTP_PUT;
+        } else if (index == 2 && parser->method == HTTP_UNLOCK && ch == 'S') {
+          parser->method = HTTP_UNSUBSCRIBE;
+        } else if (index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') {
+          parser->method = HTTP_PROPPATCH;
+        } else {
+          goto error;
+        }
+
+        ++index;
+        break;
+      }
+      case s_req_spaces_before_url:
+      {
+        if (ch == ' ') break;
+
+        if (ch == '/' || ch == '*') {
+          MARK(url);
+          MARK(path);
+          state = s_req_path;
+          break;
+        }
+
+        c = LOWER(ch);
+
+        if (c >= 'a' && c <= 'z') {
+          MARK(url);
+          state = s_req_schema;
+          break;
+        }
+
+        goto error;
+      }
+
+      case s_req_schema:
+      {
+        c = LOWER(ch);
+
+        if (c >= 'a' && c <= 'z') break;
+
+        if (ch == ':') {
+          state = s_req_schema_slash;
+          break;
+        } else if (ch == '.') {
+          state = s_req_host;
+          break;
+        } else if ('0' <= ch && ch <= '9') {
+          state = s_req_host;
+          break;
+        }
+
+        goto error;
+      }
+
+      case s_req_schema_slash:
+        STRICT_CHECK(ch != '/');
+        state = s_req_schema_slash_slash;
+        break;
+
+      case s_req_schema_slash_slash:
+        STRICT_CHECK(ch != '/');
+        state = s_req_host;
+        break;
+
+      case s_req_host:
+      {
+        c = LOWER(ch);
+        if (c >= 'a' && c <= 'z') break;
+        if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-') break;
+        switch (ch) {
+          case ':':
+            state = s_req_port;
+            break;
+          case '/':
+            MARK(path);
+            state = s_req_path;
+            break;
+          case ' ':
+            /* The request line looks like:
+             *   "GET http://foo.bar.com HTTP/1.1"
+             * That is, there is no path.
+             */
+            CALLBACK(url);
+            state = s_req_http_start;
+            break;
+          default:
+            goto error;
+        }
+        break;
+      }
+
+      case s_req_port:
+      {
+        if (ch >= '0' && ch <= '9') break;
+        switch (ch) {
+          case '/':
+            MARK(path);
+            state = s_req_path;
+            break;
+          case ' ':
+            /* The request line looks like:
+             *   "GET http://foo.bar.com:1234 HTTP/1.1"
+             * That is, there is no path.
+             */
+            CALLBACK(url);
+            state = s_req_http_start;
+            break;
+          default:
+            goto error;
+        }
+        break;
+      }
+
+      case s_req_path:
+      {
+        if (normal_url_char[(unsigned char)ch]) break;
+
+        switch (ch) {
+          case ' ':
+            CALLBACK(url);
+            CALLBACK(path);
+            state = s_req_http_start;
+            break;
+          case CR:
+            CALLBACK(url);
+            CALLBACK(path);
+            parser->http_major = 0;
+            parser->http_minor = 9;
+            state = s_req_line_almost_done;
+            break;
+          case LF:
+            CALLBACK(url);
+            CALLBACK(path);
+            parser->http_major = 0;
+            parser->http_minor = 9;
+            state = s_header_field_start;
+            break;
+          case '?':
+            CALLBACK(path);
+            state = s_req_query_string_start;
+            break;
+          case '#':
+            CALLBACK(path);
+            state = s_req_fragment_start;
+            break;
+          default:
+            goto error;
+        }
+        break;
+      }
+
+      case s_req_query_string_start:
+      {
+        if (normal_url_char[(unsigned char)ch]) {
+          MARK(query_string);
+          state = s_req_query_string;
+          break;
+        }
+
+        switch (ch) {
+          case '?':
+            break; /* XXX ignore extra '?' ... is this right? */
+          case ' ':
+            CALLBACK(url);
+            state = s_req_http_start;
+            break;
+          case CR:
+            CALLBACK(url);
+            parser->http_major = 0;
+            parser->http_minor = 9;
+            state = s_req_line_almost_done;
+            break;
+          case LF:
+            CALLBACK(url);
+            parser->http_major = 0;
+            parser->http_minor = 9;
+            state = s_header_field_start;
+            break;
+          case '#':
+            state = s_req_fragment_start;
+            break;
+          default:
+            goto error;
+        }
+        break;
+      }
+
+      case s_req_query_string:
+      {
+        if (normal_url_char[(unsigned char)ch]) break;
+
+        switch (ch) {
+          case '?':
+            /* allow extra '?' in query string */
+            break;
+          case ' ':
+            CALLBACK(url);
+            CALLBACK(query_string);
+            state = s_req_http_start;
+            break;
+          case CR:
+            CALLBACK(url);
+            CALLBACK(query_string);
+            parser->http_major = 0;
+            parser->http_minor = 9;
+            state = s_req_line_almost_done;
+            break;
+          case LF:
+            CALLBACK(url);
+            CALLBACK(query_string);
+            parser->http_major = 0;
+            parser->http_minor = 9;
+            state = s_header_field_start;
+            break;
+          case '#':
+            CALLBACK(query_string);
+            state = s_req_fragment_start;
+            break;
+          default:
+            goto error;
+        }
+        break;
+      }
+
+      case s_req_fragment_start:
+      {
+        if (normal_url_char[(unsigned char)ch]) {
+          MARK(fragment);
+          state = s_req_fragment;
+          break;
+        }
+
+        switch (ch) {
+          case ' ':
+            CALLBACK(url);
+            state = s_req_http_start;
+            break;
+          case CR:
+            CALLBACK(url);
+            parser->http_major = 0;
+            parser->http_minor = 9;
+            state = s_req_line_almost_done;
+            break;
+          case LF:
+            CALLBACK(url);
+            parser->http_major = 0;
+            parser->http_minor = 9;
+            state = s_header_field_start;
+            break;
+          case '?':
+            MARK(fragment);
+            state = s_req_fragment;
+            break;
+          case '#':
+            break;
+          default:
+            goto error;
+        }
+        break;
+      }
+
+      case s_req_fragment:
+      {
+        if (normal_url_char[(unsigned char)ch]) break;
+
+        switch (ch) {
+          case ' ':
+            CALLBACK(url);
+            CALLBACK(fragment);
+            state = s_req_http_start;
+            break;
+          case CR:
+            CALLBACK(url);
+            CALLBACK(fragment);
+            parser->http_major = 0;
+            parser->http_minor = 9;
+            state = s_req_line_almost_done;
+            break;
+          case LF:
+            CALLBACK(url);
+            CALLBACK(fragment);
+            parser->http_major = 0;
+            parser->http_minor = 9;
+            state = s_header_field_start;
+            break;
+          case '?':
+          case '#':
+            break;
+          default:
+            goto error;
+        }
+        break;
+      }
+
+      case s_req_http_start:
+        switch (ch) {
+          case 'H':
+            state = s_req_http_H;
+            break;
+          case ' ':
+            break;
+          default:
+            goto error;
+        }
+        break;
+
+      case s_req_http_H:
+        STRICT_CHECK(ch != 'T');
+        state = s_req_http_HT;
+        break;
+
+      case s_req_http_HT:
+        STRICT_CHECK(ch != 'T');
+        state = s_req_http_HTT;
+        break;
+
+      case s_req_http_HTT:
+        STRICT_CHECK(ch != 'P');
+        state = s_req_http_HTTP;
+        break;
+
+      case s_req_http_HTTP:
+        STRICT_CHECK(ch != '/');
+        state = s_req_first_http_major;
+        break;
+
+      /* first digit of major HTTP version */
+      case s_req_first_http_major:
+        if (ch < '1' || ch > '9') goto error;
+        parser->http_major = ch - '0';
+        state = s_req_http_major;
+        break;
+
+      /* major HTTP version or dot */
+      case s_req_http_major:
+      {
+        if (ch == '.') {
+          state = s_req_first_http_minor;
+          break;
+        }
+
+        if (ch < '0' || ch > '9') goto error;
+
+        parser->http_major *= 10;
+        parser->http_major += ch - '0';
+
+        if (parser->http_major > 999) goto error;
+        break;
+      }
+
+      /* first digit of minor HTTP version */
+      case s_req_first_http_minor:
+        if (ch < '0' || ch > '9') goto error;
+        parser->http_minor = ch - '0';
+        state = s_req_http_minor;
+        break;
+
+      /* minor HTTP version or end of request line */
+      case s_req_http_minor:
+      {
+        if (ch == CR) {
+          state = s_req_line_almost_done;
+          break;
+        }
+
+        if (ch == LF) {
+          state = s_header_field_start;
+          break;
+        }
+
+        /* XXX allow spaces after digit? */
+
+        if (ch < '0' || ch > '9') goto error;
+
+        parser->http_minor *= 10;
+        parser->http_minor += ch - '0';
+
+        if (parser->http_minor > 999) goto error;
+        break;
+      }
+
+      /* end of request line */
+      case s_req_line_almost_done:
+      {
+        if (ch != LF) goto error;
+        state = s_header_field_start;
+        break;
+      }
+
+      case s_header_field_start:
+      {
+        if (ch == CR) {
+          state = s_headers_almost_done;
+          break;
+        }
+
+        if (ch == LF) {
+          /* they might be just sending \n instead of \r\n so this would be
+           * the second \n to denote the end of headers*/
+          state = s_headers_almost_done;
+          goto headers_almost_done;
+        }
+
+        c = TOKEN(ch);
+
+        if (!c) goto error;
+
+        MARK(header_field);
+
+        index = 0;
+        state = s_header_field;
+
+        switch (c) {
+          case 'c':
+            header_state = h_C;
+            break;
+
+          case 'p':
+            header_state = h_matching_proxy_connection;
+            break;
+
+          case 't':
+            header_state = h_matching_transfer_encoding;
+            break;
+
+          case 'u':
+            header_state = h_matching_upgrade;
+            break;
+
+          default:
+            header_state = h_general;
+            break;
+        }
+        break;
+      }
+
+      case s_header_field:
+      {
+        c = TOKEN(ch);
+
+        if (c) {
+          switch (header_state) {
+            case h_general:
+              break;
+
+            case h_C:
+              index++;
+              header_state = (c == 'o' ? h_CO : h_general);
+              break;
+
+            case h_CO:
+              index++;
+              header_state = (c == 'n' ? h_CON : h_general);
+              break;
+
+            case h_CON:
+              index++;
+              switch (c) {
+                case 'n':
+                  header_state = h_matching_connection;
+                  break;
+                case 't':
+                  header_state = h_matching_content_length;
+                  break;
+                default:
+                  header_state = h_general;
+                  break;
+              }
+              break;
+
+            /* connection */
+
+            case h_matching_connection:
+              index++;
+              if (index > sizeof(CONNECTION)-1
+                  || c != CONNECTION[index]) {
+                header_state = h_general;
+              } else if (index == sizeof(CONNECTION)-2) {
+                header_state = h_connection;
+              }
+              break;
+
+            /* proxy-connection */
+
+            case h_matching_proxy_connection:
+              index++;
+              if (index > sizeof(PROXY_CONNECTION)-1
+                  || c != PROXY_CONNECTION[index]) {
+                header_state = h_general;
+              } else if (index == sizeof(PROXY_CONNECTION)-2) {
+                header_state = h_connection;
+              }
+              break;
+
+            /* content-length */
+
+            case h_matching_content_length:
+              index++;
+              if (index > sizeof(CONTENT_LENGTH)-1
+                  || c != CONTENT_LENGTH[index]) {
+                header_state = h_general;
+              } else if (index == sizeof(CONTENT_LENGTH)-2) {
+                header_state = h_content_length;
+              }
+              break;
+
+            /* transfer-encoding */
+
+            case h_matching_transfer_encoding:
+              index++;
+              if (index > sizeof(TRANSFER_ENCODING)-1
+                  || c != TRANSFER_ENCODING[index]) {
+                header_state = h_general;
+              } else if (index == sizeof(TRANSFER_ENCODING)-2) {
+                header_state = h_transfer_encoding;
+              }
+              break;
+
+            /* upgrade */
+
+            case h_matching_upgrade:
+              index++;
+              if (index > sizeof(UPGRADE)-1
+                  || c != UPGRADE[index]) {
+                header_state = h_general;
+              } else if (index == sizeof(UPGRADE)-2) {
+                header_state = h_upgrade;
+              }
+              break;
+
+            case h_connection:
+            case h_content_length:
+            case h_transfer_encoding:
+            case h_upgrade:
+              if (ch != ' ') header_state = h_general;
+              break;
+
+            default:
+              assert(0 && "Unknown header_state");
+              break;
+          }
+          break;
+        }
+
+        if (ch == ':') {
+          CALLBACK(header_field);
+          state = s_header_value_start;
+          break;
+        }
+
+        if (ch == CR) {
+          state = s_header_almost_done;
+          CALLBACK(header_field);
+          break;
+        }
+
+        if (ch == LF) {
+          CALLBACK(header_field);
+          state = s_header_field_start;
+          break;
+        }
+
+        goto error;
+      }
+
+      case s_header_value_start:
+      {
+        if (ch == ' ') break;
+
+        MARK(header_value);
+
+        state = s_header_value;
+        index = 0;
+
+        c = LOWER(ch);
+
+        if (ch == CR) {
+          CALLBACK(header_value);
+          header_state = h_general;
+          state = s_header_almost_done;
+          break;
+        }
+
+        if (ch == LF) {
+          CALLBACK(header_value);
+          state = s_header_field_start;
+          break;
+        }
+
+        switch (header_state) {
+          case h_upgrade:
+            parser->flags |= F_UPGRADE;
+            header_state = h_general;
+            break;
+
+          case h_transfer_encoding:
+            /* looking for 'Transfer-Encoding: chunked' */
+            if ('c' == c) {
+              header_state = h_matching_transfer_encoding_chunked;
+            } else {
+              header_state = h_general;
+            }
+            break;
+
+          case h_content_length:
+            if (ch < '0' || ch > '9') goto error;
+            parser->content_length = ch - '0';
+            break;
+
+          case h_connection:
+            /* looking for 'Connection: keep-alive' */
+            if (c == 'k') {
+              header_state = h_matching_connection_keep_alive;
+            /* looking for 'Connection: close' */
+            } else if (c == 'c') {
+              header_state = h_matching_connection_close;
+            } else {
+              header_state = h_general;
+            }
+            break;
+
+          default:
+            header_state = h_general;
+            break;
+        }
+        break;
+      }
+
+      case s_header_value:
+      {
+        c = LOWER(ch);
+
+        if (ch == CR) {
+          CALLBACK(header_value);
+          state = s_header_almost_done;
+          break;
+        }
+
+        if (ch == LF) {
+          CALLBACK(header_value);
+          goto header_almost_done;
+        }
+
+        switch (header_state) {
+          case h_general:
+            break;
+
+          case h_connection:
+          case h_transfer_encoding:
+            assert(0 && "Shouldn't get here.");
+            break;
+
+          case h_content_length:
+            if (ch == ' ') break;
+            if (ch < '0' || ch > '9') goto error;
+            parser->content_length *= 10;
+            parser->content_length += ch - '0';
+            break;
+
+          /* Transfer-Encoding: chunked */
+          case h_matching_transfer_encoding_chunked:
+            index++;
+            if (index > sizeof(CHUNKED)-1
+                || c != CHUNKED[index]) {
+              header_state = h_general;
+            } else if (index == sizeof(CHUNKED)-2) {
+              header_state = h_transfer_encoding_chunked;
+            }
+            break;
+
+          /* looking for 'Connection: keep-alive' */
+          case h_matching_connection_keep_alive:
+            index++;
+            if (index > sizeof(KEEP_ALIVE)-1
+                || c != KEEP_ALIVE[index]) {
+              header_state = h_general;
+            } else if (index == sizeof(KEEP_ALIVE)-2) {
+              header_state = h_connection_keep_alive;
+            }
+            break;
+
+          /* looking for 'Connection: close' */
+          case h_matching_connection_close:
+            index++;
+            if (index > sizeof(CLOSE)-1 || c != CLOSE[index]) {
+              header_state = h_general;
+            } else if (index == sizeof(CLOSE)-2) {
+              header_state = h_connection_close;
+            }
+            break;
+
+          case h_transfer_encoding_chunked:
+          case h_connection_keep_alive:
+          case h_connection_close:
+            if (ch != ' ') header_state = h_general;
+            break;
+
+          default:
+            state = s_header_value;
+            header_state = h_general;
+            break;
+        }
+        break;
+      }
+
+      case s_header_almost_done:
+      header_almost_done:
+      {
+        STRICT_CHECK(ch != LF);
+
+        state = s_header_field_start;
+
+        switch (header_state) {
+          case h_connection_keep_alive:
+            parser->flags |= F_CONNECTION_KEEP_ALIVE;
+            break;
+          case h_connection_close:
+            parser->flags |= F_CONNECTION_CLOSE;
+            break;
+          case h_transfer_encoding_chunked:
+            parser->flags |= F_CHUNKED;
+            break;
+          default:
+            break;
+        }
+        break;
+      }
+
+      case s_headers_almost_done:
+      headers_almost_done:
+      {
+        STRICT_CHECK(ch != LF);
+
+        if (parser->flags & F_TRAILING) {
+          /* End of a chunked request */
+          CALLBACK2(message_complete);
+          state = NEW_MESSAGE();
+          break;
+        }
+
+        nread = 0;
+
+        if (parser->flags & F_UPGRADE || parser->method == HTTP_CONNECT) {
+          parser->upgrade = 1;
+        }
+
+        /* Here we call the headers_complete callback. This is somewhat
+         * different than other callbacks because if the user returns 1, we
+         * will interpret that as saying that this message has no body. This
+         * is needed for the annoying case of recieving a response to a HEAD
+         * request.
+         */
+        if (settings->on_headers_complete) {
+          switch (settings->on_headers_complete(parser)) {
+            case 0:
+              break;
+
+            case 1:
+              parser->flags |= F_SKIPBODY;
+              break;
+
+            default:
+              return p - data; /* Error */
+          }
+        }
+
+        /* Exit, the rest of the connect is in a different protocol. */
+        if (parser->upgrade) {
+          CALLBACK2(message_complete);
+          return (p - data);
+        }
+
+        if (parser->flags & F_SKIPBODY) {
+          CALLBACK2(message_complete);
+          state = NEW_MESSAGE();
+        } else if (parser->flags & F_CHUNKED) {
+          /* chunked encoding - ignore Content-Length header */
+          state = s_chunk_size_start;
+        } else {
+          if (parser->content_length == 0) {
+            /* Content-Length header given but zero: Content-Length: 0\r\n */
+            CALLBACK2(message_complete);
+            state = NEW_MESSAGE();
+          } else if (parser->content_length > 0) {
+            /* Content-Length header given and non-zero */
+            state = s_body_identity;
+          } else {
+            if (parser->type == HTTP_REQUEST || http_should_keep_alive(parser)) {
+              /* Assume content-length 0 - read the next */
+              CALLBACK2(message_complete);
+              state = NEW_MESSAGE();
+            } else {
+              /* Read body until EOF */
+              state = s_body_identity_eof;
+            }
+          }
+        }
+
+        break;
+      }
+
+      case s_body_identity:
+        to_read = MIN(pe - p, (int64_t)parser->content_length);
+        if (to_read > 0) {
+          if (settings->on_body) settings->on_body(parser, p, to_read);
+          p += to_read - 1;
+          parser->content_length -= to_read;
+          if (parser->content_length == 0) {
+            CALLBACK2(message_complete);
+            state = NEW_MESSAGE();
+          }
+        }
+        break;
+
+      /* read until EOF */
+      case s_body_identity_eof:
+        to_read = pe - p;
+        if (to_read > 0) {
+          if (settings->on_body) settings->on_body(parser, p, to_read);
+          p += to_read - 1;
+        }
+        break;
+
+      case s_chunk_size_start:
+      {
+        assert(parser->flags & F_CHUNKED);
+
+        c = unhex[(unsigned char)ch];
+        if (c == -1) goto error;
+        parser->content_length = c;
+        state = s_chunk_size;
+        break;
+      }
+
+      case s_chunk_size:
+      {
+        assert(parser->flags & F_CHUNKED);
+
+        if (ch == CR) {
+          state = s_chunk_size_almost_done;
+          break;
+        }
+
+        c = unhex[(unsigned char)ch];
+
+        if (c == -1) {
+          if (ch == ';' || ch == ' ') {
+            state = s_chunk_parameters;
+            break;
+          }
+          goto error;
+        }
+
+        parser->content_length *= 16;
+        parser->content_length += c;
+        break;
+      }
+
+      case s_chunk_parameters:
+      {
+        assert(parser->flags & F_CHUNKED);
+        /* just ignore this shit. TODO check for overflow */
+        if (ch == CR) {
+          state = s_chunk_size_almost_done;
+          break;
+        }
+        break;
+      }
+
+      case s_chunk_size_almost_done:
+      {
+        assert(parser->flags & F_CHUNKED);
+        STRICT_CHECK(ch != LF);
+
+        if (parser->content_length == 0) {
+          parser->flags |= F_TRAILING;
+          state = s_header_field_start;
+        } else {
+          state = s_chunk_data;
+        }
+        break;
+      }
+
+      case s_chunk_data:
+      {
+        assert(parser->flags & F_CHUNKED);
+
+        to_read = MIN(pe - p, (int64_t)(parser->content_length));
+
+        if (to_read > 0) {
+          if (settings->on_body) settings->on_body(parser, p, to_read);
+          p += to_read - 1;
+        }
+
+        if (to_read == parser->content_length) {
+          state = s_chunk_data_almost_done;
+        }
+
+        parser->content_length -= to_read;
+        break;
+      }
+
+      case s_chunk_data_almost_done:
+        assert(parser->flags & F_CHUNKED);
+        STRICT_CHECK(ch != CR);
+        state = s_chunk_data_done;
+        break;
+
+      case s_chunk_data_done:
+        assert(parser->flags & F_CHUNKED);
+        STRICT_CHECK(ch != LF);
+        state = s_chunk_size_start;
+        break;
+
+      default:
+        assert(0 && "unhandled state");
+        goto error;
+    }
+  }
+
+  CALLBACK_NOCLEAR(header_field);
+  CALLBACK_NOCLEAR(header_value);
+  CALLBACK_NOCLEAR(fragment);
+  CALLBACK_NOCLEAR(query_string);
+  CALLBACK_NOCLEAR(path);
+  CALLBACK_NOCLEAR(url);
+
+  parser->state = state;
+  parser->header_state = header_state;
+  parser->index = index;
+  parser->nread = nread;
+
+  return len;
+
+error:
+  parser->state = s_dead;
+  return (p - data);
+}
+
+
+int
+http_should_keep_alive (http_parser *parser)
+{
+  if (parser->http_major > 0 && parser->http_minor > 0) {
+    /* HTTP/1.1 */
+    if (parser->flags & F_CONNECTION_CLOSE) {
+      return 0;
+    } else {
+      return 1;
+    }
+  } else {
+    /* HTTP/1.0 or earlier */
+    if (parser->flags & F_CONNECTION_KEEP_ALIVE) {
+      return 1;
+    } else {
+      return 0;
+    }
+  }
+}
+
+
+const char * http_method_str (enum http_method m)
+{
+  return method_strings[m];
+}
+
+
+void
+http_parser_init (http_parser *parser, enum http_parser_type t)
+{
+  parser->type = t;
+  parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res));
+  parser->nread = 0;
+  parser->upgrade = 0;
+  parser->flags = 0;
+  parser->method = 0;
+}
diff --git a/http-parser/http_parser.h b/http-parser/http_parser.h
new file mode 100644 (file)
index 0000000..c03ec05
--- /dev/null
@@ -0,0 +1,181 @@
+/* Copyright 2009,2010 Ryan Dahl <ry@tinyclouds.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#ifndef http_parser_h
+#define http_parser_h
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#include <sys/types.h>
+#if defined(_WIN32) && !defined(__MINGW32__)
+typedef __int8 int8_t;
+typedef unsigned __int8 uint8_t;
+typedef __int16 int16_t;
+typedef unsigned __int16 uint16_t;
+typedef __int32 int32_t;
+typedef unsigned __int32 uint32_t;
+typedef __int64 int64_t;
+typedef unsigned __int64 uint64_t;
+
+typedef unsigned int size_t;
+typedef int ssize_t;
+#else
+#include <stdint.h>
+#endif
+
+/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
+ * faster
+ */
+#ifndef HTTP_PARSER_STRICT
+# define HTTP_PARSER_STRICT 1
+#else
+# define HTTP_PARSER_STRICT 0
+#endif
+
+
+/* Maximium header size allowed */
+#define HTTP_MAX_HEADER_SIZE (80*1024)
+
+
+typedef struct http_parser http_parser;
+typedef struct http_parser_settings http_parser_settings;
+
+
+/* Callbacks should return non-zero to indicate an error. The parser will
+ * then halt execution.
+ *
+ * The one exception is on_headers_complete. In a HTTP_RESPONSE parser
+ * returning '1' from on_headers_complete will tell the parser that it
+ * should not expect a body. This is used when receiving a response to a
+ * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:
+ * chunked' headers that indicate the presence of a body.
+ *
+ * http_data_cb does not return data chunks. It will be call arbitrarally
+ * many times for each string. E.G. you might get 10 callbacks for "on_path"
+ * each providing just a few characters more data.
+ */
+typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
+typedef int (*http_cb) (http_parser*);
+
+
+/* Request Methods */
+enum http_method
+  { HTTP_DELETE    = 0
+  , HTTP_GET
+  , HTTP_HEAD
+  , HTTP_POST
+  , HTTP_PUT
+  /* pathological */
+  , HTTP_CONNECT
+  , HTTP_OPTIONS
+  , HTTP_TRACE
+  /* webdav */
+  , HTTP_COPY
+  , HTTP_LOCK
+  , HTTP_MKCOL
+  , HTTP_MOVE
+  , HTTP_PROPFIND
+  , HTTP_PROPPATCH
+  , HTTP_UNLOCK
+  /* subversion */
+  , HTTP_REPORT
+  , HTTP_MKACTIVITY
+  , HTTP_CHECKOUT
+  , HTTP_MERGE
+  /* upnp */
+  , HTTP_MSEARCH
+  , HTTP_NOTIFY
+  , HTTP_SUBSCRIBE
+  , HTTP_UNSUBSCRIBE
+  };
+
+
+enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };
+
+
+struct http_parser {
+  /** PRIVATE **/
+  unsigned char type : 2;
+  unsigned char flags : 6;
+  unsigned char state;
+  unsigned char header_state;
+  unsigned char index;
+
+  uint32_t nread;
+  int64_t content_length;
+
+  /** READ-ONLY **/
+  unsigned short http_major;
+  unsigned short http_minor;
+  unsigned short status_code; /* responses only */
+  unsigned char method;    /* requests only */
+
+  /* 1 = Upgrade header was present and the parser has exited because of that.
+   * 0 = No upgrade header present.
+   * Should be checked when http_parser_execute() returns in addition to
+   * error checking.
+   */
+  char upgrade;
+
+  /** PUBLIC **/
+  void *data; /* A pointer to get hook to the "connection" or "socket" object */
+};
+
+
+struct http_parser_settings {
+  http_cb      on_message_begin;
+  http_data_cb on_path;
+  http_data_cb on_query_string;
+  http_data_cb on_url;
+  http_data_cb on_fragment;
+  http_data_cb on_header_field;
+  http_data_cb on_header_value;
+  http_cb      on_headers_complete;
+  http_data_cb on_body;
+  http_cb      on_message_complete;
+};
+
+
+void http_parser_init(http_parser *parser, enum http_parser_type type);
+
+
+size_t http_parser_execute(http_parser *parser,
+                           const http_parser_settings *settings,
+                           const char *data,
+                           size_t len);
+
+
+/* If http_should_keep_alive() in the on_headers_complete or
+ * on_message_complete callback returns true, then this will be should be
+ * the last message on the connection.
+ * If you are the server, respond with the "Connection: close" header.
+ * If you are the client, close the connection.
+ */
+int http_should_keep_alive(http_parser *parser);
+
+/* Returns a string version of the HTTP method. */
+const char *http_method_str(enum http_method);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/http.c b/http.c
new file mode 100644 (file)
index 0000000..5bd3143
--- /dev/null
+++ b/http.c
@@ -0,0 +1,330 @@
+#include "http.h"
+#include "server.h"
+#include "worker.h"
+#include "client.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+
+/* HTTP Response */
+
+struct http_response *
+http_response_init(struct worker *w, int code, const char *msg) {
+
+       /* create object */
+       struct http_response *r = calloc(1, sizeof(struct http_response));
+
+       r->code = code;
+       r->msg = msg;
+       r->w = w;
+       r->keep_alive = 0; /* default */
+
+       http_response_set_header(r, "Server", "Webdis");
+
+       /* Cross-Origin Resource Sharing, CORS. */
+       http_response_set_header(r, "Allow", "GET,POST,PUT,OPTIONS");
+       /*
+       Chrome doesn't support Allow and requires 
+       Access-Control-Allow-Methods
+       */
+       http_response_set_header(r, "Access-Control-Allow-Methods", "GET,POST,PUT,OPTIONS");
+       http_response_set_header(r, "Access-Control-Allow-Origin", "*");
+       /* 
+       According to 
+       http://www.w3.org/TR/cors/#access-control-allow-headers-response-header
+       Access-Control-Allow-Headers cannot be a wildcard and must be set
+       with explicit names
+       */
+       http_response_set_header(r, "Access-Control-Allow-Headers", "X-Requested-With, Content-Type");
+
+       return r;
+}
+
+
+void
+http_response_set_header(struct http_response *r, const char *k, const char *v) {
+
+       int i, pos = r->header_count;
+       size_t key_sz = strlen(k);
+       size_t val_sz = strlen(v);
+
+       for(i = 0; i < r->header_count; ++i) {
+               if(strncmp(r->headers[i].key, k, key_sz) == 0) {
+                       pos = i;
+                       /* free old value before replacing it. */
+                       free(r->headers[i].key);
+                       free(r->headers[i].val);
+                       break;
+               }
+       }
+
+       /* extend array */
+       if(pos == r->header_count) {
+               r->headers = realloc(r->headers,
+                               sizeof(struct http_header)*(r->header_count + 1));
+               r->header_count++;
+       }
+
+       /* copy key */
+       r->headers[pos].key = calloc(key_sz + 1, 1);
+       memcpy(r->headers[pos].key, k, key_sz);
+       r->headers[pos].key_sz = key_sz;
+
+       /* copy val */
+       r->headers[pos].val = calloc(val_sz + 1, 1);
+       memcpy(r->headers[pos].val, v, val_sz);
+       r->headers[pos].val_sz = val_sz;
+
+       if(!r->chunked && !strcmp(k, "Transfer-Encoding") && !strcmp(v, "chunked")) {
+               r->chunked = 1;
+       }
+}
+
+void
+http_response_set_body(struct http_response *r, const char *body, size_t body_len) {
+
+       r->body = body;
+       r->body_len = body_len;
+}
+
+static void
+http_response_cleanup(struct http_response *r, int fd, int success) {
+
+       int i;
+
+       /* cleanup buffer */
+       free(r->out);
+       if(!r->keep_alive || !success) {
+               /* Close fd is client doesn't support Keep-Alive. */
+               close(fd);
+       }
+
+       /* cleanup response object */
+       for(i = 0; i < r->header_count; ++i) {
+               free(r->headers[i].key);
+               free(r->headers[i].val);
+       }
+       free(r->headers);
+
+       free(r);
+}
+
+static void
+http_can_write(int fd, short event, void *p) {
+
+       int ret;
+       struct http_response *r = p;
+
+       (void)event;
+       
+       ret = write(fd, r->out + r->sent, r->out_sz - r->sent);
+       
+       if(ret > 0)
+               r->sent += ret;
+
+       if(ret <= 0 || r->out_sz - r->sent == 0) { /* error or done */
+               http_response_cleanup(r, fd, (int)r->out_sz == r->sent ? 1 : 0);
+       } else { /* reschedule write */
+               http_schedule_write(fd, r);
+       }
+}
+
+void
+http_schedule_write(int fd, struct http_response *r) {
+
+       if(r->w) { /* async */
+               event_set(&r->ev, fd, EV_WRITE, http_can_write, r);
+               event_base_set(r->w->base, &r->ev);
+               event_add(&r->ev, NULL);
+       } else { /* blocking */
+               http_can_write(fd, 0, r);
+       }
+
+}
+
+static char *
+format_chunk(const char *p, size_t sz, size_t *out_sz) {
+
+       char *out, tmp[64];
+       int chunk_size;
+
+       /* calculate format size */
+       chunk_size = sprintf(tmp, "%x\r\n", (int)sz);
+       
+       *out_sz = chunk_size + sz + 2;
+       out = malloc(*out_sz);
+       memcpy(out, tmp, chunk_size);
+       memcpy(out + chunk_size, p, sz);
+       memcpy(out + chunk_size + sz, "\r\n", 2);
+
+       return out;
+}
+
+void
+http_response_write(struct http_response *r, int fd) {
+
+       char *p;
+       int i, ret;
+
+       /*r->keep_alive = 0;*/
+       r->out_sz = sizeof("HTTP/1.x xxx ")-1 + strlen(r->msg) + 2;
+       r->out = calloc(r->out_sz + 1, 1);
+
+       ret = sprintf(r->out, "HTTP/1.%d %d %s\r\n", (r->http_version?1:0), r->code, r->msg);
+       (void)ret;
+       p = r->out;
+
+       if(!r->chunked) {
+               if(r->code == 200 && r->body) {
+                       char content_length[10];
+                       sprintf(content_length, "%zd", r->body_len);
+                       http_response_set_header(r, "Content-Length", content_length);
+               } else {
+                       http_response_set_header(r, "Content-Length", "0");
+               }
+       }
+
+       for(i = 0; i < r->header_count; ++i) {
+               /* "Key: Value\r\n" */
+               size_t header_sz = r->headers[i].key_sz + 2 + r->headers[i].val_sz + 2;
+               r->out = realloc(r->out, r->out_sz + header_sz);
+               p = r->out + r->out_sz;
+
+               /* add key */
+               memcpy(p, r->headers[i].key, r->headers[i].key_sz);
+               p += r->headers[i].key_sz;
+
+               /* add ": " */
+               *(p++) = ':';
+               *(p++) = ' ';
+
+               /* add value */
+               memcpy(p, r->headers[i].val, r->headers[i].val_sz);
+               p += r->headers[i].val_sz;
+
+               /* add "\r\n" */
+               *(p++) = '\r';
+               *(p++) = '\n';
+
+               r->out_sz += header_sz;
+
+               if(strncasecmp("Connection", r->headers[i].key, r->headers[i].key_sz) == 0 &&
+                       strncasecmp("Keep-Alive", r->headers[i].val, r->headers[i].val_sz) == 0) {
+                       r->keep_alive = 1;
+               }
+       }
+
+       /* end of headers */
+       r->out = realloc(r->out, r->out_sz + 2);
+       memcpy(r->out + r->out_sz, "\r\n", 2);
+       r->out_sz += 2;
+
+       /* append body if there is one. */
+       if(r->body && r->body_len) {
+
+               char *tmp = (char*)r->body;
+               size_t tmp_len = r->body_len;
+               if(r->chunked) { /* replace body with formatted chunk */
+                       tmp = format_chunk(r->body, r->body_len, &tmp_len);
+               }
+
+               r->out = realloc(r->out, r->out_sz + tmp_len);
+               memcpy(r->out + r->out_sz, tmp, tmp_len);
+               r->out_sz += tmp_len;
+
+               if(r->chunked) { /* need to free the chunk */
+                       free(tmp);
+               }
+       }
+
+       /* send buffer to client */
+       r->sent = 0;
+       http_schedule_write(fd, r);
+}
+
+static void
+http_response_set_connection_header(struct http_client *c, struct http_response *r) {
+       http_response_set_keep_alive(r, c->keep_alive);
+}
+
+
+
+/* Adobe flash cross-domain request */
+void
+http_crossdomain(struct http_client *c) {
+
+       struct http_response *resp = http_response_init(NULL, 200, "OK");
+       char out[] = "<?xml version=\"1.0\"?>\n"
+"<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n"
+"<cross-domain-policy>\n"
+  "<allow-access-from domain=\"*\" />\n"
+"</cross-domain-policy>\n";
+
+       resp->http_version = c->http_version;
+       http_response_set_connection_header(c, resp);
+       http_response_set_header(resp, "Content-Type", "application/xml");
+       http_response_set_body(resp, out, sizeof(out)-1);
+
+       http_response_write(resp, c->fd);
+       http_client_reset(c);
+}
+
+/* Simple error response */
+void
+http_send_error(struct http_client *c, short code, const char *msg) {
+
+       struct http_response *resp = http_response_init(NULL, code, msg);
+       resp->http_version = c->http_version;
+       http_response_set_connection_header(c, resp);
+       http_response_set_body(resp, NULL, 0);
+
+       http_response_write(resp, c->fd);
+       http_client_reset(c);
+}
+
+/**
+ * Set Connection field, either Keep-Alive or Close.
+ */
+void
+http_response_set_keep_alive(struct http_response *r, int enabled) {
+       r->keep_alive = enabled;
+       if(enabled) {
+               http_response_set_header(r, "Connection", "Keep-Alive");
+       } else {
+               http_response_set_header(r, "Connection", "Close");
+       }
+}
+
+/* Response to HTTP OPTIONS */
+void
+http_send_options(struct http_client *c) {
+
+       struct http_response *resp = http_response_init(NULL, 200, "OK");
+       resp->http_version = c->http_version;
+       http_response_set_connection_header(c, resp);
+
+       http_response_set_header(resp, "Content-Type", "text/html");
+       http_response_set_header(resp, "Content-Length", "0");
+
+       http_response_write(resp, c->fd);
+       http_client_reset(c);
+}
+
+/**
+ * Write HTTP chunk.
+ */
+void
+http_response_write_chunk(int fd, struct worker *w, const char *p, size_t sz) {
+
+       struct http_response *r = http_response_init(w, 0, NULL);
+       r->keep_alive = 1; /* chunks are always keep-alive */
+
+       /* format packet */
+       r->out = format_chunk(p, sz, &r->out_sz);
+
+       /* send async write */
+       http_schedule_write(fd, r);
+}
+
diff --git a/http.h b/http.h
new file mode 100644 (file)
index 0000000..eac1ffe
--- /dev/null
+++ b/http.h
@@ -0,0 +1,75 @@
+#ifndef HTTP_H
+#define HTTP_H
+
+#include <sys/types.h>
+#include <event.h>
+
+struct http_client;
+struct worker;
+
+struct http_header {
+       char *key;
+       size_t key_sz;
+
+       char *val;
+       size_t val_sz;
+};
+
+
+struct http_response {
+
+       struct event ev;
+
+       short code;
+       const char *msg;
+
+       struct http_header *headers;
+       int header_count;
+
+       const char *body;
+       size_t body_len;
+
+       char *out;
+       size_t out_sz;
+
+       int chunked;
+       int http_version;
+       int keep_alive;
+       int sent;
+
+       struct worker *w;
+};
+
+/* HTTP response */
+
+struct http_response *
+http_response_init(struct worker *w, int code, const char *msg);
+
+void
+http_response_set_header(struct http_response *r, const char *k, const char *v);
+
+void
+http_response_set_body(struct http_response *r, const char *body, size_t body_len);
+
+void
+http_response_write(struct http_response *r, int fd);
+
+void
+http_schedule_write(int fd, struct http_response *r);
+
+void
+http_crossdomain(struct http_client *c);
+
+void
+http_send_error(struct http_client *c, short code, const char *msg);
+
+void
+http_send_options(struct http_client *c);
+
+void
+http_response_write_chunk(int fd, struct worker *w, const char *p, size_t sz);
+
+void
+http_response_set_keep_alive(struct http_response *r, int enabled);
+
+#endif
diff --git a/jansson/.gitignore b/jansson/.gitignore
new file mode 100644 (file)
index 0000000..3b4bffe
--- /dev/null
@@ -0,0 +1,27 @@
+*~
+*.o
+*.a
+.libs
+.deps
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+config.guess
+config.h
+config.h.in
+config.log
+config.status
+config.sub
+configure
+depcomp
+install-sh
+libtool
+ltmain.sh
+missing
+*.lo
+*.la
+stamp-h1
+*.pyc
+*.pc
+/src/jansson_config.h
diff --git a/jansson/CHANGES b/jansson/CHANGES
new file mode 100644 (file)
index 0000000..0cadf5c
--- /dev/null
@@ -0,0 +1,206 @@
+Version 1.3
+===========
+
+Released 2010-06-13
+
+* New functions:
+
+  - `json_object_iter_set()`, `json_object_iter_set_new()`: Change
+    object contents while iterating over it.
+
+  - `json_object_iter_at()`: Return an iterator that points to a
+    specific object item.
+
+* New encoding flags:
+
+  - ``JSON_PRESERVE_ORDER``: Preserve the insertion order of object
+    keys.
+
+* Bug fixes:
+
+  - Fix an error that occured when an array or object was first
+    encoded as empty, then populated with some data, and then
+    re-encoded
+
+  - Fix the situation like above, but when the first encoding resulted
+    in an error
+
+* Documentation:
+
+  - Clarify the documentation on reference stealing, providing an
+    example usage pattern
+
+
+Version 1.2.1
+=============
+
+Released 2010-04-03
+
+* Bug fixes:
+
+  - Fix reference counting on ``true``, ``false`` and ``null``
+  - Estimate real number underflows in decoder with 0.0 instead of
+    issuing an error
+
+* Portability:
+
+  - Make ``int32_t`` available on all systems
+  - Support compilers that don't have the ``inline`` keyword
+  - Require Autoconf 2.60 (for ``int32_t``)
+
+* Tests:
+
+  - Print test names correctly when ``VERBOSE=1``
+  - ``test/suites/api``: Fail when a test fails
+  - Enhance tests for iterators
+  - Enhance tests for decoding texts that contain null bytes
+
+* Documentation:
+
+  - Don't remove ``changes.rst`` in ``make clean``
+  - Add a chapter on RFC conformance
+
+
+Version 1.2
+===========
+
+Released 2010-01-21
+
+* New functions:
+
+  - `json_equal()`: Test whether two JSON values are equal
+  - `json_copy()` and `json_deep_copy()`: Make shallow and deep copies
+    of JSON values
+  - Add a version of all functions taking a string argument that
+    doesn't check for valid UTF-8: `json_string_nocheck()`,
+    `json_string_set_nocheck()`, `json_object_set_nocheck()`,
+    `json_object_set_new_nocheck()`
+
+* New encoding flags:
+
+  - ``JSON_SORT_KEYS``: Sort objects by key
+  - ``JSON_ENSURE_ASCII``: Escape all non-ASCII Unicode characters
+  - ``JSON_COMPACT``: Use a compact representation with all unneeded
+    whitespace stripped
+
+* Bug fixes:
+
+  - Revise and unify whitespace usage in encoder: Add spaces between
+    array and object items, never append newline to output.
+  - Remove const qualifier from the ``json_t`` parameter in
+    `json_string_set()`, `json_integer_set()` and `json_real_set`.
+  - Use ``int32_t`` internally for representing Unicode code points
+    (int is not enough on all platforms)
+
+* Other changes:
+
+  - Convert ``CHANGES`` (this file) to reStructured text and add it to
+    HTML documentation
+  - The test system has been refactored. Python is no longer required
+    to run the tests.
+  - Documentation can now be built by invoking ``make html``
+  - Support for pkg-config
+
+
+Version 1.1.3
+=============
+
+Released 2009-12-18
+
+* Encode reals correctly, so that first encoding and then decoding a
+  real always produces the same value
+* Don't export private symbols in ``libjansson.so``
+
+
+Version 1.1.2
+=============
+
+Released 2009-11-08
+
+* Fix a bug where an error message was not produced if the input file
+  could not be opened in `json_load_file()`
+* Fix an assertion failure in decoder caused by a minus sign without a
+  digit after it
+* Remove an unneeded include of ``stdint.h`` in ``jansson.h``
+
+
+Version 1.1.1
+=============
+
+Released 2009-10-26
+
+* All documentation files were not distributed with v1.1; build
+  documentation in make distcheck to prevent this in the future
+* Fix v1.1 release date in ``CHANGES``
+
+
+Version 1.1
+===========
+
+Released 2009-10-20
+
+* API additions and improvements:
+
+  - Extend array and object APIs
+  - Add functions to modify integer, real and string values
+  - Improve argument validation
+  - Use unsigned int instead of ``uint32_t`` for encoding flags
+
+* Enhance documentation
+
+  - Add getting started guide and tutorial
+  - Fix some typos
+  - General clarifications and cleanup
+
+* Check for integer and real overflows and underflows in decoder
+* Make singleton values thread-safe (``true``, ``false`` and ``null``)
+* Enhance circular reference handling
+* Don't define ``-std=c99`` in ``AM_CFLAGS``
+* Add C++ guards to ``jansson.h``
+* Minor performance and portability improvements
+* Expand test coverage
+
+
+Version 1.0.4
+=============
+
+Released 2009-10-11
+
+* Relax Autoconf version requirement to 2.59
+* Make Jansson compile on platforms where plain ``char`` is unsigned
+* Fix API tests for object
+
+
+Version 1.0.3
+=============
+
+Released 2009-09-14
+
+* Check for integer and real overflows and underflows in decoder
+* Use the Python json module for tests, or simplejson if the json
+  module is not found
+* Distribute changelog (this file)
+
+
+Version 1.0.2
+=============
+
+Released 2009-09-08
+
+* Handle EOF correctly in decoder
+
+
+Version 1.0.1
+=============
+
+Released 2009-09-04
+
+* Fixed broken `json_is_boolean()`
+
+
+Version 1.0
+===========
+
+Released 2009-08-25
+
+* Initial release
diff --git a/jansson/LICENSE b/jansson/LICENSE
new file mode 100644 (file)
index 0000000..552b349
--- /dev/null
@@ -0,0 +1,19 @@
+Copyright (c) 2009, 2010 Petri Lehtinen <petri@digip.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/jansson/Makefile.am b/jansson/Makefile.am
new file mode 100644 (file)
index 0000000..173c7de
--- /dev/null
@@ -0,0 +1,8 @@
+EXTRA_DIST = CHANGES LICENSE README.rst
+SUBDIRS = doc src test
+
+check-doc:
+       $(MAKE) SPHINXOPTS_EXTRA=-W html
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = jansson.pc
diff --git a/jansson/README.rst b/jansson/README.rst
new file mode 100644 (file)
index 0000000..27706dd
--- /dev/null
@@ -0,0 +1,59 @@
+Jansson README
+==============
+
+Jansson_ is a C library for encoding, decoding and manipulating JSON
+data. Its main features and design principles are:
+
+- Simple and intuitive API and data model
+
+- Comprehensive documentation
+
+- No dependencies on other libraries
+
+- Full Unicode support (UTF-8)
+
+- Extensive test suite
+
+Jansson is licensed under the `MIT license`_; see LICENSE in the
+source distribution for details.
+
+
+Compilation and Installation
+----------------------------
+
+If you obtained a source tarball, just use the standard autotools
+commands::
+
+   $ ./configure && make && make install
+
+If the source has been checked out from a Git repository, the
+./configure script has to be generated fist. The easiest way is to use
+autoreconf::
+
+   $ autoreconf -i
+
+To run the test suite, invoke::
+
+   $ make check
+
+
+Documentation
+-------------
+
+Documentation is in the ``doc/`` subdirectory. It's written in
+reStructuredText_ with Sphinx_ annotations, so reading it in plain may
+be inconvenient. For this reason, prebuilt HTML documentation is
+available at http://www.digip.org/jansson/doc/.
+
+To generate HTML documentation yourself, invoke::
+
+   make html
+
+and point your browser to ``doc/_build/html/index.html``. Sphinx_ is
+required to generate the documentation.
+
+
+.. _Jansson: http://www.digip.org/jansson/
+.. _`MIT license`: http://www.opensource.org/licenses/mit-license.php
+.. _reStructuredText: http://docutils.sourceforge.net/rst.html
+.. _Sphinx: http://sphinx.pocoo.org/
diff --git a/jansson/configure.ac b/jansson/configure.ac
new file mode 100644 (file)
index 0000000..cabc732
--- /dev/null
@@ -0,0 +1,49 @@
+AC_PREREQ([2.60])
+AC_INIT([jansson], [2.0pre], [petri@digip.org])
+
+AM_INIT_AUTOMAKE([1.10 foreign])
+
+AC_CONFIG_SRCDIR([src/value.c])
+AC_CONFIG_HEADERS([config.h])
+
+# Checks for programs.
+AC_PROG_CC
+AC_PROG_LIBTOOL
+AM_CONDITIONAL([GCC], [test x$GCC = xyes])
+
+# Checks for libraries.
+
+# Checks for header files.
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_TYPE_INT32_T
+
+AC_TYPE_LONG_LONG_INT
+case $ac_cv_type_long_long_int in
+     yes) json_have_long_long=1;;
+     *) json_have_long_long=0;;
+esac
+AC_SUBST([json_have_long_long])
+
+AC_C_INLINE
+case $ac_cv_c_inline in
+    yes) json_inline=inline;;
+    no) json_inline=;;
+    *) json_inline=$ac_cv_c_inline;;
+esac
+AC_SUBST([json_inline])
+
+# Checks for library functions.
+
+AC_CONFIG_FILES([
+        jansson.pc
+        Makefile
+        doc/Makefile
+        src/Makefile
+        src/jansson_config.h
+        test/Makefile
+        test/bin/Makefile
+        test/suites/Makefile
+        test/suites/api/Makefile
+])
+AC_OUTPUT
diff --git a/jansson/jansson.pc.in b/jansson/jansson.pc.in
new file mode 100644 (file)
index 0000000..d9bf4da
--- /dev/null
@@ -0,0 +1,10 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=${prefix}/include
+
+Name: Jansson
+Description: Library for encoding, decoding and manipulating JSON data
+Version: @VERSION@
+Libs: -L${libdir} -ljansson
+Cflags: -I${includedir}
diff --git a/jansson/src/Makefile.am b/jansson/src/Makefile.am
new file mode 100644 (file)
index 0000000..be8fcb9
--- /dev/null
@@ -0,0 +1,24 @@
+include_HEADERS = jansson.h jansson_config.h
+
+lib_LTLIBRARIES = libjansson.la
+libjansson_la_SOURCES = \
+       dump.c \
+       error.c \
+       hashtable.c \
+       hashtable.h \
+       jansson_private.h \
+       load.c \
+       strbuffer.c \
+       strbuffer.h \
+       utf.c \
+       utf.h \
+       value.c \
+       variadic.c
+libjansson_la_LDFLAGS = \
+       -export-symbols-regex '^json_' \
+       -version-info 3:0:3
+
+if GCC
+# These flags are gcc specific
+AM_CFLAGS = -Wall -Wextra -Werror
+endif
diff --git a/jansson/src/dump.c b/jansson/src/dump.c
new file mode 100644 (file)
index 0000000..42eb256
--- /dev/null
@@ -0,0 +1,461 @@
+/*
+ * Copyright (c) 2009, 2010 Petri Lehtinen <petri@digip.org>
+ *
+ * Jansson is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include <jansson.h>
+#include "jansson_private.h"
+#include "strbuffer.h"
+#include "utf.h"
+
+#define MAX_INTEGER_STR_LENGTH  100
+#define MAX_REAL_STR_LENGTH     100
+
+typedef int (*dump_func)(const char *buffer, int size, void *data);
+
+struct string
+{
+    char *buffer;
+    int length;
+    int size;
+};
+
+static int dump_to_strbuffer(const char *buffer, int size, void *data)
+{
+    return strbuffer_append_bytes((strbuffer_t *)data, buffer, size);
+}
+
+static int dump_to_file(const char *buffer, int size, void *data)
+{
+    FILE *dest = (FILE *)data;
+    if(fwrite(buffer, size, 1, dest) != 1)
+        return -1;
+    return 0;
+}
+
+/* 32 spaces (the maximum indentation size) */
+static char whitespace[] = "                                ";
+
+static int dump_indent(size_t flags, int depth, int space, dump_func dump, void *data)
+{
+    if(JSON_INDENT(flags) > 0)
+    {
+        int i, ws_count = JSON_INDENT(flags);
+
+        if(dump("\n", 1, data))
+            return -1;
+
+        for(i = 0; i < depth; i++)
+        {
+            if(dump(whitespace, ws_count, data))
+                return -1;
+        }
+    }
+    else if(space && !(flags & JSON_COMPACT))
+    {
+        return dump(" ", 1, data);
+    }
+    return 0;
+}
+
+static int dump_string(const char *str, int ascii, dump_func dump, void *data)
+{
+    const char *pos, *end;
+    int32_t codepoint;
+
+    if(dump("\"", 1, data))
+        return -1;
+
+    end = pos = str;
+    while(1)
+    {
+        const char *text;
+        char seq[13];
+        int length;
+
+        while(*end)
+        {
+            end = utf8_iterate(pos, &codepoint);
+            if(!end)
+                return -1;
+
+            /* mandatory escape or control char */
+            if(codepoint == '\\' || codepoint == '"' || codepoint < 0x20)
+                break;
+
+            /* non-ASCII */
+            if(ascii && codepoint > 0x7F)
+                break;
+
+            pos = end;
+        }
+
+        if(pos != str) {
+            if(dump(str, pos - str, data))
+                return -1;
+        }
+
+        if(end == pos)
+            break;
+
+        /* handle \, ", and control codes */
+        length = 2;
+        switch(codepoint)
+        {
+            case '\\': text = "\\\\"; break;
+            case '\"': text = "\\\""; break;
+            case '\b': text = "\\b"; break;
+            case '\f': text = "\\f"; break;
+            case '\n': text = "\\n"; break;
+            case '\r': text = "\\r"; break;
+            case '\t': text = "\\t"; break;
+            default:
+            {
+                /* codepoint is in BMP */
+                if(codepoint < 0x10000)
+                {
+                    sprintf(seq, "\\u%04x", codepoint);
+                    length = 6;
+                }
+
+                /* not in BMP -> construct a UTF-16 surrogate pair */
+                else
+                {
+                    int32_t first, last;
+
+                    codepoint -= 0x10000;
+                    first = 0xD800 | ((codepoint & 0xffc00) >> 10);
+                    last = 0xDC00 | (codepoint & 0x003ff);
+
+                    sprintf(seq, "\\u%04x\\u%04x", first, last);
+                    length = 12;
+                }
+
+                text = seq;
+                break;
+            }
+        }
+
+        if(dump(text, length, data))
+            return -1;
+
+        str = pos = end;
+    }
+
+    return dump("\"", 1, data);
+}
+
+static int object_key_compare_keys(const void *key1, const void *key2)
+{
+    return strcmp((*(const object_key_t **)key1)->key,
+                  (*(const object_key_t **)key2)->key);
+}
+
+static int object_key_compare_serials(const void *key1, const void *key2)
+{
+    return (*(const object_key_t **)key1)->serial -
+           (*(const object_key_t **)key2)->serial;
+}
+
+static int do_dump(const json_t *json, size_t flags, int depth,
+                   dump_func dump, void *data)
+{
+    int ascii = flags & JSON_ENSURE_ASCII ? 1 : 0;
+
+    switch(json_typeof(json)) {
+        case JSON_NULL:
+            return dump("null", 4, data);
+
+        case JSON_TRUE:
+            return dump("true", 4, data);
+
+        case JSON_FALSE:
+            return dump("false", 5, data);
+
+        case JSON_INTEGER:
+        {
+            char buffer[MAX_INTEGER_STR_LENGTH];
+            int size;
+
+            size = snprintf(buffer, MAX_INTEGER_STR_LENGTH,
+                            "%" JSON_INTEGER_FORMAT,
+                            json_integer_value(json));
+            if(size >= MAX_INTEGER_STR_LENGTH)
+                return -1;
+
+            return dump(buffer, size, data);
+        }
+
+        case JSON_REAL:
+        {
+            char buffer[MAX_REAL_STR_LENGTH];
+            int size;
+
+            size = snprintf(buffer, MAX_REAL_STR_LENGTH, "%.17g",
+                            json_real_value(json));
+            if(size >= MAX_REAL_STR_LENGTH)
+                return -1;
+
+            /* Make sure there's a dot or 'e' in the output. Otherwise
+               a real is converted to an integer when decoding */
+            if(strchr(buffer, '.') == NULL &&
+               strchr(buffer, 'e') == NULL)
+            {
+                if(size + 2 >= MAX_REAL_STR_LENGTH) {
+                    /* No space to append ".0" */
+                    return -1;
+                }
+                buffer[size] = '.';
+                buffer[size + 1] = '0';
+                size += 2;
+            }
+
+            return dump(buffer, size, data);
+        }
+
+        case JSON_STRING:
+            return dump_string(json_string_value(json), ascii, dump, data);
+
+        case JSON_ARRAY:
+        {
+            int i;
+            int n;
+            json_array_t *array;
+
+            /* detect circular references */
+            array = json_to_array(json);
+            if(array->visited)
+                goto array_error;
+            array->visited = 1;
+
+            n = json_array_size(json);
+
+            if(dump("[", 1, data))
+                goto array_error;
+            if(n == 0) {
+                array->visited = 0;
+                return dump("]", 1, data);
+            }
+            if(dump_indent(flags, depth + 1, 0, dump, data))
+                goto array_error;
+
+            for(i = 0; i < n; ++i) {
+                if(do_dump(json_array_get(json, i), flags, depth + 1,
+                           dump, data))
+                    goto array_error;
+
+                if(i < n - 1)
+                {
+                    if(dump(",", 1, data) ||
+                       dump_indent(flags, depth + 1, 1, dump, data))
+                        goto array_error;
+                }
+                else
+                {
+                    if(dump_indent(flags, depth, 0, dump, data))
+                        goto array_error;
+                }
+            }
+
+            array->visited = 0;
+            return dump("]", 1, data);
+
+        array_error:
+            array->visited = 0;
+            return -1;
+        }
+
+        case JSON_OBJECT:
+        {
+            json_object_t *object;
+            void *iter;
+            const char *separator;
+            int separator_length;
+
+            if(flags & JSON_COMPACT) {
+                separator = ":";
+                separator_length = 1;
+            }
+            else {
+                separator = ": ";
+                separator_length = 2;
+            }
+
+            /* detect circular references */
+            object = json_to_object(json);
+            if(object->visited)
+                goto object_error;
+            object->visited = 1;
+
+            iter = json_object_iter((json_t *)json);
+
+            if(dump("{", 1, data))
+                goto object_error;
+            if(!iter) {
+                object->visited = 0;
+                return dump("}", 1, data);
+            }
+            if(dump_indent(flags, depth + 1, 0, dump, data))
+                goto object_error;
+
+            if(flags & JSON_SORT_KEYS || flags & JSON_PRESERVE_ORDER)
+            {
+                const object_key_t **keys;
+                size_t size, i;
+                int (*cmp_func)(const void *, const void *);
+
+                size = json_object_size(json);
+                keys = malloc(size * sizeof(object_key_t *));
+                if(!keys)
+                    goto object_error;
+
+                i = 0;
+                while(iter)
+                {
+                    keys[i] = jsonp_object_iter_fullkey(iter);
+                    iter = json_object_iter_next((json_t *)json, iter);
+                    i++;
+                }
+                assert(i == size);
+
+                if(flags & JSON_SORT_KEYS)
+                    cmp_func = object_key_compare_keys;
+                else
+                    cmp_func = object_key_compare_serials;
+
+                qsort(keys, size, sizeof(object_key_t *), cmp_func);
+
+                for(i = 0; i < size; i++)
+                {
+                    const char *key;
+                    json_t *value;
+
+                    key = keys[i]->key;
+                    value = json_object_get(json, key);
+                    assert(value);
+
+                    dump_string(key, ascii, dump, data);
+                    if(dump(separator, separator_length, data) ||
+                       do_dump(value, flags, depth + 1, dump, data))
+                    {
+                        free(keys);
+                        goto object_error;
+                    }
+
+                    if(i < size - 1)
+                    {
+                        if(dump(",", 1, data) ||
+                           dump_indent(flags, depth + 1, 1, dump, data))
+                        {
+                            free(keys);
+                            goto object_error;
+                        }
+                    }
+                    else
+                    {
+                        if(dump_indent(flags, depth, 0, dump, data))
+                        {
+                            free(keys);
+                            goto object_error;
+                        }
+                    }
+                }
+
+                free(keys);
+            }
+            else
+            {
+                /* Don't sort keys */
+
+                while(iter)
+                {
+                    void *next = json_object_iter_next((json_t *)json, iter);
+
+                    dump_string(json_object_iter_key(iter), ascii, dump, data);
+                    if(dump(separator, separator_length, data) ||
+                       do_dump(json_object_iter_value(iter), flags, depth + 1,
+                               dump, data))
+                        goto object_error;
+
+                    if(next)
+                    {
+                        if(dump(",", 1, data) ||
+                           dump_indent(flags, depth + 1, 1, dump, data))
+                            goto object_error;
+                    }
+                    else
+                    {
+                        if(dump_indent(flags, depth, 0, dump, data))
+                            goto object_error;
+                    }
+
+                    iter = next;
+                }
+            }
+
+            object->visited = 0;
+            return dump("}", 1, data);
+
+        object_error:
+            object->visited = 0;
+            return -1;
+        }
+
+        default:
+            /* not reached */
+            return -1;
+    }
+}
+
+
+char *json_dumps(const json_t *json, size_t flags)
+{
+    strbuffer_t strbuff;
+    char *result;
+
+    if(!json_is_array(json) && !json_is_object(json))
+        return NULL;
+
+    if(strbuffer_init(&strbuff))
+        return NULL;
+
+    if(do_dump(json, flags, 0, dump_to_strbuffer, (void *)&strbuff)) {
+        strbuffer_close(&strbuff);
+        return NULL;
+    }
+
+    result = strdup(strbuffer_value(&strbuff));
+    strbuffer_close(&strbuff);
+
+    return result;
+}
+
+int json_dumpf(const json_t *json, FILE *output, size_t flags)
+{
+    if(!json_is_array(json) && !json_is_object(json))
+        return -1;
+
+    return do_dump(json, flags, 0, dump_to_file, (void *)output);
+}
+
+int json_dump_file(const json_t *json, const char *path, size_t flags)
+{
+    int result;
+
+    FILE *output = fopen(path, "w");
+    if(!output)
+        return -1;
+
+    result = json_dumpf(json, output, flags);
+
+    fclose(output);
+    return result;
+}
diff --git a/jansson/src/error.c b/jansson/src/error.c
new file mode 100644 (file)
index 0000000..b8c78db
--- /dev/null
@@ -0,0 +1,38 @@
+#include <string.h>
+#include <stdarg.h>
+
+#include "jansson_private.h"
+
+void jsonp_error_init(json_error_t *error, const char *source)
+{
+    if(error)
+    {
+        error->text[0] = '\0';
+        error->line = -1;
+        error->column = -1;
+
+        strncpy(error->source, source, JSON_ERROR_SOURCE_LENGTH);
+        error->source[JSON_ERROR_SOURCE_LENGTH - 1] = '\0';
+    }
+}
+
+void jsonp_error_set(json_error_t *error, int line, int column,
+                     const char *msg, ...)
+{
+    va_list ap;
+
+    if(!error)
+        return;
+
+    if(error->text[0] != '\0') {
+        /* error already set */
+        return;
+    }
+
+    error->line = line;
+    error->column = column;
+
+    va_start(ap, msg);
+    vsnprintf(error->text, JSON_ERROR_TEXT_LENGTH, msg, ap);
+    va_end(ap);
+}
diff --git a/jansson/src/hashtable.c b/jansson/src/hashtable.c
new file mode 100644 (file)
index 0000000..77d2b80
--- /dev/null
@@ -0,0 +1,372 @@
+/*
+ * Copyright (c) 2009, 2010 Petri Lehtinen <petri@digip.org>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+
+#include <stdlib.h>
+#include <jansson_config.h>   /* for JSON_INLINE */
+#include "jansson_private.h"  /* for container_of() */
+#include "hashtable.h"
+
+typedef struct hashtable_list list_t;
+typedef struct hashtable_pair pair_t;
+typedef struct hashtable_bucket bucket_t;
+
+#define list_to_pair(list_)  container_of(list_, pair_t, list)
+
+static JSON_INLINE void list_init(list_t *list)
+{
+    list->next = list;
+    list->prev = list;
+}
+
+static JSON_INLINE void list_insert(list_t *list, list_t *node)
+{
+    node->next = list;
+    node->prev = list->prev;
+    list->prev->next = node;
+    list->prev = node;
+}
+
+static JSON_INLINE void list_remove(list_t *list)
+{
+    list->prev->next = list->next;
+    list->next->prev = list->prev;
+}
+
+static JSON_INLINE int bucket_is_empty(hashtable_t *hashtable, bucket_t *bucket)
+{
+    return bucket->first == &hashtable->list && bucket->first == bucket->last;
+}
+
+static void insert_to_bucket(hashtable_t *hashtable, bucket_t *bucket,
+                             list_t *list)
+{
+    if(bucket_is_empty(hashtable, bucket))
+    {
+        list_insert(&hashtable->list, list);
+        bucket->first = bucket->last = list;
+    }
+    else
+    {
+        list_insert(bucket->first, list);
+        bucket->first = list;
+    }
+}
+
+static size_t primes[] = {
+    5, 13, 23, 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593,
+    49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469,
+    12582917, 25165843, 50331653, 100663319, 201326611, 402653189,
+    805306457, 1610612741
+};
+static const size_t num_primes = sizeof(primes) / sizeof(size_t);
+
+static JSON_INLINE size_t num_buckets(hashtable_t *hashtable)
+{
+    return primes[hashtable->num_buckets];
+}
+
+
+static pair_t *hashtable_find_pair(hashtable_t *hashtable, bucket_t *bucket,
+                                   const void *key, size_t hash)
+{
+    list_t *list;
+    pair_t *pair;
+
+    if(bucket_is_empty(hashtable, bucket))
+        return NULL;
+
+    list = bucket->first;
+    while(1)
+    {
+        pair = list_to_pair(list);
+        if(pair->hash == hash && hashtable->cmp_keys(pair->key, key))
+            return pair;
+
+        if(list == bucket->last)
+            break;
+
+        list = list->next;
+    }
+
+    return NULL;
+}
+
+/* returns 0 on success, -1 if key was not found */
+static int hashtable_do_del(hashtable_t *hashtable,
+                            const void *key, size_t hash)
+{
+    pair_t *pair;
+    bucket_t *bucket;
+    size_t index;
+
+    index = hash % num_buckets(hashtable);
+    bucket = &hashtable->buckets[index];
+
+    pair = hashtable_find_pair(hashtable, bucket, key, hash);
+    if(!pair)
+        return -1;
+
+    if(&pair->list == bucket->first && &pair->list == bucket->last)
+        bucket->first = bucket->last = &hashtable->list;
+
+    else if(&pair->list == bucket->first)
+        bucket->first = pair->list.next;
+
+    else if(&pair->list == bucket->last)
+        bucket->last = pair->list.prev;
+
+    list_remove(&pair->list);
+
+    if(hashtable->free_key)
+        hashtable->free_key(pair->key);
+    if(hashtable->free_value)
+        hashtable->free_value(pair->value);
+
+    free(pair);
+    hashtable->size--;
+
+    return 0;
+}
+
+static void hashtable_do_clear(hashtable_t *hashtable)
+{
+    list_t *list, *next;
+    pair_t *pair;
+
+    for(list = hashtable->list.next; list != &hashtable->list; list = next)
+    {
+        next = list->next;
+        pair = list_to_pair(list);
+        if(hashtable->free_key)
+            hashtable->free_key(pair->key);
+        if(hashtable->free_value)
+            hashtable->free_value(pair->value);
+        free(pair);
+    }
+}
+
+static int hashtable_do_rehash(hashtable_t *hashtable)
+{
+    list_t *list, *next;
+    pair_t *pair;
+    size_t i, index, new_size;
+
+    free(hashtable->buckets);
+
+    hashtable->num_buckets++;
+    new_size = num_buckets(hashtable);
+
+    hashtable->buckets = malloc(new_size * sizeof(bucket_t));
+    if(!hashtable->buckets)
+        return -1;
+
+    for(i = 0; i < num_buckets(hashtable); i++)
+    {
+        hashtable->buckets[i].first = hashtable->buckets[i].last =
+            &hashtable->list;
+    }
+
+    list = hashtable->list.next;
+    list_init(&hashtable->list);
+
+    for(; list != &hashtable->list; list = next) {
+        next = list->next;
+        pair = list_to_pair(list);
+        index = pair->hash % new_size;
+        insert_to_bucket(hashtable, &hashtable->buckets[index], &pair->list);
+    }
+
+    return 0;
+}
+
+
+hashtable_t *hashtable_create(key_hash_fn hash_key, key_cmp_fn cmp_keys,
+                              free_fn free_key, free_fn free_value)
+{
+    hashtable_t *hashtable = malloc(sizeof(hashtable_t));
+    if(!hashtable)
+        return NULL;
+
+    if(hashtable_init(hashtable, hash_key, cmp_keys, free_key, free_value))
+    {
+        free(hashtable);
+        return NULL;
+    }
+
+    return hashtable;
+}
+
+void hashtable_destroy(hashtable_t *hashtable)
+{
+    hashtable_close(hashtable);
+    free(hashtable);
+}
+
+int hashtable_init(hashtable_t *hashtable,
+                   key_hash_fn hash_key, key_cmp_fn cmp_keys,
+                   free_fn free_key, free_fn free_value)
+{
+    size_t i;
+
+    hashtable->size = 0;
+    hashtable->num_buckets = 0;  /* index to primes[] */
+    hashtable->buckets = malloc(num_buckets(hashtable) * sizeof(bucket_t));
+    if(!hashtable->buckets)
+        return -1;
+
+    list_init(&hashtable->list);
+
+    hashtable->hash_key = hash_key;
+    hashtable->cmp_keys = cmp_keys;
+    hashtable->free_key = free_key;
+    hashtable->free_value = free_value;
+
+    for(i = 0; i < num_buckets(hashtable); i++)
+    {
+        hashtable->buckets[i].first = hashtable->buckets[i].last =
+            &hashtable->list;
+    }
+
+    return 0;
+}
+
+void hashtable_close(hashtable_t *hashtable)
+{
+    hashtable_do_clear(hashtable);
+    free(hashtable->buckets);
+}
+
+int hashtable_set(hashtable_t *hashtable, void *key, void *value)
+{
+    pair_t *pair;
+    bucket_t *bucket;
+    size_t hash, index;
+
+    /* rehash if the load ratio exceeds 1 */
+    if(hashtable->size >= num_buckets(hashtable))
+        if(hashtable_do_rehash(hashtable))
+            return -1;
+
+    hash = hashtable->hash_key(key);
+    index = hash % num_buckets(hashtable);
+    bucket = &hashtable->buckets[index];
+    pair = hashtable_find_pair(hashtable, bucket, key, hash);
+
+    if(pair)
+    {
+        if(hashtable->free_key)
+            hashtable->free_key(key);
+        if(hashtable->free_value)
+            hashtable->free_value(pair->value);
+        pair->value = value;
+    }
+    else
+    {
+        pair = malloc(sizeof(pair_t));
+        if(!pair)
+            return -1;
+
+        pair->key = key;
+        pair->value = value;
+        pair->hash = hash;
+        list_init(&pair->list);
+
+        insert_to_bucket(hashtable, bucket, &pair->list);
+
+        hashtable->size++;
+    }
+    return 0;
+}
+
+void *hashtable_get(hashtable_t *hashtable, const void *key)
+{
+    pair_t *pair;
+    size_t hash;
+    bucket_t *bucket;
+
+    hash = hashtable->hash_key(key);
+    bucket = &hashtable->buckets[hash % num_buckets(hashtable)];
+
+    pair = hashtable_find_pair(hashtable, bucket, key, hash);
+    if(!pair)
+        return NULL;
+
+    return pair->value;
+}
+
+int hashtable_del(hashtable_t *hashtable, const void *key)
+{
+    size_t hash = hashtable->hash_key(key);
+    return hashtable_do_del(hashtable, key, hash);
+}
+
+void hashtable_clear(hashtable_t *hashtable)
+{
+    size_t i;
+
+    hashtable_do_clear(hashtable);
+
+    for(i = 0; i < num_buckets(hashtable); i++)
+    {
+        hashtable->buckets[i].first = hashtable->buckets[i].last =
+            &hashtable->list;
+    }
+
+    list_init(&hashtable->list);
+    hashtable->size = 0;
+}
+
+void *hashtable_iter(hashtable_t *hashtable)
+{
+    return hashtable_iter_next(hashtable, &hashtable->list);
+}
+
+void *hashtable_iter_at(hashtable_t *hashtable, const void *key)
+{
+    pair_t *pair;
+    size_t hash;
+    bucket_t *bucket;
+
+    hash = hashtable->hash_key(key);
+    bucket = &hashtable->buckets[hash % num_buckets(hashtable)];
+
+    pair = hashtable_find_pair(hashtable, bucket, key, hash);
+    if(!pair)
+        return NULL;
+
+    return &pair->list;
+}
+
+void *hashtable_iter_next(hashtable_t *hashtable, void *iter)
+{
+    list_t *list = (list_t *)iter;
+    if(list->next == &hashtable->list)
+        return NULL;
+    return list->next;
+}
+
+void *hashtable_iter_key(void *iter)
+{
+    pair_t *pair = list_to_pair((list_t *)iter);
+    return pair->key;
+}
+
+void *hashtable_iter_value(void *iter)
+{
+    pair_t *pair = list_to_pair((list_t *)iter);
+    return pair->value;
+}
+
+void hashtable_iter_set(hashtable_t *hashtable, void *iter, void *value)
+{
+    pair_t *pair = list_to_pair((list_t *)iter);
+
+    if(hashtable->free_value)
+        hashtable->free_value(pair->value);
+
+    pair->value = value;
+}
diff --git a/jansson/src/hashtable.h b/jansson/src/hashtable.h
new file mode 100644 (file)
index 0000000..aba5134
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2009, 2010 Petri Lehtinen <petri@digip.org>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+
+#ifndef HASHTABLE_H
+#define HASHTABLE_H
+
+typedef size_t (*key_hash_fn)(const void *key);
+typedef int (*key_cmp_fn)(const void *key1, const void *key2);
+typedef void (*free_fn)(void *key);
+
+struct hashtable_list {
+    struct hashtable_list *prev;
+    struct hashtable_list *next;
+};
+
+struct hashtable_pair {
+    void *key;
+    void *value;
+    size_t hash;
+    struct hashtable_list list;
+};
+
+struct hashtable_bucket {
+    struct hashtable_list *first;
+    struct hashtable_list *last;
+};
+
+typedef struct hashtable {
+    size_t size;
+    struct hashtable_bucket *buckets;
+    size_t num_buckets;  /* index to primes[] */
+    struct hashtable_list list;
+
+    key_hash_fn hash_key;
+    key_cmp_fn cmp_keys;  /* returns non-zero for equal keys */
+    free_fn free_key;
+    free_fn free_value;
+} hashtable_t;
+
+/**
+ * hashtable_create - Create a hashtable object
+ *
+ * @hash_key: The key hashing function
+ * @cmp_keys: The key compare function. Returns non-zero for equal and
+ *     zero for unequal unequal keys
+ * @free_key: If non-NULL, called for a key that is no longer referenced.
+ * @free_value: If non-NULL, called for a value that is no longer referenced.
+ *
+ * Returns a new hashtable object that should be freed with
+ * hashtable_destroy when it's no longer used, or NULL on failure (out
+ * of memory).
+ */
+hashtable_t *hashtable_create(key_hash_fn hash_key, key_cmp_fn cmp_keys,
+                              free_fn free_key, free_fn free_value);
+
+/**
+ * hashtable_destroy - Destroy a hashtable object
+ *
+ * @hashtable: The hashtable
+ *
+ * Destroys a hashtable created with hashtable_create().
+ */
+void hashtable_destroy(hashtable_t *hashtable);
+
+/**
+ * hashtable_init - Initialize a hashtable object
+ *
+ * @hashtable: The (statically allocated) hashtable object
+ * @hash_key: The key hashing function
+ * @cmp_keys: The key compare function. Returns non-zero for equal and
+ *     zero for unequal unequal keys
+ * @free_key: If non-NULL, called for a key that is no longer referenced.
+ * @free_value: If non-NULL, called for a value that is no longer referenced.
+ *
+ * Initializes a statically allocated hashtable object. The object
+ * should be cleared with hashtable_close when it's no longer used.
+ *
+ * Returns 0 on success, -1 on error (out of memory).
+ */
+int hashtable_init(hashtable_t *hashtable,
+                   key_hash_fn hash_key, key_cmp_fn cmp_keys,
+                   free_fn free_key, free_fn free_value);
+
+/**
+ * hashtable_close - Release all resources used by a hashtable object
+ *
+ * @hashtable: The hashtable
+ *
+ * Destroys a statically allocated hashtable object.
+ */
+void hashtable_close(hashtable_t *hashtable);
+
+/**
+ * hashtable_set - Add/modify value in hashtable
+ *
+ * @hashtable: The hashtable object
+ * @key: The key
+ * @value: The value
+ *
+ * If a value with the given key already exists, its value is replaced
+ * with the new value.
+ *
+ * Key and value are "stealed" in the sense that hashtable frees them
+ * automatically when they are no longer used. The freeing is
+ * accomplished by calling free_key and free_value functions that were
+ * supplied to hashtable_new. In case one or both of the free
+ * functions is NULL, the corresponding item is not "stealed".
+ *
+ * Returns 0 on success, -1 on failure (out of memory).
+ */
+int hashtable_set(hashtable_t *hashtable, void *key, void *value);
+
+/**
+ * hashtable_get - Get a value associated with a key
+ *
+ * @hashtable: The hashtable object
+ * @key: The key
+ *
+ * Returns value if it is found, or NULL otherwise.
+ */
+void *hashtable_get(hashtable_t *hashtable, const void *key);
+
+/**
+ * hashtable_del - Remove a value from the hashtable
+ *
+ * @hashtable: The hashtable object
+ * @key: The key
+ *
+ * Returns 0 on success, or -1 if the key was not found.
+ */
+int hashtable_del(hashtable_t *hashtable, const void *key);
+
+/**
+ * hashtable_clear - Clear hashtable
+ *
+ * @hashtable: The hashtable object
+ *
+ * Removes all items from the hashtable.
+ */
+void hashtable_clear(hashtable_t *hashtable);
+
+/**
+ * hashtable_iter - Iterate over hashtable
+ *
+ * @hashtable: The hashtable object
+ *
+ * Returns an opaque iterator to the first element in the hashtable.
+ * The iterator should be passed to hashtable_iter_* functions.
+ * The hashtable items are not iterated over in any particular order.
+ *
+ * There's no need to free the iterator in any way. The iterator is
+ * valid as long as the item that is referenced by the iterator is not
+ * deleted. Other values may be added or deleted. In particular,
+ * hashtable_iter_next() may be called on an iterator, and after that
+ * the key/value pair pointed by the old iterator may be deleted.
+ */
+void *hashtable_iter(hashtable_t *hashtable);
+
+/**
+ * hashtable_iter_at - Return an iterator at a specific key
+ *
+ * @hashtable: The hashtable object
+ * @key: The key that the iterator should point to
+ *
+ * Like hashtable_iter() but returns an iterator pointing to a
+ * specific key.
+ */
+void *hashtable_iter_at(hashtable_t *hashtable, const void *key);
+
+/**
+ * hashtable_iter_next - Advance an iterator
+ *
+ * @hashtable: The hashtable object
+ * @iter: The iterator
+ *
+ * Returns a new iterator pointing to the next element in the
+ * hashtable or NULL if the whole hastable has been iterated over.
+ */
+void *hashtable_iter_next(hashtable_t *hashtable, void *iter);
+
+/**
+ * hashtable_iter_key - Retrieve the key pointed by an iterator
+ *
+ * @iter: The iterator
+ */
+void *hashtable_iter_key(void *iter);
+
+/**
+ * hashtable_iter_value - Retrieve the value pointed by an iterator
+ *
+ * @iter: The iterator
+ */
+void *hashtable_iter_value(void *iter);
+
+/**
+ * hashtable_iter_set - Set the value pointed by an iterator
+ *
+ * @iter: The iterator
+ * @value: The value to set
+ */
+void hashtable_iter_set(hashtable_t *hashtable, void *iter, void *value);
+
+#endif
diff --git a/jansson/src/jansson.h b/jansson/src/jansson.h
new file mode 100644 (file)
index 0000000..1e6fe6e
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2009, 2010 Petri Lehtinen <petri@digip.org>
+ *
+ * Jansson is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+
+#ifndef JANSSON_H
+#define JANSSON_H
+
+#include <stdio.h>
+#include <stdlib.h>  /* for size_t */
+#include <jansson_config.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* version */
+
+#define JANSSON_MAJOR_VERSION  1
+#define JANSSON_MINOR_VERSION  3
+#define JANSSON_MICRO_VERSION  0
+
+/* Micro version is omitted if it's 0 */
+#define JANSSON_VERSION  "1.3"
+
+/* Version as a 3-byte hex number, e.g. 0x010201 == 1.2.1. Use this
+   for numeric comparisons, e.g. #if JANSSON_VERSION_HEX >= ... */
+#define JANSSON_VERSION_HEX  ((JANSSON_MAJOR_VERSION << 16) |   \
+                              (JANSSON_MINOR_VERSION << 8)  |   \
+                              (JANSSON_MICRO_VERSION << 0)))
+
+
+/* types */
+
+typedef enum {
+    JSON_OBJECT,
+    JSON_ARRAY,
+    JSON_STRING,
+    JSON_INTEGER,
+    JSON_REAL,
+    JSON_TRUE,
+    JSON_FALSE,
+    JSON_NULL
+} json_type;
+
+typedef struct {
+    json_type type;
+    size_t refcount;
+} json_t;
+
+#if JSON_INTEGER_IS_LONG_LONG
+#define JSON_INTEGER_FORMAT "lld"
+typedef long long json_int_t;
+#else
+#define JSON_INTEGER_FORMAT "ld"
+typedef long json_int_t;
+#endif /* JSON_INTEGER_IS_LONG_LONG */
+
+#define json_typeof(json)      ((json)->type)
+#define json_is_object(json)   (json && json_typeof(json) == JSON_OBJECT)
+#define json_is_array(json)    (json && json_typeof(json) == JSON_ARRAY)
+#define json_is_string(json)   (json && json_typeof(json) == JSON_STRING)
+#define json_is_integer(json)  (json && json_typeof(json) == JSON_INTEGER)
+#define json_is_real(json)     (json && json_typeof(json) == JSON_REAL)
+#define json_is_number(json)   (json_is_integer(json) || json_is_real(json))
+#define json_is_true(json)     (json && json_typeof(json) == JSON_TRUE)
+#define json_is_false(json)    (json && json_typeof(json) == JSON_FALSE)
+#define json_is_boolean(json)  (json_is_true(json) || json_is_false(json))
+#define json_is_null(json)     (json && json_typeof(json) == JSON_NULL)
+
+/* construction, destruction, reference counting */
+
+json_t *json_object(void);
+json_t *json_array(void);
+json_t *json_string(const char *value);
+json_t *json_string_nocheck(const char *value);
+json_t *json_integer(json_int_t value);
+json_t *json_real(double value);
+json_t *json_true(void);
+json_t *json_false(void);
+json_t *json_null(void);
+
+static JSON_INLINE
+json_t *json_incref(json_t *json)
+{
+    if(json && json->refcount != (size_t)-1)
+        ++json->refcount;
+    return json;
+}
+
+/* do not call json_delete directly */
+void json_delete(json_t *json);
+
+static JSON_INLINE
+void json_decref(json_t *json)
+{
+    if(json && json->refcount != (size_t)-1 && --json->refcount == 0)
+        json_delete(json);
+}
+
+
+/* error reporting */
+
+#define JSON_ERROR_TEXT_LENGTH    160
+#define JSON_ERROR_SOURCE_LENGTH   80
+
+typedef struct {
+    char text[JSON_ERROR_TEXT_LENGTH];
+    int line;
+    int column;
+    char source[JSON_ERROR_SOURCE_LENGTH];
+} json_error_t;
+
+
+/* getters, setters, manipulation */
+
+size_t json_object_size(const json_t *object);
+json_t *json_object_get(const json_t *object, const char *key);
+int json_object_set_new(json_t *object, const char *key, json_t *value);
+int json_object_set_new_nocheck(json_t *object, const char *key, json_t *value);
+int json_object_del(json_t *object, const char *key);
+int json_object_clear(json_t *object);
+int json_object_update(json_t *object, json_t *other);
+void *json_object_iter(json_t *object);
+void *json_object_iter_at(json_t *object, const char *key);
+void *json_object_iter_next(json_t *object, void *iter);
+const char *json_object_iter_key(void *iter);
+json_t *json_object_iter_value(void *iter);
+int json_object_iter_set_new(json_t *object, void *iter, json_t *value);
+
+static JSON_INLINE
+int json_object_set(json_t *object, const char *key, json_t *value)
+{
+    return json_object_set_new(object, key, json_incref(value));
+}
+
+static JSON_INLINE
+int json_object_set_nocheck(json_t *object, const char *key, json_t *value)
+{
+    return json_object_set_new_nocheck(object, key, json_incref(value));
+}
+
+static JSON_INLINE
+int json_object_iter_set(json_t *object, void *iter, json_t *value)
+{
+    return json_object_iter_set_new(object, iter, json_incref(value));
+}
+
+size_t json_array_size(const json_t *array);
+json_t *json_array_get(const json_t *array, size_t index);
+int json_array_set_new(json_t *array, size_t index, json_t *value);
+int json_array_append_new(json_t *array, json_t *value);
+int json_array_insert_new(json_t *array, size_t index, json_t *value);
+int json_array_remove(json_t *array, size_t index);
+int json_array_clear(json_t *array);
+int json_array_extend(json_t *array, json_t *other);
+
+static JSON_INLINE
+int json_array_set(json_t *array, size_t index, json_t *value)
+{
+    return json_array_set_new(array, index, json_incref(value));
+}
+
+static JSON_INLINE
+int json_array_append(json_t *array, json_t *value)
+{
+    return json_array_append_new(array, json_incref(value));
+}
+
+static JSON_INLINE
+int json_array_insert(json_t *array, size_t index, json_t *value)
+{
+    return json_array_insert_new(array, index, json_incref(value));
+}
+
+const char *json_string_value(const json_t *string);
+json_int_t json_integer_value(const json_t *integer);
+double json_real_value(const json_t *real);
+double json_number_value(const json_t *json);
+
+int json_string_set(json_t *string, const char *value);
+int json_string_set_nocheck(json_t *string, const char *value);
+int json_integer_set(json_t *integer, json_int_t value);
+int json_real_set(json_t *real, double value);
+
+json_t *json_pack(json_error_t *error, const char *fmt, ...);
+int json_unpack(json_t *root, json_error_t *error, const char *fmt, ...);
+
+/* equality */
+
+int json_equal(json_t *value1, json_t *value2);
+
+
+/* copying */
+
+json_t *json_copy(json_t *value);
+json_t *json_deep_copy(json_t *value);
+
+
+/* loading, printing */
+
+json_t *json_loads(const char *input, size_t flags, json_error_t *error);
+json_t *json_loadf(FILE *input, size_t flags, json_error_t *error);
+json_t *json_load_file(const char *path, size_t flags, json_error_t *error);
+
+#define JSON_INDENT(n)      (n & 0x1F)
+#define JSON_COMPACT        0x20
+#define JSON_ENSURE_ASCII   0x40
+#define JSON_SORT_KEYS      0x80
+#define JSON_PRESERVE_ORDER 0x100
+
+char *json_dumps(const json_t *json, size_t flags);
+int json_dumpf(const json_t *json, FILE *output, size_t flags);
+int json_dump_file(const json_t *json, const char *path, size_t flags);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/jansson/src/jansson_config.h b/jansson/src/jansson_config.h
new file mode 100644 (file)
index 0000000..ff0f43f
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2010 Petri Lehtinen <petri@digip.org>
+ *
+ * Jansson is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ *
+ *
+ * This file specifies a part of the site-specific configuration for
+ * Jansson, namely those things that affect the public API in
+ * jansson.h.
+ *
+ * The configure script copies this file to jansson_config.h and
+ * replaces @var@ substitutions by values that fit your system. If you
+ * cannot run the configure script, you can do the value substitution
+ * by hand.
+ */
+
+#ifndef JANSSON_CONFIG_H
+#define JANSSON_CONFIG_H
+
+/* If your compiler supports the inline keyword in C, JSON_INLINE is
+   defined to `inline', otherwise empty. In C++, the inline is always
+   supported. */
+#ifdef __cplusplus
+#define JSON_INLINE inline
+#else
+#define JSON_INLINE inline
+#endif
+
+/* If your compiler supports the `long long` type,
+   JSON_INTEGER_IS_LONG_LONG is defined to 1, otherwise to 0. */
+#define JSON_INTEGER_IS_LONG_LONG 1
+
+#endif
diff --git a/jansson/src/jansson_config.h.in b/jansson/src/jansson_config.h.in
new file mode 100644 (file)
index 0000000..d2a9392
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2010 Petri Lehtinen <petri@digip.org>
+ *
+ * Jansson is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ *
+ *
+ * This file specifies a part of the site-specific configuration for
+ * Jansson, namely those things that affect the public API in
+ * jansson.h.
+ *
+ * The configure script copies this file to jansson_config.h and
+ * replaces @var@ substitutions by values that fit your system. If you
+ * cannot run the configure script, you can do the value substitution
+ * by hand.
+ */
+
+#ifndef JANSSON_CONFIG_H
+#define JANSSON_CONFIG_H
+
+/* If your compiler supports the inline keyword in C, JSON_INLINE is
+   defined to `inline', otherwise empty. In C++, the inline is always
+   supported. */
+#ifdef __cplusplus
+#define JSON_INLINE inline
+#else
+#define JSON_INLINE @json_inline@
+#endif
+
+/* If your compiler supports the `long long` type,
+   JSON_INTEGER_IS_LONG_LONG is defined to 1, otherwise to 0. */
+#define JSON_INTEGER_IS_LONG_LONG @json_have_long_long@
+
+#endif
diff --git a/jansson/src/jansson_config.h.win32 b/jansson/src/jansson_config.h.win32
new file mode 100644 (file)
index 0000000..ffb512f
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2010 Petri Lehtinen <petri@digip.org>
+ *
+ * Jansson is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ *
+ *
+ * This file specifies a part of the site-specific configuration for
+ * Jansson, namely those things that affect the public API in
+ * jansson.h.
+ *
+ * The configure script copies this file to jansson_config.h and
+ * replaces @var@ substitutions by values that fit your system. If you
+ * cannot run the configure script, you can do the value substitution
+ * by hand.
+ */
+
+#ifndef JANSSON_CONFIG_H
+#define JANSSON_CONFIG_H
+
+/* If your compiler supports the inline keyword in C, JSON_INLINE is
+   defined to `inline', otherwise empty. In C++, the inline is always
+   supported. */
+#ifdef __cplusplus
+#define JSON_INLINE inline
+#else
+#define JSON_INLINE 
+#endif
+
+/* If your compiler supports the `long long` type,
+   JSON_INTEGER_IS_LONG_LONG is defined to 1, otherwise to 0. */
+#define JSON_INTEGER_IS_LONG_LONG 1
+
+#endif
diff --git a/jansson/src/jansson_private.h b/jansson/src/jansson_private.h
new file mode 100644 (file)
index 0000000..951780c
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2009, 2010 Petri Lehtinen <petri@digip.org>
+ *
+ * Jansson is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+
+#ifndef JANSSON_PRIVATE_H
+#define JANSSON_PRIVATE_H
+
+#include <stddef.h>
+#include "jansson.h"
+#include "hashtable.h"
+
+#define container_of(ptr_, type_, member_)  \
+    ((type_ *)((char *)ptr_ - offsetof(type_, member_)))
+
+/* On some platforms, max() may already be defined */
+#ifndef max
+#define max(a, b)  ((a) > (b) ? (a) : (b))
+#endif
+
+typedef struct {
+    json_t json;
+    hashtable_t hashtable;
+    size_t serial;
+    int visited;
+} json_object_t;
+
+typedef struct {
+    json_t json;
+    size_t size;
+    size_t entries;
+    json_t **table;
+    int visited;
+} json_array_t;
+
+typedef struct {
+    json_t json;
+    char *value;
+} json_string_t;
+
+typedef struct {
+    json_t json;
+    double value;
+} json_real_t;
+
+typedef struct {
+    json_t json;
+    json_int_t value;
+} json_integer_t;
+
+#define json_to_object(json_)  container_of(json_, json_object_t, json)
+#define json_to_array(json_)   container_of(json_, json_array_t, json)
+#define json_to_string(json_)  container_of(json_, json_string_t, json)
+#define json_to_real(json_)   container_of(json_, json_real_t, json)
+#define json_to_integer(json_) container_of(json_, json_integer_t, json)
+
+typedef struct {
+    size_t serial;
+    char key[1];
+} object_key_t;
+
+const object_key_t *jsonp_object_iter_fullkey(void *iter);
+
+void jsonp_error_init(json_error_t *error, const char *source);
+void jsonp_error_set(json_error_t *error, int line, int column,
+                     const char *msg, ...);
+
+#endif
diff --git a/jansson/src/load.c b/jansson/src/load.c
new file mode 100644 (file)
index 0000000..f05230b
--- /dev/null
@@ -0,0 +1,885 @@
+/*
+ * Copyright (c) 2009, 2010 Petri Lehtinen <petri@digip.org>
+ *
+ * Jansson is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+
+#define _GNU_SOURCE
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <assert.h>
+
+#include <jansson.h>
+#include "jansson_private.h"
+#include "strbuffer.h"
+#include "utf.h"
+
+#define TOKEN_INVALID         -1
+#define TOKEN_EOF              0
+#define TOKEN_STRING         256
+#define TOKEN_INTEGER        257
+#define TOKEN_REAL           258
+#define TOKEN_TRUE           259
+#define TOKEN_FALSE          260
+#define TOKEN_NULL           261
+
+/* read one byte from stream, return EOF on end of file */
+typedef int (*get_func)(void *data);
+
+/* return non-zero if end of file has been reached */
+typedef int (*eof_func)(void *data);
+
+typedef struct {
+    get_func get;
+    eof_func eof;
+    void *data;
+    int stream_pos;
+    char buffer[5];
+    int buffer_pos;
+} stream_t;
+
+
+typedef struct {
+    stream_t stream;
+    strbuffer_t saved_text;
+    int token;
+    int line, column;
+    union {
+        char *string;
+        json_int_t integer;
+        double real;
+    } value;
+} lex_t;
+
+
+/*** error reporting ***/
+
+static void error_set(json_error_t *error, const lex_t *lex,
+                      const char *msg, ...)
+{
+    va_list ap;
+    char msg_text[JSON_ERROR_TEXT_LENGTH];
+
+    int line = -1, col = -1;
+    const char *result = msg_text;
+
+    if(!error)
+        return;
+
+    va_start(ap, msg);
+    vsnprintf(msg_text, JSON_ERROR_TEXT_LENGTH, msg, ap);
+    va_end(ap);
+
+    if(lex)
+    {
+        const char *saved_text = strbuffer_value(&lex->saved_text);
+        char msg_with_context[JSON_ERROR_TEXT_LENGTH];
+
+        line = lex->line;
+
+        if(saved_text && saved_text[0])
+        {
+            if(lex->saved_text.length <= 20) {
+                snprintf(msg_with_context, JSON_ERROR_TEXT_LENGTH,
+                         "%s near '%s'", msg_text, saved_text);
+                result = msg_with_context;
+            }
+        }
+        else
+        {
+            snprintf(msg_with_context, JSON_ERROR_TEXT_LENGTH,
+                     "%s near end of file", msg_text);
+            result = msg_with_context;
+        }
+    }
+
+    jsonp_error_set(error, line, col, "%s", result);
+}
+
+
+/*** lexical analyzer ***/
+
+static void
+stream_init(stream_t *stream, get_func get, eof_func eof, void *data)
+{
+    stream->get = get;
+    stream->eof = eof;
+    stream->data = data;
+    stream->stream_pos = 0;
+    stream->buffer[0] = '\0';
+    stream->buffer_pos = 0;
+}
+
+static char stream_get(stream_t *stream, json_error_t *error)
+{
+    char c;
+
+    if(!stream->buffer[stream->buffer_pos])
+    {
+        stream->buffer[0] = stream->get(stream->data);
+        stream->buffer_pos = 0;
+
+        c = stream->buffer[0];
+
+        if((unsigned char)c >= 0x80 && c != (char)EOF)
+        {
+            /* multi-byte UTF-8 sequence */
+            int i, count;
+
+            count = utf8_check_first(c);
+            if(!count)
+                goto out;
+
+            assert(count >= 2);
+
+            for(i = 1; i < count; i++)
+                stream->buffer[i] = stream->get(stream->data);
+
+            if(!utf8_check_full(stream->buffer, count, NULL))
+                goto out;
+
+            stream->stream_pos += count;
+            stream->buffer[count] = '\0';
+        }
+        else {
+            stream->buffer[1] = '\0';
+            stream->stream_pos++;
+        }
+    }
+
+    return stream->buffer[stream->buffer_pos++];
+
+out:
+    error_set(error, NULL, "unable to decode byte 0x%x at position %d",
+              (unsigned char)c, stream->stream_pos);
+
+    stream->buffer[0] = EOF;
+    stream->buffer[1] = '\0';
+    stream->buffer_pos = 1;
+
+    return EOF;
+}
+
+static void stream_unget(stream_t *stream, char c)
+{
+    assert(stream->buffer_pos > 0);
+    stream->buffer_pos--;
+    assert(stream->buffer[stream->buffer_pos] == c);
+}
+
+
+static int lex_get(lex_t *lex, json_error_t *error)
+{
+    return stream_get(&lex->stream, error);
+}
+
+static int lex_eof(lex_t *lex)
+{
+    return lex->stream.eof(lex->stream.data);
+}
+
+static void lex_save(lex_t *lex, char c)
+{
+    strbuffer_append_byte(&lex->saved_text, c);
+}
+
+static int lex_get_save(lex_t *lex, json_error_t *error)
+{
+    char c = stream_get(&lex->stream, error);
+    lex_save(lex, c);
+    return c;
+}
+
+static void lex_unget_unsave(lex_t *lex, char c)
+{
+    char d;
+    stream_unget(&lex->stream, c);
+    d = strbuffer_pop(&lex->saved_text);
+    assert(c == d);
+}
+
+static void lex_save_cached(lex_t *lex)
+{
+    while(lex->stream.buffer[lex->stream.buffer_pos] != '\0')
+    {
+        lex_save(lex, lex->stream.buffer[lex->stream.buffer_pos]);
+        lex->stream.buffer_pos++;
+    }
+}
+
+/* assumes that str points to 'u' plus at least 4 valid hex digits */
+static int32_t decode_unicode_escape(const char *str)
+{
+    int i;
+    int32_t value = 0;
+
+    assert(str[0] == 'u');
+
+    for(i = 1; i <= 4; i++) {
+        char c = str[i];
+        value <<= 4;
+        if(isdigit(c))
+            value += c - '0';
+        else if(islower(c))
+            value += c - 'a' + 10;
+        else if(isupper(c))
+            value += c - 'A' + 10;
+        else
+            assert(0);
+    }
+
+    return value;
+}
+
+static void lex_scan_string(lex_t *lex, json_error_t *error)
+{
+    char c;
+    const char *p;
+    char *t;
+    int i;
+
+    lex->value.string = NULL;
+    lex->token = TOKEN_INVALID;
+
+    c = lex_get_save(lex, error);
+
+    while(c != '"') {
+        if(c == (char)EOF) {
+            lex_unget_unsave(lex, c);
+            if(lex_eof(lex))
+                error_set(error, lex, "premature end of input");
+            goto out;
+        }
+
+        else if((unsigned char)c <= 0x1F) {
+            /* control character */
+            lex_unget_unsave(lex, c);
+            if(c == '\n')
+                error_set(error, lex, "unexpected newline", c);
+            else
+                error_set(error, lex, "control character 0x%x", c);
+            goto out;
+        }
+
+        else if(c == '\\') {
+            c = lex_get_save(lex, error);
+            if(c == 'u') {
+                c = lex_get_save(lex, error);
+                for(i = 0; i < 4; i++) {
+                    if(!isxdigit(c)) {
+                        lex_unget_unsave(lex, c);
+                        error_set(error, lex, "invalid escape");
+                        goto out;
+                    }
+                    c = lex_get_save(lex, error);
+                }
+            }
+            else if(c == '"' || c == '\\' || c == '/' || c == 'b' ||
+                    c == 'f' || c == 'n' || c == 'r' || c == 't')
+                c = lex_get_save(lex, error);
+            else {
+                lex_unget_unsave(lex, c);
+                error_set(error, lex, "invalid escape");
+                goto out;
+            }
+        }
+        else
+            c = lex_get_save(lex, error);
+    }
+
+    /* the actual value is at most of the same length as the source
+       string, because:
+         - shortcut escapes (e.g. "\t") (length 2) are converted to 1 byte
+         - a single \uXXXX escape (length 6) is converted to at most 3 bytes
+         - two \uXXXX escapes (length 12) forming an UTF-16 surrogate pair
+           are converted to 4 bytes
+    */
+    lex->value.string = malloc(lex->saved_text.length + 1);
+    if(!lex->value.string) {
+        /* this is not very nice, since TOKEN_INVALID is returned */
+        goto out;
+    }
+
+    /* the target */
+    t = lex->value.string;
+
+    /* + 1 to skip the " */
+    p = strbuffer_value(&lex->saved_text) + 1;
+
+    while(*p != '"') {
+        if(*p == '\\') {
+            p++;
+            if(*p == 'u') {
+                char buffer[4];
+                int length;
+                int32_t value;
+
+                value = decode_unicode_escape(p);
+                p += 5;
+
+                if(0xD800 <= value && value <= 0xDBFF) {
+                    /* surrogate pair */
+                    if(*p == '\\' && *(p + 1) == 'u') {
+                        int32_t value2 = decode_unicode_escape(++p);
+                        p += 5;
+
+                        if(0xDC00 <= value2 && value2 <= 0xDFFF) {
+                            /* valid second surrogate */
+                            value =
+                                ((value - 0xD800) << 10) +
+                                (value2 - 0xDC00) +
+                                0x10000;
+                        }
+                        else {
+                            /* invalid second surrogate */
+                            error_set(error, lex,
+                                      "invalid Unicode '\\u%04X\\u%04X'",
+                                      value, value2);
+                            goto out;
+                        }
+                    }
+                    else {
+                        /* no second surrogate */
+                        error_set(error, lex, "invalid Unicode '\\u%04X'",
+                                  value);
+                        goto out;
+                    }
+                }
+                else if(0xDC00 <= value && value <= 0xDFFF) {
+                    error_set(error, lex, "invalid Unicode '\\u%04X'", value);
+                    goto out;
+                }
+                else if(value == 0)
+                {
+                    error_set(error, lex, "\\u0000 is not allowed");
+                    goto out;
+                }
+
+                if(utf8_encode(value, buffer, &length))
+                    assert(0);
+
+                memcpy(t, buffer, length);
+                t += length;
+            }
+            else {
+                switch(*p) {
+                    case '"': case '\\': case '/':
+                        *t = *p; break;
+                    case 'b': *t = '\b'; break;
+                    case 'f': *t = '\f'; break;
+                    case 'n': *t = '\n'; break;
+                    case 'r': *t = '\r'; break;
+                    case 't': *t = '\t'; break;
+                    default: assert(0);
+                }
+                t++;
+                p++;
+            }
+        }
+        else
+            *(t++) = *(p++);
+    }
+    *t = '\0';
+    lex->token = TOKEN_STRING;
+    return;
+
+out:
+    free(lex->value.string);
+}
+
+#if JSON_INTEGER_IS_LONG_LONG
+#define json_strtoint     strtoll
+#else
+#define json_strtoint     strtol
+#endif
+
+static int lex_scan_number(lex_t *lex, char c, json_error_t *error)
+{
+    const char *saved_text;
+    char *end;
+    double value;
+
+    lex->token = TOKEN_INVALID;
+
+    if(c == '-')
+        c = lex_get_save(lex, error);
+
+    if(c == '0') {
+        c = lex_get_save(lex, error);
+        if(isdigit(c)) {
+            lex_unget_unsave(lex, c);
+            goto out;
+        }
+    }
+    else if(isdigit(c)) {
+        c = lex_get_save(lex, error);
+        while(isdigit(c))
+            c = lex_get_save(lex, error);
+    }
+    else {
+      lex_unget_unsave(lex, c);
+      goto out;
+    }
+
+    if(c != '.' && c != 'E' && c != 'e') {
+        json_int_t value;
+
+        lex_unget_unsave(lex, c);
+
+        saved_text = strbuffer_value(&lex->saved_text);
+
+        errno = 0;
+        value = json_strtoint(saved_text, &end, 10);
+        if(errno == ERANGE) {
+            if(value < 0)
+                error_set(error, lex, "too big negative integer");
+            else
+                error_set(error, lex, "too big integer");
+            goto out;
+        }
+
+        assert(end == saved_text + lex->saved_text.length);
+
+        lex->token = TOKEN_INTEGER;
+        lex->value.integer = value;
+        return 0;
+    }
+
+    if(c == '.') {
+        c = lex_get(lex, error);
+        if(!isdigit(c))
+            goto out;
+        lex_save(lex, c);
+
+        c = lex_get_save(lex, error);
+        while(isdigit(c))
+            c = lex_get_save(lex, error);
+    }
+
+    if(c == 'E' || c == 'e') {
+        c = lex_get_save(lex, error);
+        if(c == '+' || c == '-')
+            c = lex_get_save(lex, error);
+
+        if(!isdigit(c)) {
+            lex_unget_unsave(lex, c);
+            goto out;
+        }
+
+        c = lex_get_save(lex, error);
+        while(isdigit(c))
+            c = lex_get_save(lex, error);
+    }
+
+    lex_unget_unsave(lex, c);
+
+    saved_text = strbuffer_value(&lex->saved_text);
+    value = strtod(saved_text, &end);
+    assert(end == saved_text + lex->saved_text.length);
+
+    if(errno == ERANGE && value != 0) {
+        error_set(error, lex, "real number overflow");
+        goto out;
+    }
+
+    lex->token = TOKEN_REAL;
+    lex->value.real = value;
+    return 0;
+
+out:
+    return -1;
+}
+
+static int lex_scan(lex_t *lex, json_error_t *error)
+{
+    char c;
+
+    strbuffer_clear(&lex->saved_text);
+
+    if(lex->token == TOKEN_STRING) {
+        free(lex->value.string);
+        lex->value.string = NULL;
+    }
+
+    c = lex_get(lex, error);
+    while(c == ' ' || c == '\t' || c == '\n' || c == '\r')
+    {
+        if(c == '\n')
+            lex->line++;
+
+        c = lex_get(lex, error);
+    }
+
+    if(c == (char)EOF) {
+        if(lex_eof(lex))
+            lex->token = TOKEN_EOF;
+        else
+            lex->token = TOKEN_INVALID;
+        goto out;
+    }
+
+    lex_save(lex, c);
+
+    if(c == '{' || c == '}' || c == '[' || c == ']' || c == ':' || c == ',')
+        lex->token = c;
+
+    else if(c == '"')
+        lex_scan_string(lex, error);
+
+    else if(isdigit(c) || c == '-') {
+        if(lex_scan_number(lex, c, error))
+            goto out;
+    }
+
+    else if(isupper(c) || islower(c)) {
+        /* eat up the whole identifier for clearer error messages */
+        const char *saved_text;
+
+        c = lex_get_save(lex, error);
+        while(isupper(c) || islower(c))
+            c = lex_get_save(lex, error);
+        lex_unget_unsave(lex, c);
+
+        saved_text = strbuffer_value(&lex->saved_text);
+
+        if(strcmp(saved_text, "true") == 0)
+            lex->token = TOKEN_TRUE;
+        else if(strcmp(saved_text, "false") == 0)
+            lex->token = TOKEN_FALSE;
+        else if(strcmp(saved_text, "null") == 0)
+            lex->token = TOKEN_NULL;
+        else
+            lex->token = TOKEN_INVALID;
+    }
+
+    else {
+        /* save the rest of the input UTF-8 sequence to get an error
+           message of valid UTF-8 */
+        lex_save_cached(lex);
+        lex->token = TOKEN_INVALID;
+    }
+
+out:
+    return lex->token;
+}
+
+static char *lex_steal_string(lex_t *lex)
+{
+    char *result = NULL;
+    if(lex->token == TOKEN_STRING)
+    {
+        result = lex->value.string;
+        lex->value.string = NULL;
+    }
+    return result;
+}
+
+static int lex_init(lex_t *lex, get_func get, eof_func eof, void *data)
+{
+    stream_init(&lex->stream, get, eof, data);
+    if(strbuffer_init(&lex->saved_text))
+        return -1;
+
+    lex->token = TOKEN_INVALID;
+    lex->line = 1;
+
+    return 0;
+}
+
+static void lex_close(lex_t *lex)
+{
+    if(lex->token == TOKEN_STRING)
+        free(lex->value.string);
+    strbuffer_close(&lex->saved_text);
+}
+
+
+/*** parser ***/
+
+static json_t *parse_value(lex_t *lex, json_error_t *error);
+
+static json_t *parse_object(lex_t *lex, json_error_t *error)
+{
+    json_t *object = json_object();
+    if(!object)
+        return NULL;
+
+    lex_scan(lex, error);
+    if(lex->token == '}')
+        return object;
+
+    while(1) {
+        char *key;
+        json_t *value;
+
+        if(lex->token != TOKEN_STRING) {
+            error_set(error, lex, "string or '}' expected");
+            goto error;
+        }
+
+        key = lex_steal_string(lex);
+        if(!key)
+            return NULL;
+
+        lex_scan(lex, error);
+        if(lex->token != ':') {
+            free(key);
+            error_set(error, lex, "':' expected");
+            goto error;
+        }
+
+        lex_scan(lex, error);
+        value = parse_value(lex, error);
+        if(!value) {
+            free(key);
+            goto error;
+        }
+
+        if(json_object_set_nocheck(object, key, value)) {
+            free(key);
+            json_decref(value);
+            goto error;
+        }
+
+        json_decref(value);
+        free(key);
+
+        lex_scan(lex, error);
+        if(lex->token != ',')
+            break;
+
+        lex_scan(lex, error);
+    }
+
+    if(lex->token != '}') {
+        error_set(error, lex, "'}' expected");
+        goto error;
+    }
+
+    return object;
+
+error:
+    json_decref(object);
+    return NULL;
+}
+
+static json_t *parse_array(lex_t *lex, json_error_t *error)
+{
+    json_t *array = json_array();
+    if(!array)
+        return NULL;
+
+    lex_scan(lex, error);
+    if(lex->token == ']')
+        return array;
+
+    while(lex->token) {
+        json_t *elem = parse_value(lex, error);
+        if(!elem)
+            goto error;
+
+        if(json_array_append(array, elem)) {
+            json_decref(elem);
+            goto error;
+        }
+        json_decref(elem);
+
+        lex_scan(lex, error);
+        if(lex->token != ',')
+            break;
+
+        lex_scan(lex, error);
+    }
+
+    if(lex->token != ']') {
+        error_set(error, lex, "']' expected");
+        goto error;
+    }
+
+    return array;
+
+error:
+    json_decref(array);
+    return NULL;
+}
+
+static json_t *parse_value(lex_t *lex, json_error_t *error)
+{
+    json_t *json;
+
+    switch(lex->token) {
+        case TOKEN_STRING: {
+            json = json_string_nocheck(lex->value.string);
+            break;
+        }
+
+        case TOKEN_INTEGER: {
+            json = json_integer(lex->value.integer);
+            break;
+        }
+
+        case TOKEN_REAL: {
+            json = json_real(lex->value.real);
+            break;
+        }
+
+        case TOKEN_TRUE:
+            json = json_true();
+            break;
+
+        case TOKEN_FALSE:
+            json = json_false();
+            break;
+
+        case TOKEN_NULL:
+            json = json_null();
+            break;
+
+        case '{':
+            json = parse_object(lex, error);
+            break;
+
+        case '[':
+            json = parse_array(lex, error);
+            break;
+
+        case TOKEN_INVALID:
+            error_set(error, lex, "invalid token");
+            return NULL;
+
+        default:
+            error_set(error, lex, "unexpected token");
+            return NULL;
+    }
+
+    if(!json)
+        return NULL;
+
+    return json;
+}
+
+static json_t *parse_json(lex_t *lex, json_error_t *error)
+{
+    lex_scan(lex, error);
+    if(lex->token != '[' && lex->token != '{') {
+        error_set(error, lex, "'[' or '{' expected");
+        return NULL;
+    }
+
+    return parse_value(lex, error);
+}
+
+typedef struct
+{
+    const char *data;
+    int pos;
+} string_data_t;
+
+static int string_get(void *data)
+{
+    char c;
+    string_data_t *stream = (string_data_t *)data;
+    c = stream->data[stream->pos];
+    if(c == '\0')
+        return EOF;
+    else
+    {
+        stream->pos++;
+        return c;
+    }
+}
+
+static int string_eof(void *data)
+{
+    string_data_t *stream = (string_data_t *)data;
+    return (stream->data[stream->pos] == '\0');
+}
+
+json_t *json_loads(const char *string, size_t flags, json_error_t *error)
+{
+    lex_t lex;
+    json_t *result;
+    (void)flags; /* unused */
+
+    string_data_t stream_data = {string, 0};
+
+    if(lex_init(&lex, string_get, string_eof, (void *)&stream_data))
+        return NULL;
+
+    jsonp_error_init(error, "<string>");
+
+    result = parse_json(&lex, error);
+    if(!result)
+        goto out;
+
+    lex_scan(&lex, error);
+    if(lex.token != TOKEN_EOF) {
+        error_set(error, &lex, "end of file expected");
+        json_decref(result);
+        result = NULL;
+    }
+
+out:
+    lex_close(&lex);
+    return result;
+}
+
+json_t *json_loadf(FILE *input, size_t flags, json_error_t *error)
+{
+    lex_t lex;
+    const char *source;
+    json_t *result;
+    (void)flags; /* unused */
+
+    if(lex_init(&lex, (get_func)fgetc, (eof_func)feof, input))
+        return NULL;
+
+    if(input == stdin)
+        source = "<stdin>";
+    else
+        source = "<stream>";
+
+    jsonp_error_init(error, source);
+
+    result = parse_json(&lex, error);
+    if(!result)
+        goto out;
+
+    lex_scan(&lex, error);
+    if(lex.token != TOKEN_EOF) {
+        error_set(error, &lex, "end of file expected");
+        json_decref(result);
+        result = NULL;
+    }
+
+out:
+    lex_close(&lex);
+    return result;
+}
+
+json_t *json_load_file(const char *path, size_t flags, json_error_t *error)
+{
+    json_t *result;
+    FILE *fp;
+
+    jsonp_error_init(error, path);
+
+    fp = fopen(path, "r");
+    if(!fp)
+    {
+        error_set(error, NULL, "unable to open %s: %s",
+                  path, strerror(errno));
+        return NULL;
+    }
+
+    result = json_loadf(fp, flags, error);
+
+    fclose(fp);
+    return result;
+}
diff --git a/jansson/src/strbuffer.c b/jansson/src/strbuffer.c
new file mode 100644 (file)
index 0000000..4e866bd
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2009, 2010 Petri Lehtinen <petri@digip.org>
+ *
+ * Jansson is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <string.h>
+#include "jansson_private.h"
+#include "strbuffer.h"
+
+#define STRBUFFER_MIN_SIZE  16
+#define STRBUFFER_FACTOR    2
+
+int strbuffer_init(strbuffer_t *strbuff)
+{
+    strbuff->size = STRBUFFER_MIN_SIZE;
+    strbuff->length = 0;
+
+    strbuff->value = malloc(strbuff->size);
+    if(!strbuff->value)
+        return -1;
+
+    /* initialize to empty */
+    strbuff->value[0] = '\0';
+    return 0;
+}
+
+void strbuffer_close(strbuffer_t *strbuff)
+{
+    free(strbuff->value);
+    strbuff->size = 0;
+    strbuff->length = 0;
+    strbuff->value = NULL;
+}
+
+void strbuffer_clear(strbuffer_t *strbuff)
+{
+    strbuff->length = 0;
+    strbuff->value[0] = '\0';
+}
+
+const char *strbuffer_value(const strbuffer_t *strbuff)
+{
+    return strbuff->value;
+}
+
+char *strbuffer_steal_value(strbuffer_t *strbuff)
+{
+    char *result = strbuff->value;
+    strbuffer_init(strbuff);
+    return result;
+}
+
+int strbuffer_append(strbuffer_t *strbuff, const char *string)
+{
+    return strbuffer_append_bytes(strbuff, string, strlen(string));
+}
+
+int strbuffer_append_byte(strbuffer_t *strbuff, char byte)
+{
+    return strbuffer_append_bytes(strbuff, &byte, 1);
+}
+
+int strbuffer_append_bytes(strbuffer_t *strbuff, const char *data, int size)
+{
+    if(strbuff->length + size >= strbuff->size)
+    {
+        strbuff->size = max(strbuff->size * STRBUFFER_FACTOR,
+                            strbuff->length + size + 1);
+
+        strbuff->value = realloc(strbuff->value, strbuff->size);
+        if(!strbuff->value)
+            return -1;
+    }
+
+    memcpy(strbuff->value + strbuff->length, data, size);
+    strbuff->length += size;
+    strbuff->value[strbuff->length] = '\0';
+
+    return 0;
+}
+
+char strbuffer_pop(strbuffer_t *strbuff)
+{
+    if(strbuff->length > 0) {
+        char c = strbuff->value[--strbuff->length];
+        strbuff->value[strbuff->length] = '\0';
+        return c;
+    }
+    else
+        return '\0';
+}
diff --git a/jansson/src/strbuffer.h b/jansson/src/strbuffer.h
new file mode 100644 (file)
index 0000000..f4c5f77
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2009, 2010 Petri Lehtinen <petri@digip.org>
+ *
+ * Jansson is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+
+#ifndef STRBUFFER_H
+#define STRBUFFER_H
+
+typedef struct {
+    char *value;
+    int length;   /* bytes used */
+    int size;     /* bytes allocated */
+} strbuffer_t;
+
+int strbuffer_init(strbuffer_t *strbuff);
+void strbuffer_close(strbuffer_t *strbuff);
+
+void strbuffer_clear(strbuffer_t *strbuff);
+
+const char *strbuffer_value(const strbuffer_t *strbuff);
+char *strbuffer_steal_value(strbuffer_t *strbuff);
+
+int strbuffer_append(strbuffer_t *strbuff, const char *string);
+int strbuffer_append_byte(strbuffer_t *strbuff, char byte);
+int strbuffer_append_bytes(strbuffer_t *strbuff, const char *data, int size);
+
+char strbuffer_pop(strbuffer_t *strbuff);
+
+#endif
diff --git a/jansson/src/utf.c b/jansson/src/utf.c
new file mode 100644 (file)
index 0000000..92484d0
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2009, 2010 Petri Lehtinen <petri@digip.org>
+ *
+ * Jansson is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+
+#include <string.h>
+#include "utf.h"
+
+int utf8_encode(int32_t codepoint, char *buffer, int *size)
+{
+    if(codepoint < 0)
+        return -1;
+    else if(codepoint < 0x80)
+    {
+        buffer[0] = (char)codepoint;
+        *size = 1;
+    }
+    else if(codepoint < 0x800)
+    {
+        buffer[0] = 0xC0 + ((codepoint & 0x7C0) >> 6);
+        buffer[1] = 0x80 + ((codepoint & 0x03F));
+        *size = 2;
+    }
+    else if(codepoint < 0x10000)
+    {
+        buffer[0] = 0xE0 + ((codepoint & 0xF000) >> 12);
+        buffer[1] = 0x80 + ((codepoint & 0x0FC0) >> 6);
+        buffer[2] = 0x80 + ((codepoint & 0x003F));
+        *size = 3;
+    }
+    else if(codepoint <= 0x10FFFF)
+    {
+        buffer[0] = 0xF0 + ((codepoint & 0x1C0000) >> 18);
+        buffer[1] = 0x80 + ((codepoint & 0x03F000) >> 12);
+        buffer[2] = 0x80 + ((codepoint & 0x000FC0) >> 6);
+        buffer[3] = 0x80 + ((codepoint & 0x00003F));
+        *size = 4;
+    }
+    else
+        return -1;
+
+    return 0;
+}
+
+int utf8_check_first(char byte)
+{
+    unsigned char u = (unsigned char)byte;
+
+    if(u < 0x80)
+        return 1;
+
+    if(0x80 <= u && u <= 0xBF) {
+        /* second, third or fourth byte of a multi-byte
+           sequence, i.e. a "continuation byte" */
+        return 0;
+    }
+    else if(u == 0xC0 || u == 0xC1) {
+        /* overlong encoding of an ASCII byte */
+        return 0;
+    }
+    else if(0xC2 <= u && u <= 0xDF) {
+        /* 2-byte sequence */
+        return 2;
+    }
+
+    else if(0xE0 <= u && u <= 0xEF) {
+        /* 3-byte sequence */
+        return 3;
+    }
+    else if(0xF0 <= u && u <= 0xF4) {
+        /* 4-byte sequence */
+        return 4;
+    }
+    else { /* u >= 0xF5 */
+        /* Restricted (start of 4-, 5- or 6-byte sequence) or invalid
+           UTF-8 */
+        return 0;
+    }
+}
+
+int utf8_check_full(const char *buffer, int size, int32_t *codepoint)
+{
+    int i;
+    int32_t value = 0;
+    unsigned char u = (unsigned char)buffer[0];
+
+    if(size == 2)
+    {
+        value = u & 0x1F;
+    }
+    else if(size == 3)
+    {
+        value = u & 0xF;
+    }
+    else if(size == 4)
+    {
+        value = u & 0x7;
+    }
+    else
+        return 0;
+
+    for(i = 1; i < size; i++)
+    {
+        u = (unsigned char)buffer[i];
+
+        if(u < 0x80 || u > 0xBF) {
+            /* not a continuation byte */
+            return 0;
+        }
+
+        value = (value << 6) + (u & 0x3F);
+    }
+
+    if(value > 0x10FFFF) {
+        /* not in Unicode range */
+        return 0;
+    }
+
+    else if(0xD800 <= value && value <= 0xDFFF) {
+        /* invalid code point (UTF-16 surrogate halves) */
+        return 0;
+    }
+
+    else if((size == 2 && value < 0x80) ||
+            (size == 3 && value < 0x800) ||
+            (size == 4 && value < 0x10000)) {
+        /* overlong encoding */
+        return 0;
+    }
+
+    if(codepoint)
+        *codepoint = value;
+
+    return 1;
+}
+
+const char *utf8_iterate(const char *buffer, int32_t *codepoint)
+{
+    int count;
+    int32_t value;
+
+    if(!*buffer)
+        return buffer;
+
+    count = utf8_check_first(buffer[0]);
+    if(count <= 0)
+        return NULL;
+
+    if(count == 1)
+        value = (unsigned char)buffer[0];
+    else
+    {
+        if(!utf8_check_full(buffer, count, &value))
+            return NULL;
+    }
+
+    if(codepoint)
+        *codepoint = value;
+
+    return buffer + count;
+}
+
+int utf8_check_string(const char *string, int length)
+{
+    int i;
+
+    if(length == -1)
+        length = strlen(string);
+
+    for(i = 0; i < length; i++)
+    {
+        int count = utf8_check_first(string[i]);
+        if(count == 0)
+            return 0;
+        else if(count > 1)
+        {
+            if(i + count > length)
+                return 0;
+
+            if(!utf8_check_full(&string[i], count, NULL))
+                return 0;
+
+            i += count - 1;
+        }
+    }
+
+    return 1;
+}
diff --git a/jansson/src/utf.h b/jansson/src/utf.h
new file mode 100644 (file)
index 0000000..862c548
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2009, 2010 Petri Lehtinen <petri@digip.org>
+ *
+ * Jansson is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+
+#ifndef UTF_H
+#define UTF_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+
+#ifdef HAVE_INTTYPES_H
+/* inttypes.h includes stdint.h in a standard environment, so there's
+no need to include stdint.h separately. If inttypes.h doesn't define
+int32_t, it's defined in config.h. */
+#include <inttypes.h>
+#endif /* HAVE_INTTYPES_H */
+
+#else /* !HAVE_CONFIG_H */
+#ifdef _WIN32
+typedef int int32_t;
+#else /* !_WIN32 */
+/* Assume a standard environment */
+#include <inttypes.h>
+#endif /* _WIN32 */
+
+#endif /* HAVE_CONFIG_H */
+
+int utf8_encode(int codepoint, char *buffer, int *size);
+
+int utf8_check_first(char byte);
+int utf8_check_full(const char *buffer, int size, int32_t *codepoint);
+const char *utf8_iterate(const char *buffer, int32_t *codepoint);
+
+int utf8_check_string(const char *string, int length);
+
+#endif
diff --git a/jansson/src/value.c b/jansson/src/value.c
new file mode 100644 (file)
index 0000000..89d7b71
--- /dev/null
@@ -0,0 +1,967 @@
+/*
+ * Copyright (c) 2009, 2010 Petri Lehtinen <petri@digip.org>
+ *
+ * Jansson is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+
+#define _GNU_SOURCE
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <jansson.h>
+#include "hashtable.h"
+#include "jansson_private.h"
+#include "utf.h"
+
+
+static JSON_INLINE void json_init(json_t *json, json_type type)
+{
+    json->type = type;
+    json->refcount = 1;
+}
+
+
+/*** object ***/
+
+/* This macro just returns a pointer that's a few bytes backwards from
+   string. This makes it possible to pass a pointer to object_key_t
+   when only the string inside it is used, without actually creating
+   an object_key_t instance. */
+#define string_to_key(string)  container_of(string, object_key_t, key)
+
+static size_t hash_key(const void *ptr)
+{
+    const char *str = ((const object_key_t *)ptr)->key;
+
+    size_t hash = 5381;
+    size_t c;
+
+    while((c = (size_t)*str))
+    {
+        hash = ((hash << 5) + hash) + c;
+        str++;
+    }
+
+    return hash;
+}
+
+static int key_equal(const void *ptr1, const void *ptr2)
+{
+    return strcmp(((const object_key_t *)ptr1)->key,
+                  ((const object_key_t *)ptr2)->key) == 0;
+}
+
+static void value_decref(void *value)
+{
+    json_decref((json_t *)value);
+}
+
+json_t *json_object(void)
+{
+    json_object_t *object = malloc(sizeof(json_object_t));
+    if(!object)
+        return NULL;
+    json_init(&object->json, JSON_OBJECT);
+
+    if(hashtable_init(&object->hashtable, hash_key, key_equal,
+                      free, value_decref))
+    {
+        free(object);
+        return NULL;
+    }
+
+    object->serial = 0;
+    object->visited = 0;
+
+    return &object->json;
+}
+
+static void json_delete_object(json_object_t *object)
+{
+    hashtable_close(&object->hashtable);
+    free(object);
+}
+
+size_t json_object_size(const json_t *json)
+{
+    json_object_t *object;
+
+    if(!json_is_object(json))
+        return -1;
+
+    object = json_to_object(json);
+    return object->hashtable.size;
+}
+
+json_t *json_object_get(const json_t *json, const char *key)
+{
+    json_object_t *object;
+
+    if(!json_is_object(json))
+        return NULL;
+
+    object = json_to_object(json);
+    return hashtable_get(&object->hashtable, string_to_key(key));
+}
+
+int json_object_set_new_nocheck(json_t *json, const char *key, json_t *value)
+{
+    json_object_t *object;
+    object_key_t *k;
+
+    if(!key || !value)
+        return -1;
+
+    if(!json_is_object(json) || json == value)
+    {
+        json_decref(value);
+        return -1;
+    }
+    object = json_to_object(json);
+
+    /* offsetof(...) returns the size of object_key_t without the
+       last, flexible member. This way, the correct amount is
+       allocated. */
+    k = malloc(offsetof(object_key_t, key) +
+    strlen(key) + 1); if(!k) return -1;
+
+    k->serial = object->serial++;
+    strcpy(k->key, key);
+
+    if(hashtable_set(&object->hashtable, k, value))
+    {
+        json_decref(value);
+        return -1;
+    }
+
+    return 0;
+}
+
+int json_object_set_new(json_t *json, const char *key, json_t *value)
+{
+    if(!key || !utf8_check_string(key, -1))
+    {
+        json_decref(value);
+        return -1;
+    }
+
+    return json_object_set_new_nocheck(json, key, value);
+}
+
+int json_object_del(json_t *json, const char *key)
+{
+    json_object_t *object;
+
+    if(!json_is_object(json))
+        return -1;
+
+    object = json_to_object(json);
+    return hashtable_del(&object->hashtable, string_to_key(key));
+}
+
+int json_object_clear(json_t *json)
+{
+    json_object_t *object;
+
+    if(!json_is_object(json))
+        return -1;
+
+    object = json_to_object(json);
+    hashtable_clear(&object->hashtable);
+
+    return 0;
+}
+
+int json_object_update(json_t *object, json_t *other)
+{
+    void *iter;
+
+    if(!json_is_object(object) || !json_is_object(other))
+        return -1;
+
+    iter = json_object_iter(other);
+    while(iter) {
+        const char *key;
+        json_t *value;
+
+        key = json_object_iter_key(iter);
+        value = json_object_iter_value(iter);
+
+        if(json_object_set_nocheck(object, key, value))
+            return -1;
+
+        iter = json_object_iter_next(other, iter);
+    }
+
+    return 0;
+}
+
+void *json_object_iter(json_t *json)
+{
+    json_object_t *object;
+
+    if(!json_is_object(json))
+        return NULL;
+
+    object = json_to_object(json);
+    return hashtable_iter(&object->hashtable);
+}
+
+void *json_object_iter_at(json_t *json, const char *key)
+{
+    json_object_t *object;
+
+    if(!key || !json_is_object(json))
+        return NULL;
+
+    object = json_to_object(json);
+    return hashtable_iter_at(&object->hashtable, string_to_key(key));
+}
+
+void *json_object_iter_next(json_t *json, void *iter)
+{
+    json_object_t *object;
+
+    if(!json_is_object(json) || iter == NULL)
+        return NULL;
+
+    object = json_to_object(json);
+    return hashtable_iter_next(&object->hashtable, iter);
+}
+
+const object_key_t *jsonp_object_iter_fullkey(void *iter)
+{
+    if(!iter)
+        return NULL;
+
+    return hashtable_iter_key(iter);
+}
+
+const char *json_object_iter_key(void *iter)
+{
+    if(!iter)
+        return NULL;
+
+    return jsonp_object_iter_fullkey(iter)->key;
+}
+
+json_t *json_object_iter_value(void *iter)
+{
+    if(!iter)
+        return NULL;
+
+    return (json_t *)hashtable_iter_value(iter);
+}
+
+int json_object_iter_set_new(json_t *json, void *iter, json_t *value)
+{
+    json_object_t *object;
+
+    if(!json_is_object(json) || !iter || !value)
+        return -1;
+
+    object = json_to_object(json);
+    hashtable_iter_set(&object->hashtable, iter, value);
+
+    return 0;
+}
+
+static int json_object_equal(json_t *object1, json_t *object2)
+{
+    void *iter;
+
+    if(json_object_size(object1) != json_object_size(object2))
+        return 0;
+
+    iter = json_object_iter(object1);
+    while(iter)
+    {
+        const char *key;
+        json_t *value1, *value2;
+
+        key = json_object_iter_key(iter);
+        value1 = json_object_iter_value(iter);
+        value2 = json_object_get(object2, key);
+
+        if(!json_equal(value1, value2))
+            return 0;
+
+        iter = json_object_iter_next(object1, iter);
+    }
+
+    return 1;
+}
+
+static json_t *json_object_copy(json_t *object)
+{
+    json_t *result;
+    void *iter;
+
+    result = json_object();
+    if(!result)
+        return NULL;
+
+    iter = json_object_iter(object);
+    while(iter)
+    {
+        const char *key;
+        json_t *value;
+
+        key = json_object_iter_key(iter);
+        value = json_object_iter_value(iter);
+        json_object_set_nocheck(result, key, value);
+
+        iter = json_object_iter_next(object, iter);
+    }
+
+    return result;
+}
+
+static json_t *json_object_deep_copy(json_t *object)
+{
+    json_t *result;
+    void *iter;
+
+    result = json_object();
+    if(!result)
+        return NULL;
+
+    iter = json_object_iter(object);
+    while(iter)
+    {
+        const char *key;
+        json_t *value;
+
+        key = json_object_iter_key(iter);
+        value = json_object_iter_value(iter);
+        json_object_set_new_nocheck(result, key, json_deep_copy(value));
+
+        iter = json_object_iter_next(object, iter);
+    }
+
+    return result;
+}
+
+
+/*** array ***/
+
+json_t *json_array(void)
+{
+    json_array_t *array = malloc(sizeof(json_array_t));
+    if(!array)
+        return NULL;
+    json_init(&array->json, JSON_ARRAY);
+
+    array->entries = 0;
+    array->size = 8;
+
+    array->table = malloc(array->size * sizeof(json_t *));
+    if(!array->table) {
+        free(array);
+        return NULL;
+    }
+
+    array->visited = 0;
+
+    return &array->json;
+}
+
+static void json_delete_array(json_array_t *array)
+{
+    size_t i;
+
+    for(i = 0; i < array->entries; i++)
+        json_decref(array->table[i]);
+
+    free(array->table);
+    free(array);
+}
+
+size_t json_array_size(const json_t *json)
+{
+    if(!json_is_array(json))
+        return 0;
+
+    return json_to_array(json)->entries;
+}
+
+json_t *json_array_get(const json_t *json, size_t index)
+{
+    json_array_t *array;
+    if(!json_is_array(json))
+        return NULL;
+    array = json_to_array(json);
+
+    if(index >= array->entries)
+        return NULL;
+
+    return array->table[index];
+}
+
+int json_array_set_new(json_t *json, size_t index, json_t *value)
+{
+    json_array_t *array;
+
+    if(!value)
+        return -1;
+
+    if(!json_is_array(json) || json == value)
+    {
+        json_decref(value);
+        return -1;
+    }
+    array = json_to_array(json);
+
+    if(index >= array->entries)
+    {
+        json_decref(value);
+        return -1;
+    }
+
+    json_decref(array->table[index]);
+    array->table[index] = value;
+
+    return 0;
+}
+
+static void array_move(json_array_t *array, size_t dest,
+                       size_t src, size_t count)
+{
+    memmove(&array->table[dest], &array->table[src], count * sizeof(json_t *));
+}
+
+static void array_copy(json_t **dest, size_t dpos,
+                       json_t **src, size_t spos,
+                       size_t count)
+{
+    memcpy(&dest[dpos], &src[spos], count * sizeof(json_t *));
+}
+
+static json_t **json_array_grow(json_array_t *array,
+                                size_t amount,
+                                int copy)
+{
+    size_t new_size;
+    json_t **old_table, **new_table;
+
+    if(array->entries + amount <= array->size)
+        return array->table;
+
+    old_table = array->table;
+
+    new_size = max(array->size + amount, array->size * 2);
+    new_table = malloc(new_size * sizeof(json_t *));
+    if(!new_table)
+        return NULL;
+
+    array->size = new_size;
+    array->table = new_table;
+
+    if(copy) {
+        array_copy(array->table, 0, old_table, 0, array->entries);
+        free(old_table);
+        return array->table;
+    }
+
+    return old_table;
+}
+
+int json_array_append_new(json_t *json, json_t *value)
+{
+    json_array_t *array;
+
+    if(!value)
+        return -1;
+
+    if(!json_is_array(json) || json == value)
+    {
+        json_decref(value);
+        return -1;
+    }
+    array = json_to_array(json);
+
+    if(!json_array_grow(array, 1, 1)) {
+        json_decref(value);
+        return -1;
+    }
+
+    array->table[array->entries] = value;
+    array->entries++;
+
+    return 0;
+}
+
+int json_array_insert_new(json_t *json, size_t index, json_t *value)
+{
+    json_array_t *array;
+    json_t **old_table;
+
+    if(!value)
+        return -1;
+
+    if(!json_is_array(json) || json == value) {
+        json_decref(value);
+        return -1;
+    }
+    array = json_to_array(json);
+
+    if(index > array->entries) {
+        json_decref(value);
+        return -1;
+    }
+
+    old_table = json_array_grow(array, 1, 0);
+    if(!old_table) {
+        json_decref(value);
+        return -1;
+    }
+
+    if(old_table != array->table) {
+        array_copy(array->table, 0, old_table, 0, index);
+        array_copy(array->table, index + 1, old_table, index,
+                   array->entries - index);
+        free(old_table);
+    }
+    else
+        array_move(array, index + 1, index, array->entries - index);
+
+    array->table[index] = value;
+    array->entries++;
+
+    return 0;
+}
+
+int json_array_remove(json_t *json, size_t index)
+{
+    json_array_t *array;
+
+    if(!json_is_array(json))
+        return -1;
+    array = json_to_array(json);
+
+    if(index >= array->entries)
+        return -1;
+
+    json_decref(array->table[index]);
+
+    array_move(array, index, index + 1, array->entries - index);
+    array->entries--;
+
+    return 0;
+}
+
+int json_array_clear(json_t *json)
+{
+    json_array_t *array;
+    size_t i;
+
+    if(!json_is_array(json))
+        return -1;
+    array = json_to_array(json);
+
+    for(i = 0; i < array->entries; i++)
+        json_decref(array->table[i]);
+
+    array->entries = 0;
+    return 0;
+}
+
+int json_array_extend(json_t *json, json_t *other_json)
+{
+    json_array_t *array, *other;
+    size_t i;
+
+    if(!json_is_array(json) || !json_is_array(other_json))
+        return -1;
+    array = json_to_array(json);
+    other = json_to_array(other_json);
+
+    if(!json_array_grow(array, other->entries, 1))
+        return -1;
+
+    for(i = 0; i < other->entries; i++)
+        json_incref(other->table[i]);
+
+    array_copy(array->table, array->entries, other->table, 0, other->entries);
+
+    array->entries += other->entries;
+    return 0;
+}
+
+static int json_array_equal(json_t *array1, json_t *array2)
+{
+    size_t i, size;
+
+    size = json_array_size(array1);
+    if(size != json_array_size(array2))
+        return 0;
+
+    for(i = 0; i < size; i++)
+    {
+        json_t *value1, *value2;
+
+        value1 = json_array_get(array1, i);
+        value2 = json_array_get(array2, i);
+
+        if(!json_equal(value1, value2))
+            return 0;
+    }
+
+    return 1;
+}
+
+static json_t *json_array_copy(json_t *array)
+{
+    json_t *result;
+    size_t i;
+
+    result = json_array();
+    if(!result)
+        return NULL;
+
+    for(i = 0; i < json_array_size(array); i++)
+        json_array_append(result, json_array_get(array, i));
+
+    return result;
+}
+
+static json_t *json_array_deep_copy(json_t *array)
+{
+    json_t *result;
+    size_t i;
+
+    result = json_array();
+    if(!result)
+        return NULL;
+
+    for(i = 0; i < json_array_size(array); i++)
+        json_array_append_new(result, json_deep_copy(json_array_get(array, i)));
+
+    return result;
+}
+
+/*** string ***/
+
+json_t *json_string_nocheck(const char *value)
+{
+    json_string_t *string;
+
+    if(!value)
+        return NULL;
+
+    string = malloc(sizeof(json_string_t));
+    if(!string)
+        return NULL;
+    json_init(&string->json, JSON_STRING);
+
+    string->value = strdup(value);
+    if(!string->value) {
+        free(string);
+        return NULL;
+    }
+
+    return &string->json;
+}
+
+json_t *json_string(const char *value)
+{
+    if(!value || !utf8_check_string(value, -1))
+        return NULL;
+
+    return json_string_nocheck(value);
+}
+
+const char *json_string_value(const json_t *json)
+{
+    if(!json_is_string(json))
+        return NULL;
+
+    return json_to_string(json)->value;
+}
+
+int json_string_set_nocheck(json_t *json, const char *value)
+{
+    char *dup;
+    json_string_t *string;
+
+    dup = strdup(value);
+    if(!dup)
+        return -1;
+
+    string = json_to_string(json);
+    free(string->value);
+    string->value = dup;
+
+    return 0;
+}
+
+int json_string_set(json_t *json, const char *value)
+{
+    if(!value || !utf8_check_string(value, -1))
+        return -1;
+
+    return json_string_set_nocheck(json, value);
+}
+
+static void json_delete_string(json_string_t *string)
+{
+    free(string->value);
+    free(string);
+}
+
+static int json_string_equal(json_t *string1, json_t *string2)
+{
+    return strcmp(json_string_value(string1), json_string_value(string2)) == 0;
+}
+
+static json_t *json_string_copy(json_t *string)
+{
+    return json_string_nocheck(json_string_value(string));
+}
+
+
+/*** integer ***/
+
+json_t *json_integer(json_int_t value)
+{
+    json_integer_t *integer = malloc(sizeof(json_integer_t));
+    if(!integer)
+        return NULL;
+    json_init(&integer->json, JSON_INTEGER);
+
+    integer->value = value;
+    return &integer->json;
+}
+
+json_int_t json_integer_value(const json_t *json)
+{
+    if(!json_is_integer(json))
+        return 0;
+
+    return json_to_integer(json)->value;
+}
+
+int json_integer_set(json_t *json, json_int_t value)
+{
+    if(!json_is_integer(json))
+        return -1;
+
+    json_to_integer(json)->value = value;
+
+    return 0;
+}
+
+static void json_delete_integer(json_integer_t *integer)
+{
+    free(integer);
+}
+
+static int json_integer_equal(json_t *integer1, json_t *integer2)
+{
+    return json_integer_value(integer1) == json_integer_value(integer2);
+}
+
+static json_t *json_integer_copy(json_t *integer)
+{
+    return json_integer(json_integer_value(integer));
+}
+
+
+/*** real ***/
+
+json_t *json_real(double value)
+{
+    json_real_t *real = malloc(sizeof(json_real_t));
+    if(!real)
+        return NULL;
+    json_init(&real->json, JSON_REAL);
+
+    real->value = value;
+    return &real->json;
+}
+
+double json_real_value(const json_t *json)
+{
+    if(!json_is_real(json))
+        return 0;
+
+    return json_to_real(json)->value;
+}
+
+int json_real_set(json_t *json, double value)
+{
+    if(!json_is_real(json))
+        return 0;
+
+    json_to_real(json)->value = value;
+
+    return 0;
+}
+
+static void json_delete_real(json_real_t *real)
+{
+    free(real);
+}
+
+static int json_real_equal(json_t *real1, json_t *real2)
+{
+    return json_real_value(real1) == json_real_value(real2);
+}
+
+static json_t *json_real_copy(json_t *real)
+{
+    return json_real(json_real_value(real));
+}
+
+
+/*** number ***/
+
+double json_number_value(const json_t *json)
+{
+    if(json_is_integer(json))
+        return json_integer_value(json);
+    else if(json_is_real(json))
+        return json_real_value(json);
+    else
+        return 0.0;
+}
+
+
+/*** simple values ***/
+
+json_t *json_true(void)
+{
+    static json_t the_true = {JSON_TRUE, (size_t)-1};
+    return &the_true;
+}
+
+
+json_t *json_false(void)
+{
+    static json_t the_false = {JSON_FALSE, (size_t)-1};
+    return &the_false;
+}
+
+
+json_t *json_null(void)
+{
+    static json_t the_null = {JSON_NULL, (size_t)-1};
+    return &the_null;
+}
+
+
+/*** deletion ***/
+
+void json_delete(json_t *json)
+{
+    if(json_is_object(json))
+        json_delete_object(json_to_object(json));
+
+    else if(json_is_array(json))
+        json_delete_array(json_to_array(json));
+
+    else if(json_is_string(json))
+        json_delete_string(json_to_string(json));
+
+    else if(json_is_integer(json))
+        json_delete_integer(json_to_integer(json));
+
+    else if(json_is_real(json))
+        json_delete_real(json_to_real(json));
+
+    /* json_delete is not called for true, false or null */
+}
+
+
+/*** equality ***/
+
+int json_equal(json_t *json1, json_t *json2)
+{
+    if(!json1 || !json2)
+        return 0;
+
+    if(json_typeof(json1) != json_typeof(json2))
+        return 0;
+
+    /* this covers true, false and null as they are singletons */
+    if(json1 == json2)
+        return 1;
+
+    if(json_is_object(json1))
+        return json_object_equal(json1, json2);
+
+    if(json_is_array(json1))
+        return json_array_equal(json1, json2);
+
+    if(json_is_string(json1))
+        return json_string_equal(json1, json2);
+
+    if(json_is_integer(json1))
+        return json_integer_equal(json1, json2);
+
+    if(json_is_real(json1))
+        return json_real_equal(json1, json2);
+
+    return 0;
+}
+
+
+/*** copying ***/
+
+json_t *json_copy(json_t *json)
+{
+    if(!json)
+        return NULL;
+
+    if(json_is_object(json))
+        return json_object_copy(json);
+
+    if(json_is_array(json))
+        return json_array_copy(json);
+
+    if(json_is_string(json))
+        return json_string_copy(json);
+
+    if(json_is_integer(json))
+        return json_integer_copy(json);
+
+    if(json_is_real(json))
+        return json_real_copy(json);
+
+    if(json_is_true(json) || json_is_false(json) || json_is_null(json))
+        return json;
+
+    return NULL;
+}
+
+json_t *json_deep_copy(json_t *json)
+{
+    if(!json)
+        return NULL;
+
+    if(json_is_object(json))
+        return json_object_deep_copy(json);
+
+    if(json_is_array(json))
+        return json_array_deep_copy(json);
+
+    /* for the rest of the types, deep copying doesn't differ from
+       shallow copying */
+
+    if(json_is_string(json))
+        return json_string_copy(json);
+
+    if(json_is_integer(json))
+        return json_integer_copy(json);
+
+    if(json_is_real(json))
+        return json_real_copy(json);
+
+    if(json_is_true(json) || json_is_false(json) || json_is_null(json))
+        return json;
+
+    return NULL;
+}
diff --git a/jansson/src/variadic.c b/jansson/src/variadic.c
new file mode 100644 (file)
index 0000000..89ff1da
--- /dev/null
@@ -0,0 +1,568 @@
+/*
+ * Copyright (c) 2009, 2010 Petri Lehtinen <petri@digip.org>
+ * Copyright (c) 2010 Graeme Smecher <graeme.smecher@mail.mcgill.ca>
+ *
+ * Jansson is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+
+#include <stdarg.h>
+#include <string.h>
+#include <assert.h>
+
+#include <jansson.h>
+#include "jansson_private.h"
+
+json_t *json_pack(json_error_t *error, const char *fmt, ...) {
+    int fmt_length = strlen(fmt);
+    va_list ap;
+
+    /* Keep a stack of containers (lists and objects) */
+    int depth = 0;
+    json_t **stack = NULL;
+
+    /* Keep a list of objects we create in case of error */
+    int free_count = 0;
+    json_t **free_list = NULL;
+
+    json_t *cur = NULL; /* Current container */
+    json_t *root = NULL; /* root object */
+    json_t *obj = NULL;
+
+    char *key = NULL; /* Current key in an object */
+    char *s;
+
+    int line = 1;
+
+    /* Allocation provisioned for worst case */
+    stack = calloc(fmt_length, sizeof(json_t *));
+    free_list = calloc(fmt_length, sizeof(json_t *));
+
+    jsonp_error_init(error, "");
+
+    if(!stack || !free_list)
+        goto out;
+
+    va_start(ap, fmt);
+    while(*fmt) {
+        switch(*fmt) {
+            case '\n':
+                line++;
+                break;
+
+            case ' ': /* Whitespace */
+                break;
+
+            case ',': /* Element spacer */
+                if(!root)
+                {
+                    jsonp_error_set(error, line, -1,
+                              "Unexpected COMMA precedes root element!");
+                    root = NULL;
+                    goto out;
+                }
+
+                if(!cur)
+                {
+                    jsonp_error_set(error, line, -1,
+                              "Unexpected COMMA outside a list or object!");
+                    root = NULL;
+                    goto out;
+                }
+
+                if(key)
+                {
+                    jsonp_error_set(error, line, -1,
+                              "Expected KEY, got COMMA!");
+                    root = NULL;
+                    goto out;
+                }
+                break;
+
+            case ':': /* Key/value separator */
+                if(!key)
+                {
+                    jsonp_error_set(error, line, -1,
+                              "Got key/value separator without "
+                              "a key preceding it!");
+                    root = NULL;
+                    goto out;
+                }
+
+                if(!json_is_object(cur))
+                {
+                    jsonp_error_set(error, line, -1,
+                              "Got a key/value separator "
+                              "(':') outside an object!");
+                    root = NULL;
+                    goto out;
+                }
+
+                break;
+
+            case ']': /* Close array or object */
+            case '}':
+
+                if(key)
+                {
+                    jsonp_error_set(error, line, -1,
+                              "OBJECT or ARRAY ended with an "
+                              "incomplete key/value pair!");
+                    root = NULL;
+                    goto out;
+                }
+
+                if(depth <= 0)
+                {
+                    jsonp_error_set(error, line, -1,
+                              "Too many close-brackets '%c'", *fmt);
+                    root = NULL;
+                    goto out;
+                }
+
+                if(*fmt == ']' && !json_is_array(cur))
+                {
+                    jsonp_error_set(error, line, -1,
+                              "Stray close-array ']' character");
+                    root = NULL;
+                    goto out;
+                }
+
+                if(*fmt == '}' && !json_is_object(cur))
+                {
+                    jsonp_error_set(error, line, -1,
+                              "Stray close-object '}' character");
+                    root = NULL;
+                    goto out;
+                }
+
+                cur = stack[--depth];
+                break;
+
+            case '[':
+                obj = json_array();
+                goto obj_common;
+
+            case '{':
+                obj = json_object();
+                goto obj_common;
+
+            case 's': /* string */
+                s = va_arg(ap, char*);
+
+                if(!s)
+                {
+                    jsonp_error_set(error, line, -1,
+                              "Refusing to handle a NULL string");
+                    root = NULL;
+                    goto out;
+                }
+
+                if(json_is_object(cur) && !key)
+                {
+                    /* It's a key */
+                    key = s;
+                    break;
+                }
+
+                obj = json_string(s);
+                goto obj_common;
+
+            case 'n': /* null */
+                obj = json_null();
+                goto obj_common;
+
+            case 'b': /* boolean */
+                obj = va_arg(ap, int) ?
+                    json_true() : json_false();
+                goto obj_common;
+
+            case 'i': /* integer */
+                obj = json_integer(va_arg(ap, int));
+                goto obj_common;
+
+            case 'f': /* double-precision float */
+                obj = json_real(va_arg(ap, double));
+                goto obj_common;
+
+            case 'O': /* a json_t object; increments refcount */
+                obj = va_arg(ap, json_t *);
+                json_incref(obj);
+                goto obj_common;
+
+            case 'o': /* a json_t object; doesn't increment refcount */
+                obj = va_arg(ap, json_t *);
+                goto obj_common;
+
+obj_common:     free_list[free_count++] = obj;
+
+                /* Root this object to its parent */
+                if(json_is_object(cur)) {
+                    if(!key)
+                    {
+                        jsonp_error_set(error, line, -1,
+                              "Expected key, got identifier '%c'!", *fmt);
+                        root = NULL;
+                        goto out;
+                    }
+
+                    json_object_set_new(cur, key, obj);
+                    key = NULL;
+                }
+                else if(json_is_array(cur))
+                {
+                    json_array_append_new(cur, obj);
+                }
+                else if(!root)
+                {
+                    printf("Rooting\n");
+                    root = obj;
+                }
+                else
+                {
+                    jsonp_error_set(error, line, -1,
+                              "Can't figure out where to attach "
+                              "'%c' object!", *fmt);
+                    root = NULL;
+                    goto out;
+                }
+
+                /* If it was a container ('[' or '{'), descend on the stack */
+                if(json_is_array(obj) || json_is_object(obj))
+                {
+                    stack[depth++] = cur;
+                    cur = obj;
+                }
+
+                break;
+        }
+        fmt++;
+    }
+    va_end(ap);
+
+    if(depth != 0) {
+        jsonp_error_set(error, line, -1,
+                "Missing object or array close-brackets in format string");
+        root = NULL;
+        goto out;
+    }
+
+    /* Success: don't free everything we just built! */
+    free_count = 0;
+
+out:
+    while(free_count)
+        json_decref(free_list[--free_count]);
+
+    if(free_list)
+        free(free_list);
+
+    if(stack)
+        free(stack);
+
+    return(root);
+}
+
+int json_unpack(json_t *root, json_error_t *error, const char *fmt, ...) {
+    va_list ap;
+
+    int rv=0; /* Return value */
+    int line = 1; /* Line number */
+
+    /* Keep a stack of containers (lists and objects) */
+    int depth = 0;
+    json_t **stack;
+
+    int array_index = 0;
+    char *key = NULL; /* Current key in an object */
+
+    json_t *cur = NULL; /* Current container */
+    json_t *obj = NULL;
+
+    int fmt_length = strlen(fmt);
+
+    jsonp_error_init(error, "");
+
+    /* Allocation provisioned for worst case */
+    stack = calloc(fmt_length, sizeof(json_t *));
+    if(!stack)
+    {
+        jsonp_error_set(error, line, -1, "Out of memory!");
+        rv = -1;
+        goto out;
+    }
+
+    /* Even if we're successful, we need to know if the number of
+     * arguments provided matches the number of JSON objects.
+     * We can do this by counting the elements in every array or
+     * object we open up, and decrementing the count as we visit
+     * their children. */
+    int unvisited = 0;
+
+    va_start(ap, fmt);
+    while(*fmt)
+    {
+        switch(*fmt)
+        {
+            case ' ': /* Whitespace */
+                break;
+
+            case '\n': /* Line break */
+                line++;
+                break;
+
+            case ',': /* Element spacer */
+
+                if(!cur)
+                {
+                    jsonp_error_set(error, line, -1,
+                              "Unexpected COMMA outside a list or object!");
+                    rv = -1;
+                    goto out;
+                }
+
+                if(key)
+                {
+                    jsonp_error_set(error, line, -1,
+                              "Expected KEY, got COMMA!");
+                    rv = -1;
+                    goto out;
+                }
+                break;
+
+            case ':': /* Key/value separator */
+                if(!json_is_object(cur) || !key)
+                {
+                    jsonp_error_set(error, line, -1, "Unexpected ':'");
+                    rv = -1;
+                    goto out;
+                }
+                break;
+
+            case '[':
+            case '{':
+                /* Fetch object */
+                if(!cur)
+                {
+                    obj = root;
+                }
+                else if(json_is_object(cur))
+                {
+                    if(!key)
+                    {
+                        jsonp_error_set(error, line, -1,
+                              "Objects can't be keys");
+                        rv = -1;
+                        goto out;
+                    }
+                    obj = json_object_get(cur, key);
+                    unvisited--;
+                    key = NULL;
+                }
+                else if(json_is_array(cur))
+                {
+                    obj = json_array_get(cur, array_index);
+                    unvisited--;
+                    array_index++;
+                }
+                else
+                {
+                    assert(0);
+                }
+
+                /* Make sure we got what we expected */
+                if(*fmt=='{' && !json_is_object(obj))
+                {
+                    rv = -2;
+                    goto out;
+                }
+
+                if(*fmt=='[' && !json_is_array(obj))
+                {
+                    rv = -2;
+                    goto out;
+                }
+
+                unvisited += json_is_object(obj) ?
+                    json_object_size(obj) :
+                    json_array_size(obj);
+
+                /* Descend */
+                stack[depth++] = cur;
+                cur = obj;
+
+                key = NULL;
+
+                break;
+
+
+            case ']':
+            case '}':
+
+                if(json_is_array(cur) && *fmt!=']')
+                {
+                    jsonp_error_set(error, line, -1, "Missing ']'");
+                    rv = -1;
+                    goto out;
+                }
+
+                if(json_is_object(cur) && *fmt!='}')
+                {
+                    jsonp_error_set(error, line, -1, "Missing '}'");
+                    rv = -1;
+                    goto out;
+                }
+
+                if(key)
+                {
+                    jsonp_error_set(error, line, -1, "Unexpected '%c'", *fmt);
+                    rv = -1;
+                    goto out;
+                }
+
+                if(depth <= 0)
+                {
+                    jsonp_error_set(error, line, -1, "Unexpected '%c'", *fmt);
+                    rv = -1;
+                    goto out;
+                }
+
+                cur = stack[--depth];
+
+                break;
+
+            case 's':
+                if(!key && json_is_object(cur))
+                {
+                    /* constant string for key */
+                    key = va_arg(ap, char*);
+                    break;
+                }
+                /* fall through */
+
+            case 'i': /* integer */
+            case 'f': /* double-precision float */
+            case 'O': /* a json_t object; increments refcount */
+            case 'o': /* a json_t object; borrowed reference */
+            case 'b': /* boolean */
+            case 'n': /* null */
+
+                /* Fetch object */
+                if(!cur)
+                {
+                    obj = root;
+                }
+                else if(json_is_object(cur))
+                {
+                    if(!key)
+                    {
+                        jsonp_error_set(error, line, -1,
+                                  "Only strings may be used as keys!");
+                        rv = -1;
+                        goto out;
+                    }
+
+                    obj = json_object_get(cur, key);
+                    unvisited--;
+                    key = NULL;
+                }
+                else if(json_is_array(cur))
+                {
+                    obj = json_array_get(cur, array_index);
+                    unvisited--;
+                    array_index++;
+                }
+                else
+                {
+                    jsonp_error_set(error, line, -1,
+                              "Unsure how to retrieve JSON object '%c'",
+                              *fmt);
+                    rv = -1;
+                    goto out;
+                }
+
+                switch(*fmt)
+                {
+                    case 's':
+                        if(!json_is_string(obj))
+                        {
+                            jsonp_error_set(error, line, -1,
+                                    "Type mismatch! Object wasn't a string.");
+                            rv = -2;
+                            goto out;
+                        }
+                        *va_arg(ap, const char**) = json_string_value(obj);
+                        break;
+
+                    case 'i':
+                        if(!json_is_integer(obj))
+                        {
+                            jsonp_error_set(error, line, -1,
+                                    "Type mismatch! Object wasn't an integer.");
+                            rv = -2;
+                            goto out;
+                        }
+                        *va_arg(ap, int*) = json_integer_value(obj);
+                        break;
+
+                    case 'b':
+                        if(!json_is_boolean(obj))
+                        {
+                            jsonp_error_set(error, line, -1,
+                                    "Type mismatch! Object wasn't a boolean.");
+                            rv = -2;
+                            goto out;
+                        }
+                        *va_arg(ap, int*) = json_is_true(obj);
+                        break;
+
+                    case 'f':
+                        if(!json_is_number(obj))
+                        {
+                            jsonp_error_set(error, line, -1,
+                                    "Type mismatch! Object wasn't a real.");
+                            rv = -2;
+                            goto out;
+                        }
+                        *va_arg(ap, double*) = json_number_value(obj);
+                        break;
+
+                    case 'O':
+                        json_incref(obj);
+                        /* Fall through */
+
+                    case 'o':
+                        *va_arg(ap, json_t**) = obj;
+                        break;
+
+                    case 'n':
+                        /* Don't actually assign anything; we're just happy
+                         * the null turned up as promised in the format
+                         * string. */
+                        break;
+
+                    default:
+                        jsonp_error_set(error, line, -1,
+                                "Unknown format character '%c'", *fmt);
+                        rv = -1;
+                        goto out;
+                }
+        }
+        fmt++;
+    }
+
+    /* Return 0 if everything was matched; otherwise the number of JSON
+     * objects we didn't get to. */
+    rv = unvisited;
+
+out:
+    va_end(ap);
+
+    if(stack)
+        free(stack);
+
+    return(rv);
+}
+
+/* vim: ts=4:expandtab:sw=4
+ */
diff --git a/md5/md5.c b/md5/md5.c
new file mode 100644 (file)
index 0000000..c35d96c
--- /dev/null
+++ b/md5/md5.c
@@ -0,0 +1,381 @@
+/*
+  Copyright (C) 1999, 2000, 2002 Aladdin Enterprises.  All rights reserved.
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  L. Peter Deutsch
+  ghost@aladdin.com
+
+ */
+/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */
+/*
+  Independent implementation of MD5 (RFC 1321).
+
+  This code implements the MD5 Algorithm defined in RFC 1321, whose
+  text is available at
+       http://www.ietf.org/rfc/rfc1321.txt
+  The code is derived from the text of the RFC, including the test suite
+  (section A.5) but excluding the rest of Appendix A.  It does not include
+  any code or documentation that is identified in the RFC as being
+  copyrighted.
+
+  The original and principal author of md5.c is L. Peter Deutsch
+  <ghost@aladdin.com>.  Other authors are noted in the change history
+  that follows (in reverse chronological order):
+
+  2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order
+       either statically or dynamically; added missing #include <string.h>
+       in library.
+  2002-03-11 lpd Corrected argument list for main(), and added int return
+       type, in test program and T value program.
+  2002-02-21 lpd Added missing #include <stdio.h> in test program.
+  2000-07-03 lpd Patched to eliminate warnings about "constant is
+       unsigned in ANSI C, signed in traditional"; made test program
+       self-checking.
+  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
+  1999-05-03 lpd Original version.
+ */
+
+#include "md5.h"
+#include <string.h>
+
+#undef BYTE_ORDER      /* 1 = big-endian, -1 = little-endian, 0 = unknown */
+#ifdef ARCH_IS_BIG_ENDIAN
+#  define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
+#else
+#  define BYTE_ORDER 0
+#endif
+
+#define T_MASK ((md5_word_t)~0)
+#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
+#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
+#define T3    0x242070db
+#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
+#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
+#define T6    0x4787c62a
+#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
+#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
+#define T9    0x698098d8
+#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
+#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
+#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
+#define T13    0x6b901122
+#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
+#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
+#define T16    0x49b40821
+#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
+#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
+#define T19    0x265e5a51
+#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
+#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
+#define T22    0x02441453
+#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
+#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
+#define T25    0x21e1cde6
+#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
+#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
+#define T28    0x455a14ed
+#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
+#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
+#define T31    0x676f02d9
+#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
+#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
+#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
+#define T35    0x6d9d6122
+#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
+#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
+#define T38    0x4bdecfa9
+#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
+#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
+#define T41    0x289b7ec6
+#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
+#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
+#define T44    0x04881d05
+#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
+#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
+#define T47    0x1fa27cf8
+#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
+#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
+#define T50    0x432aff97
+#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
+#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
+#define T53    0x655b59c3
+#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
+#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
+#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
+#define T57    0x6fa87e4f
+#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
+#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
+#define T60    0x4e0811a1
+#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
+#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
+#define T63    0x2ad7d2bb
+#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)
+
+
+static void
+md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
+{
+    md5_word_t
+       a = pms->abcd[0], b = pms->abcd[1],
+       c = pms->abcd[2], d = pms->abcd[3];
+    md5_word_t t;
+#if BYTE_ORDER > 0
+    /* Define storage only for big-endian CPUs. */
+    md5_word_t X[16];
+#else
+    /* Define storage for little-endian or both types of CPUs. */
+    md5_word_t xbuf[16];
+    const md5_word_t *X;
+#endif
+
+    {
+#if BYTE_ORDER == 0
+       /*
+        * Determine dynamically whether this is a big-endian or
+        * little-endian machine, since we can use a more efficient
+        * algorithm on the latter.
+        */
+       static const int w = 1;
+
+       if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
+#endif
+#if BYTE_ORDER <= 0            /* little-endian */
+       {
+           /*
+            * On little-endian machines, we can process properly aligned
+            * data without copying it.
+            */
+           if (!((data - (const md5_byte_t *)0) & 3)) {
+               /* data are properly aligned */
+               X = (const md5_word_t *)data;
+           } else {
+               /* not aligned */
+               memcpy(xbuf, data, 64);
+               X = xbuf;
+           }
+       }
+#endif
+#if BYTE_ORDER == 0
+       else                    /* dynamic big-endian */
+#endif
+#if BYTE_ORDER >= 0            /* big-endian */
+       {
+           /*
+            * On big-endian machines, we must arrange the bytes in the
+            * right order.
+            */
+           const md5_byte_t *xp = data;
+           int i;
+
+#  if BYTE_ORDER == 0
+           X = xbuf;           /* (dynamic only) */
+#  else
+#    define xbuf X             /* (static only) */
+#  endif
+           for (i = 0; i < 16; ++i, xp += 4)
+               xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
+       }
+#endif
+    }
+
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
+
+    /* Round 1. */
+    /* Let [abcd k s i] denote the operation
+       a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
+#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + F(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+    /* Do the following 16 operations. */
+    SET(a, b, c, d,  0,  7,  T1);
+    SET(d, a, b, c,  1, 12,  T2);
+    SET(c, d, a, b,  2, 17,  T3);
+    SET(b, c, d, a,  3, 22,  T4);
+    SET(a, b, c, d,  4,  7,  T5);
+    SET(d, a, b, c,  5, 12,  T6);
+    SET(c, d, a, b,  6, 17,  T7);
+    SET(b, c, d, a,  7, 22,  T8);
+    SET(a, b, c, d,  8,  7,  T9);
+    SET(d, a, b, c,  9, 12, T10);
+    SET(c, d, a, b, 10, 17, T11);
+    SET(b, c, d, a, 11, 22, T12);
+    SET(a, b, c, d, 12,  7, T13);
+    SET(d, a, b, c, 13, 12, T14);
+    SET(c, d, a, b, 14, 17, T15);
+    SET(b, c, d, a, 15, 22, T16);
+#undef SET
+
+     /* Round 2. */
+     /* Let [abcd k s i] denote the operation
+          a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
+#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + G(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  1,  5, T17);
+    SET(d, a, b, c,  6,  9, T18);
+    SET(c, d, a, b, 11, 14, T19);
+    SET(b, c, d, a,  0, 20, T20);
+    SET(a, b, c, d,  5,  5, T21);
+    SET(d, a, b, c, 10,  9, T22);
+    SET(c, d, a, b, 15, 14, T23);
+    SET(b, c, d, a,  4, 20, T24);
+    SET(a, b, c, d,  9,  5, T25);
+    SET(d, a, b, c, 14,  9, T26);
+    SET(c, d, a, b,  3, 14, T27);
+    SET(b, c, d, a,  8, 20, T28);
+    SET(a, b, c, d, 13,  5, T29);
+    SET(d, a, b, c,  2,  9, T30);
+    SET(c, d, a, b,  7, 14, T31);
+    SET(b, c, d, a, 12, 20, T32);
+#undef SET
+
+     /* Round 3. */
+     /* Let [abcd k s t] denote the operation
+          a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + H(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  5,  4, T33);
+    SET(d, a, b, c,  8, 11, T34);
+    SET(c, d, a, b, 11, 16, T35);
+    SET(b, c, d, a, 14, 23, T36);
+    SET(a, b, c, d,  1,  4, T37);
+    SET(d, a, b, c,  4, 11, T38);
+    SET(c, d, a, b,  7, 16, T39);
+    SET(b, c, d, a, 10, 23, T40);
+    SET(a, b, c, d, 13,  4, T41);
+    SET(d, a, b, c,  0, 11, T42);
+    SET(c, d, a, b,  3, 16, T43);
+    SET(b, c, d, a,  6, 23, T44);
+    SET(a, b, c, d,  9,  4, T45);
+    SET(d, a, b, c, 12, 11, T46);
+    SET(c, d, a, b, 15, 16, T47);
+    SET(b, c, d, a,  2, 23, T48);
+#undef SET
+
+     /* Round 4. */
+     /* Let [abcd k s t] denote the operation
+          a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
+#define I(x, y, z) ((y) ^ ((x) | ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + I(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  0,  6, T49);
+    SET(d, a, b, c,  7, 10, T50);
+    SET(c, d, a, b, 14, 15, T51);
+    SET(b, c, d, a,  5, 21, T52);
+    SET(a, b, c, d, 12,  6, T53);
+    SET(d, a, b, c,  3, 10, T54);
+    SET(c, d, a, b, 10, 15, T55);
+    SET(b, c, d, a,  1, 21, T56);
+    SET(a, b, c, d,  8,  6, T57);
+    SET(d, a, b, c, 15, 10, T58);
+    SET(c, d, a, b,  6, 15, T59);
+    SET(b, c, d, a, 13, 21, T60);
+    SET(a, b, c, d,  4,  6, T61);
+    SET(d, a, b, c, 11, 10, T62);
+    SET(c, d, a, b,  2, 15, T63);
+    SET(b, c, d, a,  9, 21, T64);
+#undef SET
+
+     /* Then perform the following additions. (That is increment each
+        of the four registers by the value it had before this block
+        was started.) */
+    pms->abcd[0] += a;
+    pms->abcd[1] += b;
+    pms->abcd[2] += c;
+    pms->abcd[3] += d;
+}
+
+void
+md5_init(md5_state_t *pms)
+{
+    pms->count[0] = pms->count[1] = 0;
+    pms->abcd[0] = 0x67452301;
+    pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
+    pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
+    pms->abcd[3] = 0x10325476;
+}
+
+void
+md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
+{
+    const md5_byte_t *p = data;
+    int left = nbytes;
+    int offset = (pms->count[0] >> 3) & 63;
+    md5_word_t nbits = (md5_word_t)(nbytes << 3);
+
+    if (nbytes <= 0)
+       return;
+
+    /* Update the message length. */
+    pms->count[1] += nbytes >> 29;
+    pms->count[0] += nbits;
+    if (pms->count[0] < nbits)
+       pms->count[1]++;
+
+    /* Process an initial partial block. */
+    if (offset) {
+       int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
+
+       memcpy(pms->buf + offset, p, copy);
+       if (offset + copy < 64)
+           return;
+       p += copy;
+       left -= copy;
+       md5_process(pms, pms->buf);
+    }
+
+    /* Process full blocks. */
+    for (; left >= 64; p += 64, left -= 64)
+       md5_process(pms, p);
+
+    /* Process a final partial block. */
+    if (left)
+       memcpy(pms->buf, p, left);
+}
+
+void
+md5_finish(md5_state_t *pms, md5_byte_t digest[16])
+{
+    static const md5_byte_t pad[64] = {
+       0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+    };
+    md5_byte_t data[8];
+    int i;
+
+    /* Save the length before padding. */
+    for (i = 0; i < 8; ++i)
+       data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
+    /* Pad to 56 bytes mod 64. */
+    md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
+    /* Append the length. */
+    md5_append(pms, data, 8);
+    for (i = 0; i < 16; ++i)
+       digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
+}
diff --git a/md5/md5.h b/md5/md5.h
new file mode 100644 (file)
index 0000000..7143a36
--- /dev/null
+++ b/md5/md5.h
@@ -0,0 +1,84 @@
+/*
+  Copyright (C) 1999, 2002 Aladdin Enterprises.  All rights reserved.
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  L. Peter Deutsch
+  ghost@aladdin.com
+
+ */
+/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */
+/*
+  Independent implementation of MD5 (RFC 1321).
+
+  This code implements the MD5 Algorithm defined in RFC 1321, whose
+  text is available at
+       http://www.ietf.org/rfc/rfc1321.txt
+  The code is derived from the text of the RFC, including the test suite
+  (section A.5) but excluding the rest of Appendix A.  It does not include
+  any code or documentation that is identified in the RFC as being
+  copyrighted.
+
+  The original and principal author of md5.h is L. Peter Deutsch
+  <ghost@aladdin.com>.  Other authors are noted in the change history
+  that follows (in reverse chronological order):
+
+  2002-04-13 lpd Removed support for non-ANSI compilers; removed
+       references to Ghostscript; clarified derivation from RFC 1321;
+       now handles byte order either statically or dynamically.
+  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
+       added conditionalization for C++ compilation from Martin
+       Purschke <purschke@bnl.gov>.
+  1999-05-03 lpd Original version.
+ */
+
+#ifndef md5_INCLUDED
+#define md5_INCLUDED
+
+/*
+ * This package supports both compile-time and run-time determination of CPU
+ * byte order.  If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
+ * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
+ * defined as non-zero, the code will be compiled to run only on big-endian
+ * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
+ * run on either big- or little-endian CPUs, but will run slightly less
+ * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
+ */
+
+typedef unsigned char md5_byte_t; /* 8-bit byte */
+typedef unsigned int md5_word_t; /* 32-bit word */
+
+/* Define the state of the MD5 Algorithm. */
+typedef struct md5_state_s {
+    md5_word_t count[2];       /* message length in bits, lsw first */
+    md5_word_t abcd[4];                /* digest buffer */
+    md5_byte_t buf[64];                /* accumulate block */
+} md5_state_t;
+
+
+/* Initialize the algorithm. */
+void md5_init(md5_state_t *pms);
+
+/* Append a string to the message. */
+void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
+
+/* Finish the message and return the digest. */
+void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
+
+
+#endif /* md5_INCLUDED */
diff --git a/pool.c b/pool.c
new file mode 100644 (file)
index 0000000..65ff84d
--- /dev/null
+++ b/pool.c
@@ -0,0 +1,175 @@
+#include "pool.h"
+#include "worker.h"
+#include "conf.h"
+#include "server.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <event.h>
+#include <hiredis/adapters/libevent.h>
+
+struct pool *
+pool_new(struct worker *w, int count) {
+
+       struct pool *p = calloc(1, sizeof(struct pool));
+
+       p->count = count;
+       p->ac = calloc(count, sizeof(redisAsyncContext*));
+
+       p->w = w;
+       p->cfg = w->s->cfg;
+
+       return p;
+}
+
+void
+pool_free_context(redisAsyncContext *ac) {
+
+       if (ac) {
+               redisAsyncDisconnect(ac);
+               redisAsyncFree(ac);
+       }
+}
+
+static void
+pool_on_connect(const redisAsyncContext *ac, int status) {
+       struct pool *p = ac->data;
+       int i = 0;
+
+       if(!p || status == REDIS_ERR || ac->err) {
+               return;
+       }
+       /* connected to redis! */
+
+       /* add to pool */
+       for(i = 0; i < p->count; ++i) {
+               if(p->ac[i] == NULL) {
+                       p->ac[i] = ac;
+                       return;
+               }
+       }
+}
+
+struct pool_reconnect {
+       struct event ev;
+       struct pool *p;
+
+       struct timeval tv;
+};
+
+static void
+pool_can_connect(int fd, short event, void *ptr) {
+       struct pool_reconnect *pr = ptr;
+       struct pool *p = pr->p;
+
+       (void)fd;
+       (void)event;
+
+       free(pr);
+
+       pool_connect(p, p->cfg->database, 1);
+}
+static void
+pool_schedule_reconnect(struct pool *p) {
+
+       struct pool_reconnect *pr = malloc(sizeof(struct pool_reconnect));
+       pr->p = p;
+
+       pr->tv.tv_sec = 0;
+       pr->tv.tv_usec = 100*1000; /* 0.1 sec*/
+
+       evtimer_set(&pr->ev, pool_can_connect, pr);
+       event_base_set(p->w->base, &pr->ev);
+       evtimer_add(&pr->ev, &pr->tv);
+}
+
+
+
+static void
+pool_on_disconnect(const redisAsyncContext *ac, int status) {
+
+       struct pool *p = ac->data;
+       int i = 0;
+       if (status != REDIS_OK) {
+               /* fprintf(stderr, "Error: %s\n", ac->errstr); */
+       }
+
+       if(p == NULL) { /* no need to clean anything here. */
+               return;
+       }
+
+       /* remove from the pool */
+       for(i = 0; i < p->count; ++i) {
+               if(p->ac[i] == ac) {
+                       p->ac[i] = NULL;
+                       break;
+               }
+       }
+
+       /* schedule reconnect */
+       pool_schedule_reconnect(p);
+}
+
+/**
+ * Create new connection.
+ */
+redisAsyncContext *
+pool_connect(struct pool *p, int db_num, int attach) {
+
+       struct redisAsyncContext *ac;
+       if(p->cfg->redis_host[0] == '/') { /* unix socket */
+               ac = redisAsyncConnectUnix(p->cfg->redis_host);
+       } else {
+               ac = redisAsyncConnect(p->cfg->redis_host, p->cfg->redis_port);
+       }
+
+       if(attach) {
+               ac->data = p;
+       } else {
+               ac->data = NULL;
+       }
+
+       if(ac->err) {
+               char msg[] = "Connection failed: %s";
+               size_t errlen = strlen(ac->errstr);
+               char *err = malloc(sizeof(msg) + errlen);
+               if (err) {
+                       size_t sz = sprintf(err, msg, ac->errstr);
+                       slog(p->w->s, WEBDIS_ERROR, err, sz);
+                       free(err);
+               }
+               redisAsyncFree(ac);
+               pool_schedule_reconnect(p);
+               return NULL;
+       }
+
+       redisLibeventAttach(ac, p->w->base);
+       redisAsyncSetConnectCallback(ac, pool_on_connect);
+       redisAsyncSetDisconnectCallback(ac, pool_on_disconnect);
+
+       if(p->cfg->redis_auth) { /* authenticate. */
+               redisAsyncCommand(ac, NULL, NULL, "AUTH %s", p->cfg->redis_auth);
+       }
+       if(db_num) { /* change database. */
+               redisAsyncCommand(ac, NULL, NULL, "SELECT %d", db_num);
+       }
+       return ac;
+}
+
+const redisAsyncContext *
+pool_get_context(struct pool *p) {
+
+       int orig = p->cur++;
+
+       do {
+               p->cur++;
+               p->cur %= p->count;
+               if(p->ac[p->cur] != NULL) {
+                       return p->ac[p->cur];
+               }
+       } while(p->cur != orig);
+
+       return NULL;
+
+}
+
diff --git a/pool.h b/pool.h
new file mode 100644 (file)
index 0000000..bd24ee9
--- /dev/null
+++ b/pool.h
@@ -0,0 +1,33 @@
+#ifndef POOL_H
+#define POOL_H
+
+#include <hiredis/async.h>
+
+struct conf;
+struct worker;
+
+struct pool {
+
+       struct worker *w;
+       struct conf *cfg;
+
+       const redisAsyncContext **ac;
+       int count;
+       int cur;
+
+};
+
+
+struct pool *
+pool_new(struct worker *w, int count);
+
+void
+pool_free_context(redisAsyncContext *ac);
+
+redisAsyncContext *
+pool_connect(struct pool *p, int db_num, int attach);
+
+const redisAsyncContext *
+pool_get_context(struct pool *p);
+
+#endif
diff --git a/server.c b/server.c
new file mode 100644 (file)
index 0000000..8cc667c
--- /dev/null
+++ b/server.c
@@ -0,0 +1,241 @@
+#include "server.h"
+#include "worker.h"
+#include "client.h"
+#include "conf.h"
+#include "version.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+
+/**
+ * Sets up a non-blocking socket
+ */
+static int
+socket_setup(struct server *s, const char *ip, short port) {
+
+       int reuse = 1;
+       struct sockaddr_in addr;
+       int fd, ret;
+
+       memset(&addr, 0, sizeof(addr));
+#if defined __BSD__
+       addr.sin_len = sizeof(struct sockaddr_in);
+#endif
+       addr.sin_family = AF_INET;
+       addr.sin_port = htons(port);
+
+       addr.sin_addr.s_addr = inet_addr(ip);
+
+       /* this sad list of tests could use a Maybe monad... */
+
+       /* create socket */
+       fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+       if (-1 == fd) {
+               slog(s, WEBDIS_ERROR, strerror(errno), 0);
+               return -1;
+       }
+
+       /* reuse address if we've bound to it before. */
+       if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse,
+                               sizeof(reuse)) < 0) {
+               slog(s, WEBDIS_ERROR, strerror(errno), 0);
+               return -1;
+       }
+
+       /* set socket as non-blocking. */
+       ret = fcntl(fd, F_SETFD, O_NONBLOCK);
+       if (0 != ret) {
+               slog(s, WEBDIS_ERROR, strerror(errno), 0);
+               return -1;
+       }
+
+       /* bind */
+       ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
+       if (0 != ret) {
+               slog(s, WEBDIS_ERROR, strerror(errno), 0);
+               return -1;
+       }
+
+       /* listen */
+       ret = listen(fd, SOMAXCONN);
+       if (0 != ret) {
+               slog(s, WEBDIS_ERROR, strerror(errno), 0);
+               return -1;
+       }
+
+       /* there you go, ready to accept! */
+       return fd;
+}
+
+struct server *
+server_new(const char *cfg_file) {
+       
+       int i;
+       struct server *s = calloc(1, sizeof(struct server));
+
+       s->log.fd = -1;
+       s->cfg = conf_read(cfg_file);
+
+       /* workers */
+       s->w = calloc(s->cfg->http_threads, sizeof(struct worker*));
+       for(i = 0; i < s->cfg->http_threads; ++i) {
+               s->w[i] = worker_new(s);
+       }
+       return s;
+}
+
+static void
+server_can_accept(int fd, short event, void *ptr) {
+
+       struct server *s = ptr;
+       struct worker *w;
+       struct http_client *c;
+       int client_fd;
+       struct sockaddr_in addr;
+       socklen_t addr_sz = sizeof(addr);
+       char on = 1;
+       (void)event;
+
+       /* select worker to send the client to */
+       w = s->w[s->next_worker];
+
+       /* accept client */
+       client_fd = accept(fd, (struct sockaddr*)&addr, &addr_sz);
+
+       /* make non-blocking */
+       ioctl(client_fd, (int)FIONBIO, (char *)&on);
+
+       /* create client and send to worker. */
+       if(client_fd > 0) {
+               c = http_client_new(w, client_fd, addr.sin_addr.s_addr);
+               worker_add_client(w, c);
+
+               /* loop over ring of workers */
+               s->next_worker = (s->next_worker + 1) % s->cfg->http_threads;
+       } else { /* too many connections */
+               slog(s, WEBDIS_NOTICE, "Too many connections", 0);
+       }
+}
+
+/**
+ * Daemonize server.
+ * (taken from Redis)
+ */
+static void
+server_daemonize(const char *pidfile) {
+       int fd;
+
+       if (fork() != 0) exit(0); /* parent exits */
+       setsid(); /* create a new session */
+
+       /* Every output goes to /dev/null. */
+       if ((fd = open("/dev/null", O_RDWR, 0)) != -1) {
+               dup2(fd, STDIN_FILENO);
+               dup2(fd, STDOUT_FILENO);
+               dup2(fd, STDERR_FILENO);
+               if (fd > STDERR_FILENO) close(fd);
+       }
+
+       /* write pidfile */
+       if (pidfile) {
+               FILE *f = fopen(pidfile, "w");
+               if (f) {
+                       fprintf(f, "%d\n", (int)getpid());
+                       fclose(f);
+               }
+       }
+}
+
+/* global pointer to the server object, used in signal handlers */
+static struct server *__server;
+
+static void
+server_handle_signal(int id) {
+
+       switch(id) {
+               case SIGHUP:
+                       slog_init(__server);
+                       break;
+               case SIGTERM:
+               case SIGINT:
+                       slog(__server, WEBDIS_INFO, "Webdis terminating", 0);
+                       exit(0);
+                       break;
+               default:
+                       break;
+       }
+}
+
+static void
+server_install_signal_handlers(struct server *s) {
+       __server = s;
+
+       signal(SIGHUP,  server_handle_signal);
+       signal(SIGTERM, server_handle_signal);
+       signal(SIGINT,  server_handle_signal);
+}
+
+int
+server_start(struct server *s) {
+
+       int i, ret;
+
+       /* initialize libevent */
+       s->base = event_base_new();
+
+       if(s->cfg->daemonize) {
+               server_daemonize(s->cfg->pidfile);
+
+               /* sometimes event mech gets lost on fork */
+               if(event_reinit(s->base) != 0) {
+                       fprintf(stderr, "Error: event_reinit failed after fork");
+               }
+       }
+
+       /* ignore sigpipe */
+#ifdef SIGPIPE
+       signal(SIGPIPE, SIG_IGN);
+#endif
+
+       slog_init(s);
+
+       /* install signal handlers */
+       server_install_signal_handlers(s);
+
+       /* start worker threads */
+       for(i = 0; i < s->cfg->http_threads; ++i) {
+               worker_start(s->w[i]);
+       }
+
+       /* create socket */
+       s->fd = socket_setup(s, s->cfg->http_host, s->cfg->http_port);
+       if(s->fd < 0) {
+               return -1;
+       }
+
+       /* start http server */
+       event_set(&s->ev, s->fd, EV_READ | EV_PERSIST, server_can_accept, s);
+       event_base_set(s->base, &s->ev);
+       ret = event_add(&s->ev, NULL);
+
+       if(ret < 0) {
+               slog(s, WEBDIS_ERROR, "Error calling event_add on socket", 0);
+               return -1;
+       }
+
+       slog(s, WEBDIS_INFO, "Webdis " WEBDIS_VERSION " up and running", 0);
+       event_base_dispatch(s->base);
+
+       return 0;
+}
+
diff --git a/server.h b/server.h
new file mode 100644 (file)
index 0000000..42438b3
--- /dev/null
+++ b/server.h
@@ -0,0 +1,37 @@
+#ifndef SERVER_H
+#define SERVER_H
+
+#include <event.h>
+#include <hiredis/async.h>
+#include <pthread.h>
+
+struct worker;
+struct conf;
+
+struct server {
+
+       int fd;
+       struct event ev;
+       struct event_base *base;
+
+       struct conf *cfg;
+
+       /* worker threads */
+       struct worker **w;
+       int next_worker;
+
+       /* log lock */
+       struct {
+               pid_t self;
+               int fd;
+       } log;
+};
+
+struct server *
+server_new(const char *cfg_file);
+
+int
+server_start(struct server *s);
+
+#endif
+
diff --git a/sha1/sha1.c b/sha1/sha1.c
new file mode 100644 (file)
index 0000000..d87c7f4
--- /dev/null
@@ -0,0 +1,371 @@
+/*
+ *  sha1.c
+ *
+ *  Copyright (C) 1998, 2009
+ *  Paul E. Jones <paulej@packetizer.com>
+ *  All Rights Reserved
+ *
+ *****************************************************************************
+ *  $Id: sha1.c 12 2009-06-22 19:34:25Z paulej $
+ *****************************************************************************
+ *
+ *  Description:
+ *      This file implements the Secure Hashing Standard as defined
+ *      in FIPS PUB 180-1 published April 17, 1995.
+ *
+ *      The Secure Hashing Standard, which uses the Secure Hashing
+ *      Algorithm (SHA), produces a 160-bit message digest for a
+ *      given data stream.  In theory, it is highly improbable that
+ *      two messages will produce the same message digest.  Therefore,
+ *      this algorithm can serve as a means of providing a "fingerprint"
+ *      for a message.
+ *
+ *  Portability Issues:
+ *      SHA-1 is defined in terms of 32-bit "words".  This code was
+ *      written with the expectation that the processor has at least
+ *      a 32-bit machine word size.  If the machine word size is larger,
+ *      the code should still function properly.  One caveat to that
+ *      is that the input functions taking characters and character
+ *      arrays assume that only 8 bits of information are stored in each
+ *      character.
+ *
+ *  Caveats:
+ *      SHA-1 is designed to work with messages less than 2^64 bits
+ *      long. Although SHA-1 allows a message digest to be generated for
+ *      messages of any number of bits less than 2^64, this
+ *      implementation only works with messages with a length that is a
+ *      multiple of the size of an 8-bit character.
+ *
+ */
+
+#include "sha1.h"
+
+/*
+ *  Define the circular shift macro
+ */
+#define SHA1CircularShift(bits,word) \
+                ((((word) << (bits)) & 0xFFFFFFFF) | \
+                ((word) >> (32-(bits))))
+
+/* Function prototypes */
+void SHA1ProcessMessageBlock(SHA1Context *);
+void SHA1PadMessage(SHA1Context *);
+
+/*  
+ *  SHA1Reset
+ *
+ *  Description:
+ *      This function will initialize the SHA1Context in preparation
+ *      for computing a new message digest.
+ *
+ *  Parameters:
+ *      context: [in/out]
+ *          The context to reset.
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+void SHA1Reset(SHA1Context *context)
+{
+    context->Length_Low             = 0;
+    context->Length_High            = 0;
+    context->Message_Block_Index    = 0;
+
+    context->Message_Digest[0]      = 0x67452301;
+    context->Message_Digest[1]      = 0xEFCDAB89;
+    context->Message_Digest[2]      = 0x98BADCFE;
+    context->Message_Digest[3]      = 0x10325476;
+    context->Message_Digest[4]      = 0xC3D2E1F0;
+
+    context->Computed   = 0;
+    context->Corrupted  = 0;
+}
+
+/*  
+ *  SHA1Result
+ *
+ *  Description:
+ *      This function will return the 160-bit message digest into the
+ *      Message_Digest array within the SHA1Context provided
+ *
+ *  Parameters:
+ *      context: [in/out]
+ *          The context to use to calculate the SHA-1 hash.
+ *
+ *  Returns:
+ *      1 if successful, 0 if it failed.
+ *
+ *  Comments:
+ *
+ */
+int SHA1Result(SHA1Context *context)
+{
+
+    if (context->Corrupted)
+    {
+        return 0;
+    }
+
+    if (!context->Computed)
+    {
+        SHA1PadMessage(context);
+        context->Computed = 1;
+    }
+
+    return 1;
+}
+
+/*  
+ *  SHA1Input
+ *
+ *  Description:
+ *      This function accepts an array of octets as the next portion of
+ *      the message.
+ *
+ *  Parameters:
+ *      context: [in/out]
+ *          The SHA-1 context to update
+ *      message_array: [in]
+ *          An array of characters representing the next portion of the
+ *          message.
+ *      length: [in]
+ *          The length of the message in message_array
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+void SHA1Input(     SHA1Context         *context,
+                    const unsigned char *message_array,
+                    unsigned            length)
+{
+    if (!length)
+    {
+        return;
+    }
+
+    if (context->Computed || context->Corrupted)
+    {
+        context->Corrupted = 1;
+        return;
+    }
+
+    while(length-- && !context->Corrupted)
+    {
+        context->Message_Block[context->Message_Block_Index++] =
+                                                (*message_array & 0xFF);
+
+        context->Length_Low += 8;
+        /* Force it to 32 bits */
+        context->Length_Low &= 0xFFFFFFFF;
+        if (context->Length_Low == 0)
+        {
+            context->Length_High++;
+            /* Force it to 32 bits */
+            context->Length_High &= 0xFFFFFFFF;
+            if (context->Length_High == 0)
+            {
+                /* Message is too long */
+                context->Corrupted = 1;
+            }
+        }
+
+        if (context->Message_Block_Index == 64)
+        {
+            SHA1ProcessMessageBlock(context);
+        }
+
+        message_array++;
+    }
+}
+
+/*  
+ *  SHA1ProcessMessageBlock
+ *
+ *  Description:
+ *      This function will process the next 512 bits of the message
+ *      stored in the Message_Block array.
+ *
+ *  Parameters:
+ *      None.
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *      Many of the variable names in the SHAContext, especially the
+ *      single character names, were used because those were the names
+ *      used in the publication.
+ *         
+ *
+ */
+void SHA1ProcessMessageBlock(SHA1Context *context)
+{
+    const unsigned K[] =            /* Constants defined in SHA-1   */      
+    {
+        0x5A827999,
+        0x6ED9EBA1,
+        0x8F1BBCDC,
+        0xCA62C1D6
+    };
+    int         t;                  /* Loop counter                 */
+    unsigned    temp;               /* Temporary word value         */
+    unsigned    W[80];              /* Word sequence                */
+    unsigned    A, B, C, D, E;      /* Word buffers                 */
+
+    /*
+     *  Initialize the first 16 words in the array W
+     */
+    for(t = 0; t < 16; t++)
+    {
+        W[t] = ((unsigned) context->Message_Block[t * 4]) << 24;
+        W[t] |= ((unsigned) context->Message_Block[t * 4 + 1]) << 16;
+        W[t] |= ((unsigned) context->Message_Block[t * 4 + 2]) << 8;
+        W[t] |= ((unsigned) context->Message_Block[t * 4 + 3]);
+    }
+
+    for(t = 16; t < 80; t++)
+    {
+       W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
+    }
+
+    A = context->Message_Digest[0];
+    B = context->Message_Digest[1];
+    C = context->Message_Digest[2];
+    D = context->Message_Digest[3];
+    E = context->Message_Digest[4];
+
+    for(t = 0; t < 20; t++)
+    {
+        temp =  SHA1CircularShift(5,A) +
+                ((B & C) | ((~B) & D)) + E + W[t] + K[0];
+        temp &= 0xFFFFFFFF;
+        E = D;
+        D = C;
+        C = SHA1CircularShift(30,B);
+        B = A;
+        A = temp;
+    }
+
+    for(t = 20; t < 40; t++)
+    {
+        temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1];
+        temp &= 0xFFFFFFFF;
+        E = D;
+        D = C;
+        C = SHA1CircularShift(30,B);
+        B = A;
+        A = temp;
+    }
+
+    for(t = 40; t < 60; t++)
+    {
+        temp = SHA1CircularShift(5,A) +
+               ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2];
+        temp &= 0xFFFFFFFF;
+        E = D;
+        D = C;
+        C = SHA1CircularShift(30,B);
+        B = A;
+        A = temp;
+    }
+
+    for(t = 60; t < 80; t++)
+    {
+        temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3];
+        temp &= 0xFFFFFFFF;
+        E = D;
+        D = C;
+        C = SHA1CircularShift(30,B);
+        B = A;
+        A = temp;
+    }
+
+    context->Message_Digest[0] =
+                        (context->Message_Digest[0] + A) & 0xFFFFFFFF;
+    context->Message_Digest[1] =
+                        (context->Message_Digest[1] + B) & 0xFFFFFFFF;
+    context->Message_Digest[2] =
+                        (context->Message_Digest[2] + C) & 0xFFFFFFFF;
+    context->Message_Digest[3] =
+                        (context->Message_Digest[3] + D) & 0xFFFFFFFF;
+    context->Message_Digest[4] =
+                        (context->Message_Digest[4] + E) & 0xFFFFFFFF;
+
+    context->Message_Block_Index = 0;
+}
+
+/*  
+ *  SHA1PadMessage
+ *
+ *  Description:
+ *      According to the standard, the message must be padded to an even
+ *      512 bits.  The first padding bit must be a '1'.  The last 64
+ *      bits represent the length of the original message.  All bits in
+ *      between should be 0.  This function will pad the message
+ *      according to those rules by filling the Message_Block array
+ *      accordingly.  It will also call SHA1ProcessMessageBlock()
+ *      appropriately.  When it returns, it can be assumed that the
+ *      message digest has been computed.
+ *
+ *  Parameters:
+ *      context: [in/out]
+ *          The context to pad
+ *
+ *  Returns:
+ *      Nothing.
+ *
+ *  Comments:
+ *
+ */
+void SHA1PadMessage(SHA1Context *context)
+{
+    /*
+     *  Check to see if the current message block is too small to hold
+     *  the initial padding bits and length.  If so, we will pad the
+     *  block, process it, and then continue padding into a second
+     *  block.
+     */
+    if (context->Message_Block_Index > 55)
+    {
+        context->Message_Block[context->Message_Block_Index++] = 0x80;
+        while(context->Message_Block_Index < 64)
+        {
+            context->Message_Block[context->Message_Block_Index++] = 0;
+        }
+
+        SHA1ProcessMessageBlock(context);
+
+        while(context->Message_Block_Index < 56)
+        {
+            context->Message_Block[context->Message_Block_Index++] = 0;
+        }
+    }
+    else
+    {
+        context->Message_Block[context->Message_Block_Index++] = 0x80;
+        while(context->Message_Block_Index < 56)
+        {
+            context->Message_Block[context->Message_Block_Index++] = 0;
+        }
+    }
+
+    /*
+     *  Store the message length as the last 8 octets
+     */
+    context->Message_Block[56] = (context->Length_High >> 24) & 0xFF;
+    context->Message_Block[57] = (context->Length_High >> 16) & 0xFF;
+    context->Message_Block[58] = (context->Length_High >> 8) & 0xFF;
+    context->Message_Block[59] = (context->Length_High) & 0xFF;
+    context->Message_Block[60] = (context->Length_Low >> 24) & 0xFF;
+    context->Message_Block[61] = (context->Length_Low >> 16) & 0xFF;
+    context->Message_Block[62] = (context->Length_Low >> 8) & 0xFF;
+    context->Message_Block[63] = (context->Length_Low) & 0xFF;
+
+    SHA1ProcessMessageBlock(context);
+}
diff --git a/sha1/sha1.h b/sha1/sha1.h
new file mode 100644 (file)
index 0000000..1ca4b10
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ *  sha1.h
+ *
+ *  Copyright (C) 1998, 2009
+ *  Paul E. Jones <paulej@packetizer.com>
+ *  All Rights Reserved
+ *
+ *****************************************************************************
+ *  $Id: sha1.h 12 2009-06-22 19:34:25Z paulej $
+ *****************************************************************************
+ *
+ *  Description:
+ *      This class implements the Secure Hashing Standard as defined
+ *      in FIPS PUB 180-1 published April 17, 1995.
+ *
+ *      Many of the variable names in the SHA1Context, especially the
+ *      single character names, were used because those were the names
+ *      used in the publication.
+ *
+ *      Please read the file sha1.c for more information.
+ *
+ */
+
+#ifndef _SHA1_H_
+#define _SHA1_H_
+
+/* 
+ *  This structure will hold context information for the hashing
+ *  operation
+ */
+typedef struct SHA1Context
+{
+    unsigned Message_Digest[5]; /* Message Digest (output)          */
+
+    unsigned Length_Low;        /* Message length in bits           */
+    unsigned Length_High;       /* Message length in bits           */
+
+    unsigned char Message_Block[64]; /* 512-bit message blocks      */
+    int Message_Block_Index;    /* Index into message block array   */
+
+    int Computed;               /* Is the digest computed?          */
+    int Corrupted;              /* Is the message digest corruped?  */
+} SHA1Context;
+
+/*
+ *  Function Prototypes
+ */
+void SHA1Reset(SHA1Context *);
+int SHA1Result(SHA1Context *);
+void SHA1Input( SHA1Context *,
+                const unsigned char *,
+                unsigned);
+
+#endif
diff --git a/slog.c b/slog.c
new file mode 100644 (file)
index 0000000..0d7fd21
--- /dev/null
+++ b/slog.c
@@ -0,0 +1,80 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "slog.h"
+#include "server.h"
+#include "conf.h"
+
+/**
+ * Initialize log writer.
+ */
+void
+slog_init(struct server *s) {
+
+       s->log.self = getpid();
+
+       if(s->cfg->logfile) {
+
+               int old_fd = s->log.fd;
+
+               s->log.fd = open(s->cfg->logfile,
+                       O_WRONLY | O_APPEND | O_CREAT, S_IRUSR|S_IWUSR);
+
+               /* close old log */
+               if (old_fd != -1) {
+                       close(old_fd);
+               }
+
+               if (s->log.fd != -1)
+                       return;
+
+               fprintf(stderr, "Could not open %s: %s\n", s->cfg->logfile,
+                               strerror(errno));
+       }
+       s->log.fd = 2; /* stderr */
+}
+
+/**
+ * Write log message to disk, or stderr.
+ */
+void
+slog(struct server *s, log_level level,
+               const char *body, size_t sz) {
+
+       const char *c = ".-*#";
+       time_t now;
+       char time_buf[64];
+       char msg[124];
+       char line[256]; /* bounds are checked. */
+       int line_sz, ret;
+
+       if(level > s->cfg->verbosity) return; /* too verbose */
+
+       if(!s->log.fd) return;
+
+       /* limit message size */
+       sz = sz ? sz:strlen(body);
+       snprintf(msg, sz + 1 > sizeof(msg) ? sizeof(msg) : sz + 1, "%s", body);
+
+       /* get current time */
+       now = time(NULL);
+       strftime(time_buf, sizeof(time_buf), "%d %b %H:%M:%S", localtime(&now));
+
+       /* generate output line. */
+       line_sz = snprintf(line, sizeof(line),
+               "[%d] %s %d %s\n", (int)s->log.self, time_buf, c[level], msg);
+
+       /* write to log and flush to disk. */
+       ret = write(s->log.fd, line, line_sz);
+       ret = fsync(s->log.fd);
+
+       (void)ret;
+}
diff --git a/slog.h b/slog.h
new file mode 100644 (file)
index 0000000..25d0d89
--- /dev/null
+++ b/slog.h
@@ -0,0 +1,23 @@
+#ifndef SLOG_H
+#define SLOG_H
+
+typedef enum {
+       WEBDIS_ERROR = 0,
+       WEBDIS_WARNING,
+       WEBDIS_NOTICE,
+       WEBDIS_INFO,
+       WEBDIS_DEBUG
+} log_level;
+
+struct server;
+
+void
+slog_reload();
+
+void
+slog_init(struct server *s);
+
+void slog(struct server *s, log_level level,
+               const char *body, size_t sz);
+
+#endif
diff --git a/tests/Makefile b/tests/Makefile
new file mode 100644 (file)
index 0000000..a0b8a11
--- /dev/null
@@ -0,0 +1,18 @@
+OUT=websocket pubsub
+CFLAGS=-O3 -Wall -Wextra
+LDFLAGS=-levent -lpthread -lrt
+
+all: $(OUT) Makefile
+
+websocket: websocket.o
+       $(CC) -o $@ $< $(LDFLAGS)
+
+pubsub: pubsub.o
+       $(CC) -o $@ $< $(LDFLAGS)
+
+%.o: %.c Makefile
+       $(CC) -c $(CFLAGS) -o $@ $<
+
+clean:
+       rm -f *.o $(OUT)
+
diff --git a/tests/README.tests b/tests/README.tests
new file mode 100644 (file)
index 0000000..b47cd80
--- /dev/null
@@ -0,0 +1,6 @@
+This directory contains a few test programs for Webdis:
+
+* basic.py:    Unit tests.
+* bench.sh:    Benchmark of several functions.
+* pubsub (run `make' to compile): Tests pub/sub channels; run `./pubsub -h` for options.
+* websocket (run `make' to compile): Tests HTML5 WebSockets; run `./websocket -h` for options.
diff --git a/tests/basic.py b/tests/basic.py
new file mode 100755 (executable)
index 0000000..89d664e
--- /dev/null
@@ -0,0 +1,227 @@
+#!/usr/bin/python
+import urllib2, unittest, json, hashlib
+from functools import wraps
+try:
+       import msgpack
+except:
+       msgpack = None
+
+import os
+host = os.getenv('WEBDIS_HOST', '127.0.0.1')
+port = int(os.getenv('WEBDIS_PORT', 7379))
+
+class TestWebdis(unittest.TestCase):
+
+       def wrap(self,url):
+               return 'http://%s:%d/%s' % (host, port, url)
+
+       def query(self, url, data = None, headers={}):
+               r = urllib2.Request(self.wrap(url), data, headers)
+               return urllib2.urlopen(r)
+
+class TestBasics(TestWebdis):
+
+       def test_crossdomain(self):
+               f = self.query('crossdomain.xml')
+               self.assertTrue(f.headers.getheader('Content-Type') == 'application/xml')
+               self.assertTrue("allow-access-from domain" in f.read())
+
+       def test_options(self):
+               pass
+               # not sure if OPTIONS is supported by urllib2...
+               #       f = self.query('')      # TODO: call with OPTIONS.
+               #       self.assertTrue(f.headers.getheader('Content-Type') == 'text/html')
+               #       self.assertTrue(f.headers.getheader('Allow') == 'GET,POST,PUT,OPTIONS')
+               #       self.assertTrue(f.headers.getheader('Content-Length') == '0')
+               #       self.assertTrue(f.headers.getheader('Access-Control-Allow-Origin') == '*')
+
+
+class TestJSON(TestWebdis):
+
+       def test_set(self):
+               "success type (+OK)"
+               self.query('DEL/hello')
+               f = self.query('SET/hello/world')
+               self.assertTrue(f.headers.getheader('Content-Type') == 'application/json')
+               self.assertTrue(f.headers.getheader('ETag') == '"0db1124cf79ffeb80aff6d199d5822f8"')
+               self.assertTrue(f.read() == '{"SET":[true,"OK"]}')
+
+       def test_get(self):
+               "string type"
+               self.query('SET/hello/world')
+               f = self.query('GET/hello')
+               self.assertTrue(f.headers.getheader('Content-Type') == 'application/json')
+               self.assertTrue(f.headers.getheader('ETag') == '"8cf38afc245b7a6a88696566483d1390"')
+               self.assertTrue(f.read() == '{"GET":"world"}')
+
+       def test_incr(self):
+               "integer type"
+               self.query('DEL/hello')
+               f = self.query('INCR/hello')
+               self.assertTrue(f.headers.getheader('Content-Type') == 'application/json')
+               self.assertTrue(f.headers.getheader('ETag') == '"500e9bcdcbb1e98f25c1fbb880a96c99"')
+               self.assertTrue(f.read() == '{"INCR":1}')
+
+       def test_list(self):
+               "list type"
+               self.query('DEL/hello')
+               self.query('RPUSH/hello/abc')
+               self.query('RPUSH/hello/def')
+               f = self.query('LRANGE/hello/0/-1')
+               self.assertTrue(f.headers.getheader('Content-Type') == 'application/json')
+               self.assertTrue(f.headers.getheader('ETag') == '"622e51f547a480bef7cf5452fb7782db"')
+               self.assertTrue(f.read() == '{"LRANGE":["abc","def"]}')
+
+       def test_error(self):
+               "error return type"
+               f = self.query('UNKNOWN/COMMAND')
+               self.assertTrue(f.headers.getheader('Content-Type') == 'application/json')
+               try:
+                       obj = json.loads(f.read())
+               except:
+                       self.assertTrue(False)
+                       return
+
+               self.assertTrue(len(obj) == 1)
+               self.assertTrue('UNKNOWN' in obj)
+               self.assertTrue(isinstance(obj['UNKNOWN'], list))
+               self.assertTrue(obj['UNKNOWN'][0] == False)
+               self.assertTrue(isinstance(obj['UNKNOWN'][1], unicode))
+
+class TestCustom(TestWebdis):
+       def test_list(self):
+               "List responses with custom format"
+               self.query('DEL/hello')
+               self.query('RPUSH/hello/a/b/c')
+               f = self.query('LRANGE/hello/0/-1.txt')
+               self.assertTrue(f.headers.getheader('Content-Type') == 'text/plain')
+               self.assertTrue(f.read() == "abc")
+
+       def test_separator(self):
+               "Separator in list responses with custom format"
+               self.query('DEL/hello')
+               self.query('RPUSH/hello/a/b/c')
+               f = self.query('LRANGE/hello/0/-1.txt?sep=--')
+               self.assertTrue(f.headers.getheader('Content-Type') == 'text/plain')
+               self.assertTrue(f.read() == "a--b--c")
+
+class TestRaw(TestWebdis):
+
+       def test_set(self):
+               "success type (+OK)"
+               self.query('DEL/hello')
+               f = self.query('SET/hello/world.raw')
+               self.assertTrue(f.headers.getheader('Content-Type') == 'binary/octet-stream')
+               self.assertTrue(f.read() == "+OK\r\n")
+
+       def test_get(self):
+               "string type"
+               self.query('SET/hello/world')
+               f = self.query('GET/hello.raw')
+               self.assertTrue(f.read() == '$5\r\nworld\r\n')
+
+       def test_incr(self):
+               "integer type"
+               self.query('DEL/hello')
+               f = self.query('INCR/hello.raw')
+               self.assertTrue(f.read() == ':1\r\n')
+
+       def test_list(self):
+               "list type"
+               self.query('DEL/hello')
+               self.query('RPUSH/hello/abc')
+               self.query('RPUSH/hello/def')
+               f = self.query('LRANGE/hello/0/-1.raw')
+               self.assertTrue(f.read() == "*2\r\n$3\r\nabc\r\n$3\r\ndef\r\n")
+
+       def test_error(self):
+               "error return type"
+               f = self.query('UNKNOWN/COMMAND.raw')
+               self.assertTrue(f.read().startswith("-ERR "))
+
+def need_msgpack(fn):
+       def wrapper(self):
+               if msgpack:
+                       fn(self)
+       return wrapper
+
+class TestMsgPack(TestWebdis):
+
+       @need_msgpack
+       def test_set(self):
+               "success type (+OK)"
+               self.query('DEL/hello')
+               f = self.query('SET/hello/world.msg')
+               self.assertTrue(f.headers.getheader('Content-Type') == 'application/x-msgpack')
+               obj = msgpack.loads(f.read())
+               self.assertTrue(obj == {'SET': (True, 'OK')})
+
+       @need_msgpack
+       def test_get(self):
+               "string type"
+               self.query('SET/hello/world')
+               f = self.query('GET/hello.msg')
+               obj = msgpack.loads(f.read())
+               self.assertTrue(obj == {'GET': 'world'})
+
+       @need_msgpack
+       def test_incr(self):
+               "integer type"
+               self.query('DEL/hello')
+               f = self.query('INCR/hello.msg')
+               obj = msgpack.loads(f.read())
+               self.assertTrue(obj == {'INCR': 1})
+
+       @need_msgpack
+       def test_list(self):
+               "list type"
+               self.query('DEL/hello')
+               self.query('RPUSH/hello/abc')
+               self.query('RPUSH/hello/def')
+               f = self.query('LRANGE/hello/0/-1.msg')
+               obj = msgpack.loads(f.read())
+               self.assertTrue(obj == {'LRANGE': ('abc', 'def')})
+
+       @need_msgpack
+       def test_error(self):
+               "error return type"
+               f = self.query('UNKNOWN/COMMAND.msg')
+               obj = msgpack.loads(f.read())
+               self.assertTrue('UNKNOWN' in obj)
+               self.assertTrue(isinstance(obj, dict))
+               self.assertTrue(isinstance(obj['UNKNOWN'], tuple))
+               self.assertTrue(obj['UNKNOWN'][0] == False)
+               self.assertTrue(isinstance(obj['UNKNOWN'][1], str))
+
+class TestETag(TestWebdis):
+
+       def test_etag_match(self):
+               self.query('SET/hello/world')
+               h = hashlib.md5("world").hexdigest()    # match Etag
+               try:
+                       f = self.query('GET/hello.txt', None, {'If-None-Match': '"'+ h +'"'})
+               except urllib2.HTTPError as e:
+                       self.assertTrue(e.code == 304)
+                       return
+               self.assertTrue(False) # we should have received a 304.
+
+       def test_etag_fail(self):
+               self.query('SET/hello/world')
+               h = hashlib.md5("nonsense").hexdigest() # non-matching Etag
+               f = self.query('GET/hello.txt', None, {'If-None-Match': '"'+ h +'"'})
+               self.assertTrue(f.read() == 'world')
+
+class TestDbSwitch(TestWebdis):
+       def test_db(self):
+               "Test database change"
+               self.query('0/SET/key/val0')
+               self.query('1/SET/key/val1')
+               f = self.query('0/GET/key.txt')
+               self.assertTrue(f.read() == "val0")
+               f = self.query('1/GET/key.txt')
+               self.assertTrue(f.read() == "val1")
+               f = self.query('GET/key.txt')
+               self.assertTrue(f.read() == "val0")
+
+if __name__ == '__main__':
+       unittest.main()
diff --git a/tests/bench.sh b/tests/bench.sh
new file mode 100755 (executable)
index 0000000..0761696
--- /dev/null
@@ -0,0 +1,71 @@
+#!/bin/bash
+CLIENTS=100
+REQUESTS=100000
+
+HOST=$WEBDIS_HOST
+PORT=$WEBDIS_PORT
+
+[ -n $HOST ] && HOST=127.0.0.1
+[ -n $PORT ] && PORT=7379
+
+info() {
+       echo "Testing on $HOST:$PORT with $CLIENTS clients in parallel, for a total of $REQUESTS requests per benchmark."
+}
+
+once() {
+       curl -q http://$HOST:$PORT/$1 1> /dev/null 2> /dev/null
+}
+
+bench() {
+       NUM=`ab -k -c $CLIENTS -n $REQUESTS http://$HOST:$PORT/$1 2>/dev/null | grep "#/sec" | sed -e "s/[^0-9.]//g"`
+       echo -ne $NUM
+}
+
+test_ping() {
+       echo -en "PING: "
+       bench "PING"
+       echo " requests/sec."
+}
+
+test_set() {
+       echo -en "SET(hello,world): "
+       bench "SET/hello/world"
+       echo " requests/sec."
+}
+
+test_get() {
+       echo -en "GET(hello): "
+       bench "GET/hello"
+       echo " requests/sec."
+}
+
+test_incr() {
+       once "DEL/hello"
+
+       echo -en "INCR(hello): "
+       bench "INCR/hello"
+       echo " requests/sec."
+}
+
+test_lpush() {
+       once "DEL/hello"
+
+       echo -en "LPUSH(hello,abc): "
+       bench "LPUSH/hello/abc"
+       echo " requests/sec."
+}
+
+test_lrange() {
+       echo -en "LRANGE(hello,$1,$2): "
+       bench "LRANGE/hello/$1/$2"
+       echo " requests/sec."
+}
+
+info
+test_ping
+test_set
+test_get
+test_incr
+test_lpush
+test_lrange 0 10
+test_lrange 0 100
diff --git a/tests/limits.py b/tests/limits.py
new file mode 100755 (executable)
index 0000000..1fdecc8
--- /dev/null
@@ -0,0 +1,105 @@
+#!/usr/bin/python
+import socket
+import unittest
+
+import os
+HOST = os.getenv('WEBDIS_HOST', '127.0.0.1')
+PORT = int(os.getenv('WEBDIS_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()
diff --git a/tests/pubsub.c b/tests/pubsub.c
new file mode 100644 (file)
index 0000000..f4a3451
--- /dev/null
@@ -0,0 +1,339 @@
+#include <stdlib.h>
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <signal.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <event.h>
+
+
+/*
+ * Connection object.
+ */
+struct cx {
+       int fd;
+
+       int *counter;   /* shared counter of the number of messages received. */
+       int total;      /* total number of messages to send */
+
+       char *http_request;
+
+       void (*read_fun)(int,short,void*);      /* called when able to read fd */
+       void (*write_fun)(int,short,void*);     /* called when able to write fd */
+
+       /* libevent data structures */
+       struct event evr, evw;
+       struct event_base *base;
+};
+
+int
+webdis_connect(const char *host, short port) {
+
+       int ret;
+       int fd;
+       struct sockaddr_in addr;
+
+       /* connect socket */
+       fd = socket(AF_INET, SOCK_STREAM, 0);
+       addr.sin_family = AF_INET;
+       addr.sin_port = htons(port);
+       memset(&(addr.sin_addr), 0, sizeof(addr.sin_addr));
+       addr.sin_addr.s_addr = inet_addr(host);
+
+       ret = connect(fd, (struct sockaddr*)&addr, sizeof(struct sockaddr));
+       if(ret != 0) {
+               fprintf(stderr, "connect: ret=%d: %s\n", ret, strerror(errno));
+               return -1;
+       }
+
+       return fd;
+}
+
+/**
+ * Send request and read until the delimiter string is reached. blocking.
+ */
+void
+reader_http_request(struct cx *c, const char* buffer, const char *limit) {
+
+       char resp[2048];
+       int pos = 0;
+
+       int r = write(c->fd, buffer, strlen(buffer));
+       (void)r;
+
+       memset(resp, 0, sizeof(resp));
+       while(1) {
+               int ret = read(c->fd, resp+pos, sizeof(resp)-pos);
+               if(ret <= 0) {
+                       return;
+               }
+               pos += ret;
+
+               if(strstr(resp, limit) != NULL) {
+                       break;
+               }
+       }
+}
+
+/**
+ * (re)install connection in the event loop.
+ */
+void
+cx_install(struct cx *c) {
+
+       if(c->read_fun) { /* attach callback for read. */
+               event_set(&c->evr, c->fd, EV_READ, c->read_fun, c);
+               event_base_set(c->base, &c->evr);
+               event_add(&c->evr, NULL);
+       }
+       if(c->write_fun) { /* attach callback for write. */
+               event_set(&c->evw, c->fd, EV_WRITE, c->write_fun, c);
+               event_base_set(c->base, &c->evw);
+               event_add(&c->evw, NULL);
+       }
+
+}
+
+/**
+ * Called when a reader has received data.
+ */
+void
+reader_can_read(int fd, short event, void *ptr) {
+
+       char buffer[1024];
+       struct cx *c = ptr;
+       const char *p;
+
+       (void)event;
+
+       int ret = read(fd, buffer, sizeof(buffer));
+       if(ret > 0) {
+
+               /* count messages, each message starts with '{' */
+               p = buffer;
+               do {
+                       /* look for the start of a message */
+                       p = memchr(p, '{', buffer + ret - p);
+
+                       if(!p) break;   /* none left. */
+                       p++;
+
+                       (*c->counter)++; /* increment the global message counter. */
+                       if(((*c->counter * 100) % c->total) == 0) { /* show progress. */
+                               printf("\r%d %%", 100 * *c->counter / c->total);
+                               fflush(stdout);
+                       }
+                       if(*c->counter > c->total) {
+                               /* halt event loop. */
+                               event_base_loopbreak(c->base);
+                       }
+               } while(1);
+       }
+       
+       cx_install(c);
+}
+
+
+/**
+ * create a new reader object.
+ */
+void
+reader_new(struct event_base *base, const char *host, short port, int total, int *counter, int chan) {
+
+       struct cx *c = calloc(1, sizeof(struct cx));
+       c->base = base;
+       c->counter = counter;
+       c->total = total;
+       c->fd = webdis_connect(host, port);
+       c->read_fun = reader_can_read;
+
+       /* send subscription request. */
+       c->http_request = malloc(100);
+       sprintf(c->http_request, "GET /SUBSCRIBE/chan:%d HTTP/1.1\r\n\r\n", chan);
+       reader_http_request(c, c->http_request, "{\"SUBSCRIBE\":[\"subscribe\"");
+
+       /* add to the event loop. */
+       cx_install(c);
+}
+
+/**
+ * Called when a writer has received data back. read and ignore.
+ */
+void
+writer_can_read(int fd, short event, void *ptr) {
+       char buffer[1024];
+       struct cx *c = ptr;
+       int r;
+
+       (void)event;
+
+       r = read(fd, buffer, sizeof(buffer)); /* discard */
+       (void)r;
+
+       /* re-install in the event loop. */
+       cx_install(c);
+}
+
+/* send request */
+void
+writer_can_write(int fd, short event, void *ptr) {
+
+       struct cx *c = ptr;
+
+       (void)fd;
+       (void)event;
+
+       reader_http_request(c, c->http_request, "{\"PUBLISH\":");
+
+       cx_install(c);
+}
+
+void
+writer_new(struct event_base *base, const char *host, short port, int chan) {
+
+       struct cx *c = malloc(sizeof(struct cx));
+       c->base = base;
+       c->fd = webdis_connect(host, port);
+       c->read_fun = writer_can_read;
+       c->write_fun = writer_can_write;
+
+       /* send request. */
+       c->http_request = malloc(100);
+       sprintf(c->http_request, "GET /PUBLISH/chan:%d/hi HTTP/1.1\r\n\r\n", chan);
+       reader_http_request(c, c->http_request, "{\"PUBLISH\":");
+
+       cx_install(c);
+}
+
+
+void
+usage(const char* argv0, char *host_default, short port_default,
+               int r_default, int w_default, int n_default, int c_default) {
+
+       printf("Usage: %s [options]\n"
+               "Options are:\n"
+               "\t-h host\t\t(default = \"%s\")\n"
+               "\t-p port\t\t(default = %d)\n"
+               "\t-r readers\t(default = %d)\n"
+               "\t-w writers\t(default = %d)\n"
+               "\t-c channels\t(default = %d)\n"
+               "\t-n messages\t(number of messages to read in total, default = %d)\n",
+               argv0, host_default, (int)port_default,
+               r_default, w_default,
+               c_default, n_default);
+}
+
+static struct event_base *__base;
+void on_sigint(int s) {
+       (void)s;
+       event_base_loopbreak(__base);
+}
+
+
+int
+main(int argc, char *argv[]) {
+
+       /* Create R readers and W writers, send N messages in total. */
+
+       struct timespec t0, t1;
+
+       struct event_base *base = event_base_new();
+       int i, count = 0;
+
+       /* getopt vars */
+       int opt;
+       char *colon;
+
+       /* default values */
+       short port_default = 7379;
+       char *host_default = "127.0.0.1";
+       int r_default = 450, w_default = 10, n_default = 100000, c_default = 1;
+
+
+       /* real values */
+       int r = r_default, w = w_default, chans = c_default, n = n_default;
+       char *host = host_default;
+       short port = port_default;
+
+       /* getopt */
+       while ((opt = getopt(argc, argv, "h:p:r:w:c:n:")) != -1) {
+               switch (opt) {
+                       case 'h':
+                               colon = strchr(optarg, ':');
+                               if(!colon) {
+                                       size_t sz = strlen(optarg);
+                                       host = calloc(1 + sz, 1);
+                                       strncpy(host, optarg, sz);
+                               } else {
+                                       host = calloc(1+colon-optarg, 1);
+                                       strncpy(host, optarg, colon-optarg);
+                                       port = (short)atol(colon+1);
+                               }
+                               break;
+
+                       case 'p':
+                               port = (short)atol(optarg);
+                               break;
+
+                       case 'r':
+                               r = atoi(optarg);
+                               break;
+
+                       case 'w':
+                               w = atoi(optarg);
+                               break;
+
+                       case 'c':
+                               chans = atoi(optarg);
+                               break;
+
+                       case 'n':
+                               n = atoi(optarg);
+                               break;
+
+                       default:
+                               usage(argv[0], host_default, port_default,
+                                               r_default, w_default,
+                                               n_default, c_default);
+                               exit(EXIT_FAILURE);
+               }
+       }
+
+       for(i = 0; i < r; ++i) {
+               reader_new(base, host, port, n, &count, i % chans);
+       }
+
+       for(i = 0; i < w; ++i) {
+               writer_new(base, host, port, i % chans);
+       }
+
+       /* install signal handler */
+       __base = base;
+       signal(SIGINT, on_sigint);
+
+       /* save time now */
+       clock_gettime(CLOCK_MONOTONIC, &t0);
+       
+       /* run test */
+       event_base_dispatch(base);
+
+       /* timing */
+       clock_gettime(CLOCK_MONOTONIC, &t1);
+       float mili0 = t0.tv_sec * 1000 + t0.tv_nsec / 1000000;
+       float mili1 = t1.tv_sec * 1000 + t1.tv_nsec / 1000000;
+
+       printf("\rReceived %d messages from %d writers to %d readers through %d channels in %0.2f sec: received %0.2f msg/sec\n",
+               count, w, r, chans,
+               (mili1-mili0)/1000.0,
+               1000*count/(mili1-mili0));
+
+       return EXIT_SUCCESS;
+}
+
diff --git a/tests/websocket.c b/tests/websocket.c
new file mode 100644 (file)
index 0000000..1bce69d
--- /dev/null
@@ -0,0 +1,326 @@
+/* http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 */
+
+#include <stdlib.h>
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <arpa/inet.h>
+#include <errno.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <event.h>
+
+struct host_info {
+       char *host;
+       short port;
+};
+
+/* worker_thread, with counter of remaining messages */
+struct worker_thread {
+       struct host_info *hi;
+       struct event_base *base;
+
+       int msg_target;
+       int msg_received;
+       int msg_sent;
+       int byte_count;
+       pthread_t thread;
+
+       struct evbuffer *buffer;
+       int got_header;
+
+       int verbose;
+       struct event ev_w;
+};
+
+void
+process_message(struct worker_thread *wt, size_t sz) {
+
+       // printf("process_message\n");
+       if(wt->msg_received % 10000 == 0) {
+               printf("thread %u: %8d messages left (got %9d bytes so far).\n",
+                       (unsigned int)wt->thread,
+                       wt->msg_target - wt->msg_received, wt->byte_count);
+       }
+       wt->byte_count += sz;
+
+       /* decrement read count, and stop receiving when we reach zero. */
+       wt->msg_received++;
+       if(wt->msg_received == wt->msg_target) {
+               event_base_loopexit(wt->base, NULL);
+       }
+}
+
+void
+websocket_write(int fd, short event, void *ptr) {
+       int ret;
+       struct worker_thread *wt = ptr;
+
+       if(event != EV_WRITE) {
+               return;
+       }
+
+       char message[] = "\x00[\"SET\",\"key\",\"value\"]\xff\x00[\"GET\",\"key\"]\xff";
+       ret = write(fd, message, sizeof(message)-1);
+       if(ret != sizeof(message)-1) {
+               fprintf(stderr, "write on %d failed: %s\n", fd, strerror(errno));
+               close(fd);
+       }
+
+       wt->msg_sent += 2;
+       if(wt->msg_sent < wt->msg_target) {
+               event_set(&wt->ev_w, fd, EV_WRITE, websocket_write, wt);
+               event_base_set(wt->base, &wt->ev_w);
+               ret = event_add(&wt->ev_w, NULL);
+       }
+}
+
+static void
+websocket_read(int fd, short event, void *ptr) {
+       char packet[2048], *pos;
+       int ret, success = 1;
+
+       struct worker_thread *wt = ptr;
+
+       if(event != EV_READ) {
+               return;
+       }
+
+       /* read message */
+       ret = read(fd, packet, sizeof(packet));
+       pos = packet;
+       if(ret > 0) {
+               char *data, *last;
+               int sz, msg_sz;
+
+               if(wt->got_header == 0) { /* first response */
+                       char *frame_start = strstr(packet, "MH"); /* end of the handshake */
+                       if(frame_start == NULL) {
+                               return; /* not yet */
+                       } else { /* start monitoring possible writes */
+                               printf("start monitoring possible writes\n");
+                               evbuffer_add(wt->buffer, frame_start + 2, ret - (frame_start + 2 - packet));
+
+                               wt->got_header = 1;
+                               event_set(&wt->ev_w, fd, EV_WRITE,
+                                               websocket_write, wt);
+                               event_base_set(wt->base, &wt->ev_w);
+                               ret = event_add(&wt->ev_w, NULL);
+                       }
+               } else {
+                       /* we've had the header already, now bufffer data. */
+                       evbuffer_add(wt->buffer, packet, ret);
+               }
+
+               while(1) {
+                       data = (char*)EVBUFFER_DATA(wt->buffer);
+                       sz = EVBUFFER_LENGTH(wt->buffer);
+
+                       if(sz == 0) { /* no data */
+                               break;
+                       }
+                       if(*data != 0) { /* missing frame start */
+                               success = 0;
+                               break;
+                       }
+                       last = memchr(data, 0xff, sz); /* look for frame end */
+                       if(!last) {
+                               /* no end of frame in sight. */
+                               break;
+                       }
+                       msg_sz = last - data - 1;
+                       process_message(ptr, msg_sz); /* record packet */
+
+                       /* drain including frame delimiters (+2 bytes) */
+                       evbuffer_drain(wt->buffer, msg_sz + 2); 
+               }
+       } else {
+               printf("ret=%d\n", ret);
+               success = 0;
+       }
+       if(success == 0) {
+               shutdown(fd, SHUT_RDWR);
+               close(fd);
+               event_base_loopexit(wt->base, NULL);
+       }
+}
+
+void*
+worker_main(void *ptr) {
+
+       char ws_template[] = "GET /.json HTTP/1.1\r\n"
+                               "Host: %s:%d\r\n"
+                               "Connection: Upgrade\r\n"
+                               "Upgrade: WebSocket\r\n"
+                               "Origin: http://%s:%d\r\n"
+                               "Sec-WebSocket-Key1: 18x 6]8vM;54 *(5:  {   U1]8  z [  8\r\n"
+                               "Sec-WebSocket-Key2: 1_ tx7X d  <  nw  334J702) 7]o}` 0\r\n"
+                               "\r\n"
+                               "Tm[K T2u";
+
+       struct worker_thread *wt = ptr;
+
+       int ret;
+       int fd;
+       struct sockaddr_in addr;
+       char *ws_handshake;
+       size_t ws_handshake_sz;
+
+       /* connect socket */
+       fd = socket(AF_INET, SOCK_STREAM, 0);
+       addr.sin_family = AF_INET;
+       addr.sin_port = htons(wt->hi->port);
+       memset(&(addr.sin_addr), 0, sizeof(addr.sin_addr));
+       addr.sin_addr.s_addr = inet_addr(wt->hi->host);
+
+       ret = connect(fd, (struct sockaddr*)&addr, sizeof(struct sockaddr));
+       if(ret != 0) {
+               fprintf(stderr, "connect: ret=%d: %s\n", ret, strerror(errno));
+               return NULL;
+       }
+
+       /* initialize worker thread */
+       wt->base = event_base_new();
+       wt->buffer = evbuffer_new();
+       wt->byte_count = 0;
+       wt->got_header = 0;
+
+       /* send handshake */
+       ws_handshake_sz = sizeof(ws_handshake)
+               + 2*strlen(wt->hi->host) + 500;
+       ws_handshake = calloc(ws_handshake_sz, 1);
+       ws_handshake_sz = (size_t)sprintf(ws_handshake, ws_template, 
+                       wt->hi->host, wt->hi->port,
+                       wt->hi->host, wt->hi->port);
+       ret = write(fd, ws_handshake, ws_handshake_sz);
+
+       struct event ev_r;
+       event_set(&ev_r, fd, EV_READ | EV_PERSIST, websocket_read, wt);
+       event_base_set(wt->base, &ev_r);
+       event_add(&ev_r, NULL);
+
+       /* go! */
+       event_base_dispatch(wt->base);
+       event_base_free(wt->base);
+       free(ws_handshake);
+       return NULL;
+}
+
+void
+usage(const char* argv0, char *host_default, short port_default,
+               int thread_count_default, int messages_default) {
+
+       printf("Usage: %s [options]\n"
+               "Options are:\n"
+               "\t-h host\t\t(default = \"%s\")\n"
+               "\t-p port\t\t(default = %d)\n"
+               "\t-c threads\t(default = %d)\n"
+               "\t-n count\t(number of messages per thread, default = %d)\n"
+               "\t-v\t\t(verbose)\n",
+               argv0, host_default, (int)port_default,
+               thread_count_default, messages_default);
+}
+
+int
+main(int argc, char *argv[]) {
+
+       struct timespec t0, t1;
+
+       int messages_default = 100000;
+       int thread_count_default = 4;
+       short port_default = 7379;
+       char *host_default = "127.0.0.1";
+
+       int msg_target = messages_default;
+       int thread_count = thread_count_default;
+       int i, opt;
+       char *colon;
+       double total = 0, total_bytes = 0;
+       int verbose = 0;
+
+       struct host_info hi = {host_default, port_default};
+
+       struct worker_thread *workers;
+       
+       /* getopt */
+       while ((opt = getopt(argc, argv, "h:p:c:n:v")) != -1) {
+               switch (opt) {
+                       case 'h':
+                               colon = strchr(optarg, ':');
+                               if(!colon) {
+                                       size_t sz = strlen(optarg);
+                                       hi.host = calloc(1 + sz, 1);
+                                       strncpy(hi.host, optarg, sz);
+                               } else {
+                                       hi.host = calloc(1+colon-optarg, 1);
+                                       strncpy(hi.host, optarg, colon-optarg);
+                                       hi.port = (short)atol(colon+1);
+                               }
+                               break;
+
+                       case 'p':
+                               hi.port = (short)atol(optarg);
+                               break;
+
+                       case 'c':
+                               thread_count = atoi(optarg);
+                               break;
+
+                       case 'n':
+                               msg_target = atoi(optarg);
+                               break;
+
+                       case 'v':
+                               verbose = 1;
+                               break;
+                       default: /* '?' */
+                               usage(argv[0], host_default, port_default,
+                                               thread_count_default,
+                                               messages_default);
+                               exit(EXIT_FAILURE);
+               }
+       }
+
+       /* run threads */
+       workers = calloc(sizeof(struct worker_thread), thread_count);
+
+       clock_gettime(CLOCK_MONOTONIC, &t0);
+       for(i = 0; i < thread_count; ++i) {
+               workers[i].msg_target = msg_target;
+               workers[i].hi = &hi;
+               workers[i].verbose = verbose;
+
+               pthread_create(&workers[i].thread, NULL,
+                               worker_main, &workers[i]);
+       }
+
+       /* wait for threads to finish */
+       for(i = 0; i < thread_count; ++i) {
+               pthread_join(workers[i].thread, NULL);
+               total += workers[i].msg_received;
+               total_bytes += workers[i].byte_count;
+       }
+
+       /* timing */
+       clock_gettime(CLOCK_MONOTONIC, &t1);
+       float mili0 = t0.tv_sec * 1000 + t0.tv_nsec / 1000000;
+       float mili1 = t1.tv_sec * 1000 + t1.tv_nsec / 1000000;
+
+       if(total != 0) {
+               printf("Read %ld messages in %0.2f sec: %0.2f msg/sec (%d MB/sec, %d KB/sec)\n",
+                       (long)total, 
+                       (mili1-mili0)/1000.0,
+                       1000*total/(mili1-mili0),
+                       (int)(total_bytes / (1000*(mili1-mili0))),
+                       (int)(total_bytes / (mili1-mili0)));
+               return EXIT_SUCCESS;
+       } else {
+               printf("No message was read.\n");
+               return EXIT_FAILURE;
+       }
+}
+
diff --git a/tests/websocket.html b/tests/websocket.html
new file mode 100644 (file)
index 0000000..a30f936
--- /dev/null
@@ -0,0 +1,153 @@
+<!DOCTYPE html>
+<html>
+       <head>
+               <title>WebSocket example</title>
+               <meta charset="utf-8" />
+               <style type="text/css">
+
+                       body {
+                               width: 800px;
+                               margin: auto;
+                       }
+
+                       header {
+                               font-size: 36pt;
+                               width: 100%;
+                               text-align: center;
+                               margin-bottom: 1em;
+                               color: #541F14;
+                       }
+
+                       section.proto {
+                               float: left;
+                               /*background-color: #f8f8f8;*/
+                       }
+
+                       section#json {
+                               width: 380px;
+                               margin-right: 20px;
+                       }
+
+                       section#raw {
+                               width: 380px;
+                               padding-left: 16px;
+                               border-left: 4px solid #626266;
+                       }
+
+                       div.desc {
+                               margin: 0px;
+                       }
+                       pre.sent, pre.received {
+                               margin-top: 0px;
+                               border-radius: 4px;
+                               padding: 5px;
+                       }
+                       pre.sent {
+                               border: 1px solid #938172;
+                               background-color: white;
+                       }
+                       pre.received {
+                               border: 1px solid #CC9E61;
+                               text-align: right;
+                       }
+
+               </style>
+       </head>
+
+       <body>
+               <header>Webdis with HTML5 WebSockets</header>
+
+               <section class="proto" id="json">
+                       <h3>JSON</h3>
+                       <div class="log" id="json-log">
+                               Connecting...
+                       </div>
+               </section>
+               <section class="proto" id="raw">
+                       <h3>Raw</h3>
+                       <div class="log" id="raw-log">
+                               Connecting...
+                       </div>
+               </section>
+
+
+               <script type="text/javascript">
+
+$ = function(id) {return document.getElementById(id);};
+var host = "127.0.0.1";
+var port = 7379;
+
+function log(id, dir, msg) {
+
+       var desc = document.createElement("div");
+       desc.setAttribute("class", "desc");
+       desc.innerHTML = dir;
+       $(id).appendChild(desc);
+
+       var e = document.createElement("pre");
+       e.setAttribute("class", dir);
+       e.innerHTML = msg;
+       $(id).appendChild(e);
+}
+
+function testJSON() {
+
+       if(typeof(WebSocket) == 'function')
+               f = WebSocket;
+       if(typeof(MozWebSocket) == 'function')
+               f = MozWebSocket;
+
+       var jsonSocket = new f("ws://"+host+":"+port+"/.json");
+       var self = this;
+
+       send = function(j) {
+               var json = JSON.stringify(j);
+               jsonSocket.send(json);
+               log("json-log", "sent", json);
+       };
+
+       jsonSocket.onopen = function() {
+               $("json-log").innerHTML = "";
+               self.send(["SET", "hello", "world"]);
+               self.send(["GET", "hello"]);
+       };
+
+       jsonSocket.onmessage = function(messageEvent) {
+               log("json-log", "received", messageEvent.data);
+       };
+}
+
+function testRAW() {
+
+       if(typeof(WebSocket) == 'function')
+               f = WebSocket;
+       if(typeof(MozWebSocket) == 'function')
+               f = MozWebSocket;
+
+       var rawSocket = new f("ws://"+host+":"+port+"/.raw");
+       var self = this;
+
+       sendRaw = function(raw) {
+               rawSocket.send(raw);
+               log("raw-log", "sent", raw);
+       };
+
+       rawSocket.onopen = function() {
+               $("raw-log").innerHTML = "";
+               self.sendRaw("*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n");
+               self.sendRaw("*2\r\n$3\r\nGET\r\n$5\r\nhello\r\n");
+       };
+
+       rawSocket.onmessage = function(messageEvent) {
+               log("raw-log", "received", messageEvent.data);
+       };
+}
+
+addEventListener("DOMContentLoaded", function(){
+       testJSON();
+       testRAW();
+}, null);
+
+               </script>
+       </body>
+</html>
diff --git a/version.h b/version.h
new file mode 100644 (file)
index 0000000..8022b9a
--- /dev/null
+++ b/version.h
@@ -0,0 +1,8 @@
+#ifndef VERSION_H
+#define VERSION_H
+
+#ifndef WEBDIS_VERSION
+#define WEBDIS_VERSION "0.1.1"
+#endif
+
+#endif /* VERSION_H */
diff --git a/webdis.c b/webdis.c
new file mode 100644 (file)
index 0000000..34e88e3
--- /dev/null
+++ b/webdis.c
@@ -0,0 +1,20 @@
+#include "server.h"
+
+#include <stdlib.h>
+
+int
+main(int argc, char *argv[]) {
+
+       struct server *s;
+
+       if(argc > 1) {
+               s = server_new(argv[1]);
+       } else {
+               s = server_new("webdis.json");
+       }
+
+       server_start(s);
+
+       return EXIT_SUCCESS;
+}
+
diff --git a/webdis.json b/webdis.json
new file mode 100644 (file)
index 0000000..f4b0714
--- /dev/null
@@ -0,0 +1,31 @@
+{
+       "redis_host":   "127.0.0.1",
+
+       "redis_port":   6379,
+       "redis_auth":   null,
+
+       "http_host":    "0.0.0.0",
+       "http_port":    7379,
+
+       "threads":      5,
+       "pool_size": 20,
+
+       "daemonize":    false,
+       "websockets":   false,
+
+       "database":     0,
+
+       "acl": [
+               {
+                       "disabled":     ["DEBUG"]
+               },
+
+               {
+                       "http_basic_auth":      "user:password",
+                       "enabled":              ["DEBUG"]
+               }
+       ],
+
+        "verbosity": 6,
+        "logfile": "webdis.log"
+}
diff --git a/webdis.prod.json b/webdis.prod.json
new file mode 100644 (file)
index 0000000..de35ca8
--- /dev/null
@@ -0,0 +1,28 @@
+{
+       "redis_host":   "127.0.0.1",
+
+       "redis_port":   6379,
+       "redis_auth":   null,
+
+       "http_host":    "0.0.0.0",
+       "http_port":    7379,
+       "threads":      4,
+
+       "daemonize":    true,
+
+       "database":     0,
+
+       "acl": [
+               {
+                       "disabled":     ["DEBUG"]
+               },
+
+               {
+                       "http_basic_auth":      "user:password",
+                       "enabled":              ["DEBUG"]
+               }
+       ],
+
+        "verbosity": 3,
+        "logfile": "/var/log/webdis.log"
+}
diff --git a/websocket.c b/websocket.c
new file mode 100644 (file)
index 0000000..9b0223c
--- /dev/null
@@ -0,0 +1,380 @@
+#include "sha1/sha1.h"
+#include <b64/cencode.h>
+#include "websocket.h"
+#include "client.h"
+#include "cmd.h"
+#include "worker.h"
+#include "pool.h"
+#include "http.h"
+
+/* message parsers */
+#include "formats/json.h"
+#include "formats/raw.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/param.h>
+
+/**
+ * This code uses the WebSocket specification from RFC 6455.
+ * A copy is available at http://www.rfc-editor.org/rfc/rfc6455.txt
+ */
+
+/* custom 64-bit encoding functions to avoid portability issues */
+#define webdis_ntohl64(p) \
+       ((((uint64_t)((p)[0])) <<  0) + (((uint64_t)((p)[1])) <<  8) +\
+        (((uint64_t)((p)[2])) << 16) + (((uint64_t)((p)[3])) << 24) +\
+        (((uint64_t)((p)[4])) << 32) + (((uint64_t)((p)[5])) << 40) +\
+        (((uint64_t)((p)[6])) << 48) + (((uint64_t)((p)[7])) << 56))
+
+#define webdis_htonl64(p) {\
+       (char)(((p & ((uint64_t)0xff <<  0)) >>  0) & 0xff), (char)(((p & ((uint64_t)0xff <<  8)) >>  8) & 0xff), \
+       (char)(((p & ((uint64_t)0xff << 16)) >> 16) & 0xff), (char)(((p & ((uint64_t)0xff << 24)) >> 24) & 0xff), \
+       (char)(((p & ((uint64_t)0xff << 32)) >> 32) & 0xff), (char)(((p & ((uint64_t)0xff << 40)) >> 40) & 0xff), \
+       (char)(((p & ((uint64_t)0xff << 48)) >> 48) & 0xff), (char)(((p & ((uint64_t)0xff << 56)) >> 56) & 0xff) }
+static int
+ws_compute_handshake(struct http_client *c, char *out, size_t *out_sz) {
+
+       unsigned char *buffer, sha1_output[20];
+       char magic[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+       SHA1Context ctx;
+       base64_encodestate b64_ctx;
+       int pos, i;
+
+       // websocket handshake
+       const char *key = client_get_header(c, "Sec-WebSocket-Key");
+       size_t key_sz = key?strlen(key):0, buffer_sz = key_sz + sizeof(magic) - 1;
+       buffer = calloc(buffer_sz, 1);
+
+       // concatenate key and guid in buffer
+       memcpy(buffer, key, key_sz);
+       memcpy(buffer+key_sz, magic, sizeof(magic)-1);
+
+       // compute sha-1
+       SHA1Reset(&ctx);
+       SHA1Input(&ctx, buffer, buffer_sz);
+       SHA1Result(&ctx);
+       for(i = 0; i < 5; ++i) {        // put in correct byte order before memcpy.
+               ctx.Message_Digest[i] = ntohl(ctx.Message_Digest[i]);
+       }
+       memcpy(sha1_output, (unsigned char*)ctx.Message_Digest, 20);
+
+       // encode `sha1_output' in base 64, into `out'.
+       base64_init_encodestate(&b64_ctx);
+       pos = base64_encode_block((const char*)sha1_output, 20, out, &b64_ctx);
+       base64_encode_blockend(out + pos, &b64_ctx);
+
+       // compute length, without \n
+       *out_sz = strlen(out);
+       if(out[*out_sz-1] == '\n')
+               (*out_sz)--;
+
+       free(buffer);
+
+       return 0;
+}
+
+int
+ws_handshake_reply(struct http_client *c) {
+
+       int ret;
+       char sha1_handshake[40];
+       char *buffer = NULL, *p;
+       const char *origin = NULL, *host = NULL;
+       size_t origin_sz = 0, host_sz = 0, handshake_sz = 0, sz;
+
+       char template0[] = "HTTP/1.1 101 Switching Protocols\r\n"
+               "Upgrade: websocket\r\n"
+               "Connection: Upgrade\r\n"
+               "Sec-WebSocket-Origin: "; /* %s */
+       char template1[] = "\r\n"
+               "Sec-WebSocket-Location: ws://"; /* %s%s */
+       char template2[] = "\r\n"
+               "Origin: http://"; /* %s */
+       char template3[] = "\r\n"
+               "Sec-WebSocket-Accept: "; /* %s */
+       char template4[] = "\r\n\r\n";
+
+       if((origin = client_get_header(c, "Origin"))) {
+               origin_sz = strlen(origin);
+       } else if((origin = client_get_header(c, "Sec-WebSocket-Origin"))) {
+               origin_sz = strlen(origin);
+       }
+       if((host = client_get_header(c, "Host"))) {
+               host_sz = strlen(host);
+       }
+
+       /* need those headers */
+       if(!origin || !origin_sz || !host || !host_sz || !c->path || !c->path_sz) {
+               return -1;
+       }
+
+       memset(sha1_handshake, 0, sizeof(sha1_handshake));
+       if(ws_compute_handshake(c, &sha1_handshake[0], &handshake_sz) != 0) {
+               /* failed to compute handshake. */
+               return -1;
+       }
+
+       sz = sizeof(template0)-1 + origin_sz
+               + sizeof(template1)-1 + host_sz + c->path_sz
+               + sizeof(template2)-1 + host_sz
+               + sizeof(template3)-1 + handshake_sz
+               + sizeof(template4)-1;
+
+       p = buffer = malloc(sz);
+
+       /* Concat all */
+
+       /* template0 */
+       memcpy(p, template0, sizeof(template0)-1);
+       p += sizeof(template0)-1;
+       memcpy(p, origin, origin_sz);
+       p += origin_sz;
+
+       /* template1 */
+       memcpy(p, template1, sizeof(template1)-1);
+       p += sizeof(template1)-1;
+       memcpy(p, host, host_sz);
+       p += host_sz;
+       memcpy(p, c->path, c->path_sz);
+       p += c->path_sz;
+
+       /* template2 */
+       memcpy(p, template2, sizeof(template2)-1);
+       p += sizeof(template2)-1;
+       memcpy(p, host, host_sz);
+       p += host_sz;
+
+       /* template3 */
+       memcpy(p, template3, sizeof(template3)-1);
+       p += sizeof(template3)-1;
+       memcpy(p, &sha1_handshake[0], handshake_sz);
+       p += handshake_sz;
+
+       /* template4 */
+       memcpy(p, template4, sizeof(template4)-1);
+       p += sizeof(template4)-1;
+
+       /* send data to client */
+       ret = write(c->fd, buffer, sz);
+       (void)ret;
+       free(buffer);
+
+       return 0;
+}
+
+
+static int
+ws_execute(struct http_client *c, const char *frame, size_t frame_len) {
+
+       struct cmd*(*fun_extract)(struct http_client *, const char *, size_t) = NULL;
+       formatting_fun fun_reply = NULL;
+
+       if((c->path_sz == 1 && strncmp(c->path, "/", 1) == 0) ||
+          strncmp(c->path, "/.json", 6) == 0) {
+               fun_extract = json_ws_extract;
+               fun_reply = json_reply;
+       } else if(strncmp(c->path, "/.raw", 5) == 0) {
+               fun_extract = raw_ws_extract;
+               fun_reply = raw_reply;
+       }
+
+       if(fun_extract) {
+
+               /* Parse websocket frame into a cmd object. */
+               struct cmd *cmd = fun_extract(c, frame, frame_len);
+
+               if(cmd) {
+                       /* copy client info into cmd. */
+                       cmd_setup(cmd, c);
+                       cmd->is_websocket = 1;
+
+                       if (c->pub_sub != NULL) {
+                               /* This client already has its own connection
+                                * to Redis due to a subscription; use it from
+                                * now on. */
+                               cmd->ac = c->pub_sub->ac;
+                       } else if (cmd_is_subscribe(cmd)) {
+                               /* New subscribe command; make new Redis context
+                                * for this client */
+                               cmd->ac = pool_connect(c->w->pool, cmd->database, 0);
+                               c->pub_sub = cmd;
+                               cmd->pub_sub_client = c;
+                       } else {
+                               /* get Redis connection from pool */
+                               cmd->ac = (redisAsyncContext*)pool_get_context(c->w->pool);
+                       }
+
+                       /* send it off */
+                       cmd_send(cmd, fun_reply);
+
+                       return 0;
+               }
+       }
+
+       return -1;
+}
+
+static struct ws_msg *
+ws_msg_new() {
+       return calloc(1, sizeof(struct ws_msg));
+}
+
+static void
+ws_msg_add(struct ws_msg *m, const char *p, size_t psz, const unsigned char *mask) {
+       
+       /* add data to frame */
+       size_t i;
+       m->payload = realloc(m->payload, m->payload_sz + psz);
+       memcpy(m->payload + m->payload_sz, p, psz);
+
+       /* apply mask */
+       for(i = 0; i < psz && mask; ++i) {
+               m->payload[m->payload_sz + i] = (unsigned char)p[i] ^ mask[i%4];
+       }
+
+       /* save new size */
+       m->payload_sz += psz;
+}
+
+static void
+ws_msg_free(struct ws_msg **m) {
+
+       free((*m)->payload);
+       free(*m);
+       *m = NULL;
+}
+
+static enum ws_state
+ws_parse_data(const char *frame, size_t sz, struct ws_msg **msg) {
+       
+       int has_mask;
+       uint64_t len;
+       const char *p;
+       unsigned char mask[4];
+
+       /* parse frame and extract contents */
+       if(sz < 8) {
+               return WS_READING;
+       }
+
+       has_mask = frame[1] & 0x80 ? 1:0;
+
+       /* get payload length */
+       len = frame[1] & 0x7f;  /* remove leftmost bit */
+       if(len <= 125) { /* data starts right after the mask */
+               p = frame + 2 + (has_mask ? 4 : 0);
+               if(has_mask) memcpy(&mask, frame + 2, sizeof(mask));
+       } else if(len == 126) {
+               uint16_t sz16;
+               memcpy(&sz16, frame + 2, sizeof(uint16_t));
+               len = ntohs(sz16);
+               p = frame + 4 + (has_mask ? 4 : 0);
+               if(has_mask) memcpy(&mask, frame + 4, sizeof(mask));
+       } else if(len == 127) {
+               len = webdis_ntohl64(frame+2);
+               p = frame + 10 + (has_mask ? 4 : 0);
+               if(has_mask) memcpy(&mask, frame + 10, sizeof(mask));
+       } else {
+               return WS_ERROR;
+       }
+
+       /* we now have the (possibly masked) data starting in p, and its length.  */
+       if(len > sz - (p - frame)) { /* not enough data */
+               return WS_READING;
+       }
+
+       if(!*msg)
+               *msg = ws_msg_new();
+       ws_msg_add(*msg, p, len, has_mask ? mask : NULL);
+       (*msg)->total_sz += len + (p - frame);
+
+       if(frame[0] & 0x80) { /* FIN bit set */
+               return WS_MSG_COMPLETE;
+       } else {
+               return WS_READING;      /* need more data */
+       }
+}
+
+
+/**
+ * Process some data just received on the socket.
+ */
+enum ws_state
+ws_add_data(struct http_client *c) {
+
+       enum ws_state state;
+
+       state = ws_parse_data(c->buffer, c->sz, &c->frame);
+
+       if(state == WS_MSG_COMPLETE) {
+               int ret = ws_execute(c, c->frame->payload, c->frame->payload_sz);
+
+               /* remove frame from client buffer */
+               http_client_remove_data(c, c->frame->total_sz);
+
+               /* free frame and set back to NULL */
+               ws_msg_free(&c->frame);
+
+               if(ret != 0) {
+                       /* can't process frame. */
+                       return WS_ERROR;
+               }
+       }
+       return state;
+}
+
+int
+ws_reply(struct cmd *cmd, const char *p, size_t sz) {
+
+       char *frame = malloc(sz + 8); /* create frame by prepending header */
+       size_t frame_sz = 0;
+       struct http_response *r;
+       if (frame == NULL)
+               return -1;
+
+       /*
+      The length of the "Payload data", in bytes: if 0-125, that is the
+      payload length.  If 126, the following 2 bytes interpreted as a
+      16-bit unsigned integer are the payload length.  If 127, the
+      following 8 bytes interpreted as a 64-bit unsigned integer (the
+      most significant bit MUST be 0) are the payload length.
+         */
+       frame[0] = '\x81';
+       if(sz <= 125) {
+               frame[1] = sz;
+               memcpy(frame + 2, p, sz);
+               frame_sz = sz + 2;
+       } else if (sz > 125 && sz <= 65536) {
+               uint16_t sz16 = htons(sz);
+               frame[1] = 126;
+               memcpy(frame + 2, &sz16, 2);
+               memcpy(frame + 4, p, sz);
+               frame_sz = sz + 4;
+       } else if (sz > 65536) {
+               char sz64[8] = webdis_htonl64(sz);
+               frame[1] = 127;
+               memcpy(frame + 2, sz64, 8);
+               memcpy(frame + 10, p, sz);
+               frame_sz = sz + 10;
+       }
+
+       /* send WS frame */
+       r = http_response_init(cmd->w, 0, NULL);
+       if (cmd_is_subscribe(cmd)) {
+               r->keep_alive = 1;
+       }
+       
+       if (r == NULL)
+               return -1;
+
+       r->out = frame;
+       r->out_sz = frame_sz;
+       r->sent = 0;
+       http_schedule_write(cmd->fd, r);
+
+       return 0;
+}
diff --git a/websocket.h b/websocket.h
new file mode 100644 (file)
index 0000000..dc9d764
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef WEBSOCKET_H
+#define WEBSOCKET_H
+
+#include <stdlib.h>
+#include <stdint.h>
+
+struct http_client;
+struct cmd;
+
+enum ws_state {
+       WS_ERROR,
+       WS_READING,
+       WS_MSG_COMPLETE};
+
+struct ws_msg {
+       char *payload;
+       size_t payload_sz;
+       size_t total_sz;
+};
+
+int
+ws_handshake_reply(struct http_client *c);
+
+enum ws_state
+ws_add_data(struct http_client *c);
+
+int
+ws_reply(struct cmd *cmd, const char *p, size_t sz);
+
+#endif
diff --git a/worker.c b/worker.c
new file mode 100644 (file)
index 0000000..ceec0f9
--- /dev/null
+++ b/worker.c
@@ -0,0 +1,234 @@
+#include "worker.h"
+#include "client.h"
+#include "http.h"
+#include "cmd.h"
+#include "pool.h"
+#include "slog.h"
+#include "websocket.h"
+#include "conf.h"
+#include "server.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <event.h>
+#include <string.h>
+
+
+struct worker *
+worker_new(struct server *s) {
+
+       int ret;
+       struct worker *w = calloc(1, sizeof(struct worker));
+       w->s = s;
+
+       /* setup communication link */
+       ret = pipe(w->link);
+       (void)ret;
+
+       /* Redis connection pool */
+       w->pool = pool_new(w, s->cfg->pool_size_per_thread);
+
+       return w;
+
+}
+
+void
+worker_can_read(int fd, short event, void *p) {
+
+       struct http_client *c = p;
+       int ret, nparsed;
+
+       (void)fd;
+       (void)event;
+
+       ret = http_client_read(c);
+       if(ret <= 0) {
+               if((client_error_t)ret == CLIENT_DISCONNECTED) {
+                       return;
+               } else if (c->failed_alloc || (client_error_t)ret == CLIENT_OOM) {
+                       slog(c->w->s, WEBDIS_DEBUG, "503", 3);
+                       http_send_error(c, 503, "Service Unavailable");
+                       return;
+               }
+       }
+
+       if(c->is_websocket) {
+               /* Got websocket data */
+               ws_add_data(c);
+       } else {
+               /* run parser */
+               nparsed = http_client_execute(c);
+
+               if(c->failed_alloc) {
+                       slog(c->w->s, WEBDIS_DEBUG, "503", 3);
+                       http_send_error(c, 503, "Service Unavailable");
+               } else if(c->is_websocket) {
+                       /* we need to use the remaining (unparsed) data as the body. */
+                       if(nparsed < ret) {
+                               http_client_add_to_body(c, c->buffer + nparsed + 1, c->sz - nparsed - 1);
+                               ws_handshake_reply(c);
+                       } else {
+                               c->broken = 1;
+                       }
+                       free(c->buffer);
+                       c->buffer = NULL;
+                       c->sz = 0;
+               } else if(nparsed != ret) {
+                       slog(c->w->s, WEBDIS_DEBUG, "400", 3);
+                       http_send_error(c, 400, "Bad Request");
+               } else if(c->request_sz > c->s->cfg->http_max_request_size) {
+                       slog(c->w->s, WEBDIS_DEBUG, "413", 3);
+                       http_send_error(c, 413, "Request Entity Too Large");
+               }
+       }
+
+       if(c->broken) { /* terminate client */
+               http_client_free(c);
+       } else {
+               /* start monitoring input again */
+               worker_monitor_input(c);
+       }
+}
+
+/**
+ * Monitor client FD for possible reads.
+ */
+void
+worker_monitor_input(struct http_client *c) {
+
+       event_set(&c->ev, c->fd, EV_READ, worker_can_read, c);
+       event_base_set(c->w->base, &c->ev);
+       event_add(&c->ev, NULL);
+}
+
+/**
+ * Called when a client is sent to this worker.
+ */
+static void
+worker_on_new_client(int pipefd, short event, void *ptr) {
+
+       struct http_client *c;
+       unsigned long addr;
+
+       (void)event;
+       (void)ptr;
+
+       /* Get client from messaging pipe */
+       int ret = read(pipefd, &addr, sizeof(addr));
+       if(ret == sizeof(addr)) {
+               c = (struct http_client*)addr;
+
+               /* monitor client for input */
+               worker_monitor_input(c);
+       }
+}
+
+static void
+worker_pool_connect(struct worker *w) {
+
+       int i;
+       /* create connections */
+       for(i = 0; i < w->pool->count; ++i) {
+               pool_connect(w->pool, w->s->cfg->database, 1);
+       }
+
+}
+
+static void*
+worker_main(void *p) {
+
+       struct worker *w = p;
+       struct event ev;
+
+       /* setup libevent */
+       w->base = event_base_new();
+
+       /* monitor pipe link */
+       event_set(&ev, w->link[0], EV_READ | EV_PERSIST, worker_on_new_client, w);
+       event_base_set(w->base, &ev);
+       event_add(&ev, NULL);
+
+       /* connect to Redis */
+       worker_pool_connect(w);
+
+       /* loop */
+       event_base_dispatch(w->base);
+
+       return NULL;
+}
+
+void
+worker_start(struct worker *w) {
+
+       pthread_create(&w->thread, NULL, worker_main, w);
+}
+
+/**
+ * Queue new client to process
+ */
+void
+worker_add_client(struct worker *w, struct http_client *c) {
+
+       /* write into pipe link */
+       unsigned long addr = (unsigned long)c;
+       int ret = write(w->link[1], &addr, sizeof(addr));
+       (void)ret;
+}
+
+/**
+ * Called when a client has finished reading input and can create a cmd
+ */
+void
+worker_process_client(struct http_client *c) {
+
+       /* check that the command can be executed */
+       struct worker *w = c->w;
+       cmd_response_t ret = CMD_PARAM_ERROR;
+       switch(c->parser.method) {
+               case HTTP_GET:
+                       if(c->path_sz == 16 && memcmp(c->path, "/crossdomain.xml", 16) == 0) {
+                               http_crossdomain(c);
+                               return;
+                       }
+                       slog(w->s, WEBDIS_DEBUG, c->path, c->path_sz);
+                       ret = cmd_run(c->w, c, 1+c->path, c->path_sz-1, NULL, 0);
+                       break;
+
+               case HTTP_POST:
+                       slog(w->s, WEBDIS_DEBUG, c->path, c->path_sz);
+                       ret = cmd_run(c->w, c, c->body, c->body_sz, NULL, 0);
+                       break;
+
+               case HTTP_PUT:
+                       slog(w->s, WEBDIS_DEBUG, c->path, c->path_sz);
+                       ret = cmd_run(c->w, c, 1+c->path, c->path_sz-1,
+                                       c->body, c->body_sz);
+                       break;
+
+               case HTTP_OPTIONS:
+                       http_send_options(c);
+
+               default:
+                       slog(w->s, WEBDIS_DEBUG, "405", 3);
+                       http_send_error(c, 405, "Method Not Allowed");
+                       return;
+       }
+
+       switch(ret) {
+               case CMD_ACL_FAIL:
+               case CMD_PARAM_ERROR:
+                       slog(w->s, WEBDIS_DEBUG, "403", 3);
+                       http_send_error(c, 403, "Forbidden");
+                       break;
+
+               case CMD_REDIS_UNAVAIL:
+                       slog(w->s, WEBDIS_DEBUG, "503", 3);
+                       http_send_error(c, 503, "Service Unavailable");
+                       break;
+               default:
+                       break;
+       }
+
+}
+
diff --git a/worker.h b/worker.h
new file mode 100644 (file)
index 0000000..b4e9cde
--- /dev/null
+++ b/worker.h
@@ -0,0 +1,41 @@
+#ifndef WORKER_H
+#define WORKER_H
+
+#include <pthread.h>
+
+struct http_client;
+struct pool;
+
+struct worker {
+
+       /* self */
+       pthread_t thread;
+       struct event_base *base;
+
+       /* connection dispatcher */
+       struct server *s;
+       int link[2];
+
+       /* Redis connection pool */
+       struct pool *pool;
+};
+
+struct worker *
+worker_new(struct server *s);
+
+void
+worker_start(struct worker *w);
+
+void
+worker_add_client(struct worker *w, struct http_client *c);
+
+void
+worker_monitor_input(struct http_client *c);
+
+void
+worker_can_read(int fd, short event, void *p);
+
+void
+worker_process_client(struct http_client *c);
+
+#endif