pax_global_header00006660000000000000000000000064122422202070014503gustar00rootroot0000000000000052 comment=4915fef97dce09e072f8309183428adb31a1cdfd webdis-0.1.1/000077500000000000000000000000001224222020700127575ustar00rootroot00000000000000webdis-0.1.1/.gitignore000066400000000000000000000000561224222020700147500ustar00rootroot00000000000000*.log *.swp *.o webdis websocket *.png pubsub webdis-0.1.1/.travis.yml000066400000000000000000000002541224222020700150710ustar00rootroot00000000000000script: "make clean all" language: c compiler: - gcc - clang before_install: - sudo apt-get update - sudo apt-get install libevent-dev install: "sudo make install" webdis-0.1.1/COPYING000066400000000000000000000024651224222020700140210ustar00rootroot00000000000000Copyright (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. webdis-0.1.1/Makefile000066400000000000000000000031741224222020700144240ustar00rootroot00000000000000OUT=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 webdis-0.1.1/README.markdown000066400000000000000000000256171224222020700154730ustar00rootroot00000000000000# 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`.
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"}

# 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:
{
	"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"]
}
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:**
// 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"]})
# RAW output This is the raw output of Redis; enable it with the `.raw` suffix.

// 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'
# 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.
curl -v "http://127.0.0.1:7379/GET/hello.html"
[...]
< HTTP/1.1 200 OK
< Content-Type: text/html
< Date: Mon, 03 Jan 2011 20:43:36 GMT
< Content-Length: 137
<
<!DOCTYPE html>
<html>
[...]
</html>

curl -v "http://127.0.0.1:7379/GET/hello.txt"
[...]
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Date: Mon, 03 Jan 2011 20:43:36 GMT
< Content-Length: 137
[...]

curl -v "http://127.0.0.1:7379/GET/big-file?type=application/pdf"
[...]
< HTTP/1.1 200 OK
< Content-Type: application/pdf
< Date: Mon, 03 Jan 2011 20:45:12 GMT
[...]
# 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**:
$ 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
[...]
> PUT /SET/logo HTTP/1.1
> 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
> Host: 127.0.0.1:7379
> Accept: */*
> Content-Length: 16744
> Expect: 100-continue
>
< HTTP/1.1 100 Continue
< HTTP/1.1 200 OK
< Content-Type: application/json
< ETag: "0db1124cf79ffeb80aff6d199d5822f8"
< Date: Sun, 09 Jan 2011 16:48:19 GMT
< Content-Length: 19
<
{"SET":[true,"OK"]}

$ curl -vs http://127.0.0.1:7379/GET/logo.png -o out.png
> GET /GET/logo.png HTTP/1.1
> 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
> Host: 127.0.0.1:7379
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: image/png
< ETag: "1991df597267d70bf9066a7d11969da0"
< Date: Sun, 09 Jan 2011 16:50:51 GMT
< Content-Length: 16744

$ md5sum redis-logo.png out.png
1991df597267d70bf9066a7d11969da0  redis-logo.png
1991df597267d70bf9066a7d11969da0  out.png
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**:
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();
This produces the following output:
JSON socket connected!
JSON received: {"SET":[true,"OK"]}
JSON received: {"GET":"world"}
# 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**:
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);
    }
};
Publish messages to redis to see output similar to the following:
{"SUBSCRIBE":["subscribe","hello",1]}
{"SUBSCRIBE":["message","hello","some message"]}
{"SUBSCRIBE":["message","hello","some other message"]} 
webdis-0.1.1/acl.c000066400000000000000000000042001224222020700136560ustar00rootroot00000000000000#include "acl.h" #include "cmd.h" #include "conf.h" #include "http.h" #include "client.h" #include #include #include #include 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; } webdis-0.1.1/acl.h000066400000000000000000000011461224222020700136710ustar00rootroot00000000000000#ifndef ACL_H #define ACL_H #include 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 webdis-0.1.1/b64/000077500000000000000000000000001224222020700133525ustar00rootroot00000000000000webdis-0.1.1/b64/cencode.c000066400000000000000000000047711224222020700151270ustar00rootroot00000000000000/* 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; } webdis-0.1.1/b64/cencode.h000066400000000000000000000013231224222020700151220ustar00rootroot00000000000000/* 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 */ webdis-0.1.1/client.c000066400000000000000000000203761224222020700144110ustar00rootroot00000000000000#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 #include #include #include #include #include #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; } webdis-0.1.1/client.h000066400000000000000000000031311224222020700144040ustar00rootroot00000000000000#ifndef CLIENT_H #define CLIENT_H #include #include #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 webdis-0.1.1/cmd.c000066400000000000000000000216651224222020700137000ustar00rootroot00000000000000#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 #include #include #include #include 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; } webdis-0.1.1/cmd.h000066400000000000000000000027641224222020700137040ustar00rootroot00000000000000#ifndef CMD_H #define CMD_H #include #include #include #include #include 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 webdis-0.1.1/conf.c000066400000000000000000000157411224222020700140600ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #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); } webdis-0.1.1/conf.h000066400000000000000000000015001224222020700140510ustar00rootroot00000000000000#ifndef CONF_H #define CONF_H #include #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 */ webdis-0.1.1/formats/000077500000000000000000000000001224222020700144325ustar00rootroot00000000000000webdis-0.1.1/formats/common.c000066400000000000000000000062641224222020700160760ustar00rootroot00000000000000#include "common.h" #include "cmd.h" #include "http.h" #include "client.h" #include "websocket.h" #include "md5/md5.h" #include #include /* 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; } webdis-0.1.1/formats/common.h000066400000000000000000000004521224222020700160740ustar00rootroot00000000000000#ifndef FORMATS_COMMON_H #define FORMATS_COMMON_H #include 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 webdis-0.1.1/formats/custom-type.c000066400000000000000000000051151224222020700170710ustar00rootroot00000000000000#include "custom-type.h" #include "cmd.h" #include "common.h" #include "http.h" #include #include #include 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; } webdis-0.1.1/formats/custom-type.h000066400000000000000000000003021224222020700170670ustar00rootroot00000000000000#ifndef CUSTOM_TYPE_H #define CUSTOM_TYPE_H #include #include struct cmd; void custom_type_reply(redisAsyncContext *c, void *r, void *privdata); #endif webdis-0.1.1/formats/json.c000066400000000000000000000134121224222020700155500ustar00rootroot00000000000000#include "json.h" #include "common.h" #include "cmd.h" #include "http.h" #include "client.h" #include #include #include 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; } webdis-0.1.1/formats/json.h000066400000000000000000000005401224222020700155530ustar00rootroot00000000000000#ifndef JSON_H #define JSON_H #include #include #include 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 webdis-0.1.1/formats/msgpack.c000066400000000000000000000110111224222020700162150ustar00rootroot00000000000000#include "msgpack.h" #include "common.h" #include "cmd.h" #include "http.h" #include "client.h" #include #include #include 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); } webdis-0.1.1/formats/msgpack.h000066400000000000000000000002761224222020700162350ustar00rootroot00000000000000#ifndef MSGPACK_H #define MSGPACK_H #include #include #include void msgpack_reply(redisAsyncContext *c, void *r, void *privdata); #endif webdis-0.1.1/formats/raw.c000066400000000000000000000074561224222020700154030ustar00rootroot00000000000000#include "raw.h" #include "common.h" #include "http.h" #include "client.h" #include "cmd.h" #include #include #include 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; } } webdis-0.1.1/formats/raw.h000066400000000000000000000004151224222020700153740ustar00rootroot00000000000000#ifndef RAW_H #define RAW_H #include #include 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 webdis-0.1.1/hiredis/000077500000000000000000000000001224222020700144065ustar00rootroot00000000000000webdis-0.1.1/hiredis/.gitignore000066400000000000000000000000711224222020700163740ustar00rootroot00000000000000/hiredis-test /hiredis-example* /*.o /*.so /*.dylib /*.a webdis-0.1.1/hiredis/COPYING000066400000000000000000000030641224222020700154440ustar00rootroot00000000000000Copyright (c) 2009-2011, Salvatore Sanfilippo Copyright (c) 2010-2011, Pieter Noordhuis 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. webdis-0.1.1/hiredis/Makefile000066400000000000000000000105751224222020700160560ustar00rootroot00000000000000# Hiredis Makefile # Copyright (C) 2010-2011 Salvatore Sanfilippo # Copyright (C) 2010-2011 Pieter Noordhuis # 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. /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 webdis-0.1.1/hiredis/README.md000066400000000000000000000370611224222020700156740ustar00rootroot00000000000000# 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. webdis-0.1.1/hiredis/TODO000066400000000000000000000000701224222020700150730ustar00rootroot00000000000000- add redisCommandVector() - add support for pipelining webdis-0.1.1/hiredis/adapters/000077500000000000000000000000001224222020700162115ustar00rootroot00000000000000webdis-0.1.1/hiredis/adapters/ae.h000066400000000000000000000050551224222020700167540ustar00rootroot00000000000000#ifndef __HIREDIS_AE_H__ #define __HIREDIS_AE_H__ #include #include #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 webdis-0.1.1/hiredis/adapters/libev.h000066400000000000000000000056351224222020700174740ustar00rootroot00000000000000#ifndef __HIREDIS_LIBEV_H__ #define __HIREDIS_LIBEV_H__ #include #include #include #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 webdis-0.1.1/hiredis/adapters/libevent.h000066400000000000000000000044761224222020700202050ustar00rootroot00000000000000#ifndef __HIREDIS_LIBEVENT_H__ #define __HIREDIS_LIBEVENT_H__ #include #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 webdis-0.1.1/hiredis/async.c000066400000000000000000000525341224222020700157000ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2011, Pieter Noordhuis * * 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 #include #include #include #include #include #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; } webdis-0.1.1/hiredis/async.h000066400000000000000000000115001224222020700156710ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2011, Pieter Noordhuis * * 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 webdis-0.1.1/hiredis/dict.c000066400000000000000000000244651224222020700155100ustar00rootroot00000000000000/* 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 * 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 #include #include #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; } webdis-0.1.1/hiredis/dict.h000066400000000000000000000111231224222020700155000ustar00rootroot00000000000000/* 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 * 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 */ webdis-0.1.1/hiredis/example-ae.c000066400000000000000000000027541224222020700166000ustar00rootroot00000000000000#include #include #include #include #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; } webdis-0.1.1/hiredis/example-libev.c000066400000000000000000000025741224222020700173140ustar00rootroot00000000000000#include #include #include #include #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; } webdis-0.1.1/hiredis/example-libevent.c000066400000000000000000000026561224222020700200240ustar00rootroot00000000000000#include #include #include #include #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; } webdis-0.1.1/hiredis/example.c000066400000000000000000000037631224222020700162160ustar00rootroot00000000000000#include #include #include #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; } webdis-0.1.1/hiredis/fmacros.h000066400000000000000000000004001224222020700162030ustar00rootroot00000000000000#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 webdis-0.1.1/hiredis/hiredis.c000066400000000000000000001103051224222020700162010ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2011, Pieter Noordhuis * * 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 #include #include #include #include #include #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); } webdis-0.1.1/hiredis/hiredis.h000066400000000000000000000213271224222020700162130ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2011, Pieter Noordhuis * * 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 /* for size_t */ #include /* for va_list */ #include /* 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 webdis-0.1.1/hiredis/net.c000066400000000000000000000213031224222020700153370ustar00rootroot00000000000000/* Extracted from anet.c to work properly with Hiredis error reporting. * * Copyright (c) 2006-2011, Salvatore Sanfilippo * Copyright (c) 2010-2011, Pieter Noordhuis * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; } webdis-0.1.1/hiredis/net.h000066400000000000000000000042041224222020700153450ustar00rootroot00000000000000/* Extracted from anet.c to work properly with Hiredis error reporting. * * Copyright (c) 2006-2011, Salvatore Sanfilippo * Copyright (c) 2010-2011, Pieter Noordhuis * * 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 webdis-0.1.1/hiredis/sds.c000066400000000000000000000416351224222020700153540ustar00rootroot00000000000000/* SDSLib, A C dynamic strings library * * Copyright (c) 2006-2010, Salvatore Sanfilippo * 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 #include #include #include #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 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 webdis-0.1.1/hiredis/sds.h000066400000000000000000000060401224222020700153500ustar00rootroot00000000000000/* SDSLib, A C dynamic strings library * * Copyright (c) 2006-2010, Salvatore Sanfilippo * 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 #include 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 webdis-0.1.1/hiredis/test.c000066400000000000000000000552101224222020700155340ustar00rootroot00000000000000#include "fmacros.h" #include #include #include #include #include #include #include #include #include #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; } webdis-0.1.1/hiredis/util.h000066400000000000000000000034031224222020700155340ustar00rootroot00000000000000/* * Copyright (c) 2009-2010, Salvatore Sanfilippo * 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 /* Abort on out of memory */ static void redisOOM(void) { fprintf(stderr,"Out of memory in hiredis"); exit(1); } #endif webdis-0.1.1/http-parser/000077500000000000000000000000001224222020700152305ustar00rootroot00000000000000webdis-0.1.1/http-parser/.gitignore000066400000000000000000000000251224222020700172150ustar00rootroot00000000000000tags *.o test test_g webdis-0.1.1/http-parser/CONTRIBUTIONS000066400000000000000000000002701224222020700172140ustar00rootroot00000000000000Contributors must agree to the Contributor License Agreement before patches can be accepted. http://spreadsheets2.google.com/viewform?hl=en&formkey=dDJXOGUwbzlYaWM4cHN1MERwQS1CSnc6MQ webdis-0.1.1/http-parser/LICENSE-MIT000066400000000000000000000020631224222020700166650ustar00rootroot00000000000000Copyright 2009,2010 Ryan Dahl 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. webdis-0.1.1/http-parser/README.md000066400000000000000000000161221224222020700165110ustar00rootroot00000000000000HTTP 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 webdis-0.1.1/http-parser/http_parser.c000066400000000000000000001274021224222020700177350ustar00rootroot00000000000000/* Copyright 2009,2010 Ryan Dahl * * 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 #include #include #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* * 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; } webdis-0.1.1/http-parser/http_parser.h000066400000000000000000000117731224222020700177450ustar00rootroot00000000000000/* Copyright 2009,2010 Ryan Dahl * * 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 #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 #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 webdis-0.1.1/http.c000066400000000000000000000172501224222020700141070ustar00rootroot00000000000000#include "http.h" #include "server.h" #include "worker.h" #include "client.h" #include #include #include #include /* 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[] = "\n" "\n" "\n" "\n" "\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); } webdis-0.1.1/http.h000066400000000000000000000022751224222020700141150ustar00rootroot00000000000000#ifndef HTTP_H #define HTTP_H #include #include 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 webdis-0.1.1/jansson/000077500000000000000000000000001224222020700144325ustar00rootroot00000000000000webdis-0.1.1/jansson/.gitignore000066400000000000000000000003671224222020700164300ustar00rootroot00000000000000*~ *.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 webdis-0.1.1/jansson/CHANGES000066400000000000000000000114451224222020700154320ustar00rootroot00000000000000Version 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 webdis-0.1.1/jansson/LICENSE000066400000000000000000000020721224222020700154400ustar00rootroot00000000000000Copyright (c) 2009, 2010 Petri Lehtinen 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. webdis-0.1.1/jansson/Makefile.am000066400000000000000000000002551224222020700164700ustar00rootroot00000000000000EXTRA_DIST = CHANGES LICENSE README.rst SUBDIRS = doc src test check-doc: $(MAKE) SPHINXOPTS_EXTRA=-W html pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = jansson.pc webdis-0.1.1/jansson/README.rst000066400000000000000000000027131224222020700161240ustar00rootroot00000000000000Jansson 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/ webdis-0.1.1/jansson/configure.ac000066400000000000000000000017361224222020700167270ustar00rootroot00000000000000AC_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 webdis-0.1.1/jansson/jansson.pc.in000066400000000000000000000003641224222020700170410ustar00rootroot00000000000000prefix=@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} webdis-0.1.1/jansson/src/000077500000000000000000000000001224222020700152215ustar00rootroot00000000000000webdis-0.1.1/jansson/src/Makefile.am000066400000000000000000000006411224222020700172560ustar00rootroot00000000000000include_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 webdis-0.1.1/jansson/src/dump.c000066400000000000000000000302251224222020700163340ustar00rootroot00000000000000/* * Copyright (c) 2009, 2010 Petri Lehtinen * * 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 #include #include #include #include #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; } webdis-0.1.1/jansson/src/error.c000066400000000000000000000014121224222020700165140ustar00rootroot00000000000000#include #include #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); } webdis-0.1.1/jansson/src/hashtable.c000066400000000000000000000213231224222020700173210ustar00rootroot00000000000000/* * Copyright (c) 2009, 2010 Petri Lehtinen * * 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 #include /* 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; } webdis-0.1.1/jansson/src/hashtable.h000066400000000000000000000135371224222020700173360ustar00rootroot00000000000000/* * Copyright (c) 2009, 2010 Petri Lehtinen * * 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 webdis-0.1.1/jansson/src/jansson.h000066400000000000000000000146701224222020700170550ustar00rootroot00000000000000/* * Copyright (c) 2009, 2010 Petri Lehtinen * * 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 #include /* for size_t */ #include #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 webdis-0.1.1/jansson/src/jansson_config.h000066400000000000000000000020021224222020700203640ustar00rootroot00000000000000/* * Copyright (c) 2010 Petri Lehtinen * * 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 webdis-0.1.1/jansson/src/jansson_config.h.in000066400000000000000000000020351224222020700207770ustar00rootroot00000000000000/* * Copyright (c) 2010 Petri Lehtinen * * 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 webdis-0.1.1/jansson/src/jansson_config.h.win32000066400000000000000000000017741224222020700213440ustar00rootroot00000000000000/* * Copyright (c) 2010 Petri Lehtinen * * 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 webdis-0.1.1/jansson/src/jansson_private.h000066400000000000000000000032011224222020700205730ustar00rootroot00000000000000/* * Copyright (c) 2009, 2010 Petri Lehtinen * * 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 #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 webdis-0.1.1/jansson/src/load.c000066400000000000000000000501461224222020700163120ustar00rootroot00000000000000/* * Copyright (c) 2009, 2010 Petri Lehtinen * * 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 #include #include #include #include #include #include #include #include #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, ""); 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 = ""; else source = ""; 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; } webdis-0.1.1/jansson/src/strbuffer.c000066400000000000000000000041351224222020700173720ustar00rootroot00000000000000/* * Copyright (c) 2009, 2010 Petri Lehtinen * * 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 #include #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'; } webdis-0.1.1/jansson/src/strbuffer.h000066400000000000000000000015421224222020700173760ustar00rootroot00000000000000/* * Copyright (c) 2009, 2010 Petri Lehtinen * * 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 webdis-0.1.1/jansson/src/utf.c000066400000000000000000000076661224222020700162020ustar00rootroot00000000000000/* * Copyright (c) 2009, 2010 Petri Lehtinen * * Jansson is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #include #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; } webdis-0.1.1/jansson/src/utf.h000066400000000000000000000017621224222020700161760ustar00rootroot00000000000000/* * Copyright (c) 2009, 2010 Petri Lehtinen * * 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 #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 #endif /* HAVE_INTTYPES_H */ #else /* !HAVE_CONFIG_H */ #ifdef _WIN32 typedef int int32_t; #else /* !_WIN32 */ /* Assume a standard environment */ #include #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 webdis-0.1.1/jansson/src/value.c000066400000000000000000000457141224222020700165140ustar00rootroot00000000000000/* * Copyright (c) 2009, 2010 Petri Lehtinen * * 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 #include #include #include #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; } webdis-0.1.1/jansson/src/variadic.c000066400000000000000000000403171224222020700171540ustar00rootroot00000000000000/* * Copyright (c) 2009, 2010 Petri Lehtinen * Copyright (c) 2010 Graeme Smecher * * Jansson is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #include #include #include #include #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 */ webdis-0.1.1/md5/000077500000000000000000000000001224222020700134445ustar00rootroot00000000000000webdis-0.1.1/md5/md5.c000066400000000000000000000302221224222020700142740ustar00rootroot00000000000000/* 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 . 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 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 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 #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)); } webdis-0.1.1/md5/md5.h000066400000000000000000000063441224222020700143110ustar00rootroot00000000000000/* 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 . 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 . 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 */ webdis-0.1.1/pool.c000066400000000000000000000062031224222020700140750ustar00rootroot00000000000000#include "pool.h" #include "worker.h" #include "conf.h" #include "server.h" #include #include #include #include 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; } webdis-0.1.1/pool.h000066400000000000000000000006761224222020700141120ustar00rootroot00000000000000#ifndef POOL_H #define POOL_H #include 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 webdis-0.1.1/server.c000066400000000000000000000114631224222020700144360ustar00rootroot00000000000000#include "server.h" #include "worker.h" #include "client.h" #include "conf.h" #include "version.h" #include #include #include #include #include #include #include #include #include #include #include #include /** * 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; } webdis-0.1.1/server.h000066400000000000000000000006721224222020700144430ustar00rootroot00000000000000#ifndef SERVER_H #define SERVER_H #include #include #include 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 webdis-0.1.1/sha1/000077500000000000000000000000001224222020700136135ustar00rootroot00000000000000webdis-0.1.1/sha1/sha1.c000066400000000000000000000242161224222020700146200ustar00rootroot00000000000000/* * sha1.c * * Copyright (C) 1998, 2009 * Paul E. Jones * 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); } webdis-0.1.1/sha1/sha1.h000066400000000000000000000030461224222020700146230ustar00rootroot00000000000000/* * sha1.h * * Copyright (C) 1998, 2009 * Paul E. Jones * 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 webdis-0.1.1/slog.c000066400000000000000000000030331224222020700140660ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #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; } webdis-0.1.1/slog.h000066400000000000000000000004511224222020700140740ustar00rootroot00000000000000#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 webdis-0.1.1/tests/000077500000000000000000000000001224222020700141215ustar00rootroot00000000000000webdis-0.1.1/tests/Makefile000066400000000000000000000004161224222020700155620ustar00rootroot00000000000000OUT=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) webdis-0.1.1/tests/README.tests000066400000000000000000000004651224222020700161470ustar00rootroot00000000000000This 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. webdis-0.1.1/tests/basic.py000077500000000000000000000153421224222020700155640ustar00rootroot00000000000000#!/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() webdis-0.1.1/tests/bench.sh000077500000000000000000000021601224222020700155360ustar00rootroot00000000000000#!/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 webdis-0.1.1/tests/limits.py000077500000000000000000000041041224222020700157760ustar00rootroot00000000000000#!/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() webdis-0.1.1/tests/pubsub.c000066400000000000000000000157141224222020700155750ustar00rootroot00000000000000#include #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include /* * 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; } webdis-0.1.1/tests/websocket.c000066400000000000000000000167621224222020700162670ustar00rootroot00000000000000/* http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 */ #include #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include 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; } } webdis-0.1.1/tests/websocket.html000066400000000000000000000054541224222020700170050ustar00rootroot00000000000000 WebSocket example
Webdis with HTML5 WebSockets

JSON

Connecting...

Raw

Connecting...
webdis-0.1.1/version.h000066400000000000000000000001721224222020700146150ustar00rootroot00000000000000#ifndef VERSION_H #define VERSION_H #ifndef WEBDIS_VERSION #define WEBDIS_VERSION "0.1.1" #endif #endif /* VERSION_H */ webdis-0.1.1/webdis.c000066400000000000000000000003501224222020700143760ustar00rootroot00000000000000#include "server.h" #include 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; } webdis-0.1.1/webdis.json000066400000000000000000000006131224222020700151270ustar00rootroot00000000000000{ "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" } webdis-0.1.1/webdis.prod.json000066400000000000000000000005521224222020700160740ustar00rootroot00000000000000{ "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" } webdis-0.1.1/websocket.c000066400000000000000000000226431224222020700151200ustar00rootroot00000000000000#include "sha1/sha1.h" #include #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 #include #include #include #include #include /** * 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; } webdis-0.1.1/websocket.h000066400000000000000000000006351224222020700151220ustar00rootroot00000000000000#ifndef WEBSOCKET_H #define WEBSOCKET_H #include #include 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 webdis-0.1.1/worker.c000066400000000000000000000113101224222020700144300ustar00rootroot00000000000000#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 #include #include #include #include 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; } } webdis-0.1.1/worker.h000066400000000000000000000011241224222020700144370ustar00rootroot00000000000000#ifndef WORKER_H #define WORKER_H #include 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