Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 380 lines (314 sloc) 9.141 kb
2507fce @nicolasff Added JSON output.
authored
1 #include "cmd.h"
de5c283 @nicolasff Added IP range restriction.
authored
2 #include "conf.h"
a7bf6f7 @nicolasff ACL refactoring.
authored
3 #include "acl.h"
7cfb80d @nicolasff Add client.{c,h}. Needs a lot more refactoring.
authored
4 #include "client.h"
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
5 #include "pool.h"
6 #include "worker.h"
7 #include "http.h"
8 #include "server.h"
f616c29 @nicolasff Free Redis context for custom connections
authored
9 #include "slog.h"
e2f2b36 @nicolasff Added RAW output.
authored
10
11 #include "formats/json.h"
12 #include "formats/raw.h"
525c63d @nicolasff Added more msgpack support
authored
13 #ifdef MSGPACK
14 #include "formats/msgpack.h"
15 #endif
1ad059d @nicolasff Started adding support for a custom content-type in a second key.
authored
16 #include "formats/custom-type.h"
2507fce @nicolasff Added JSON output.
authored
17
18 #include <stdlib.h>
19 #include <string.h>
20 #include <hiredis/hiredis.h>
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
21 #include <hiredis/async.h>
4448b0f @nicolasff Proper decoding of URL parameters.
authored
22 #include <ctype.h>
2507fce @nicolasff Added JSON output.
authored
23
24 struct cmd *
a298d3c @nicolasff Better client/cmd relationship.
authored
25 cmd_new(int count) {
2507fce @nicolasff Added JSON output.
authored
26
27 struct cmd *c = calloc(1, sizeof(struct cmd));
28
29 c->count = count;
30
1abb414 @nicolasff Final touch to content-type feature.
authored
31 c->argv = calloc(count, sizeof(char*));
32 c->argv_len = calloc(count, sizeof(size_t));
2507fce @nicolasff Added JSON output.
authored
33
34 return c;
35 }
36
37
38 void
39 cmd_free(struct cmd *c) {
40
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
41 int i;
a298d3c @nicolasff Better client/cmd relationship.
authored
42 if(!c) return;
43
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
44 for(i = 0; i < c->count; ++i) {
45 free((char*)c->argv[i]);
46 }
2507fce @nicolasff Added JSON output.
authored
47
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
48 free(c->jsonp);
006186f @nicolasff Added separator param for custom list responses.
authored
49 free(c->separator);
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
50 free(c->if_none_match);
102f9fc @nicolasff Started adding Accept support.
authored
51 if(c->mime_free) free(c->mime);
52
f616c29 @nicolasff Free Redis context for custom connections
authored
53 if (c->ac && /* we have a connection */
54 (c->database != c->w->s->cfg->database /* custom DB */
55 || cmd_is_subscribe(c))) {
56 pool_free_context(c->ac);
57 }
39cdc9b @nicolasff Remove access to free'd memory in cmd_free
authored
58 free(c->argv);
59 free(c->argv_len);
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
60
2507fce @nicolasff Added JSON output.
authored
61 free(c);
62 }
63
4448b0f @nicolasff Proper decoding of URL parameters.
authored
64 /* taken from libevent */
65 static char *
66 decode_uri(const char *uri, size_t length, size_t *out_len, int always_decode_plus) {
67 char c;
68 size_t i, j;
69 int in_query = always_decode_plus;
70
71 char *ret = malloc(length);
72
73 for (i = j = 0; i < length; i++) {
74 c = uri[i];
75 if (c == '?') {
76 in_query = 1;
77 } else if (c == '+' && in_query) {
78 c = ' ';
79 } else if (c == '%' && isxdigit((unsigned char)uri[i+1]) &&
80 isxdigit((unsigned char)uri[i+2])) {
81 char tmp[] = { uri[i+1], uri[i+2], '\0' };
82 c = (char)strtol(tmp, NULL, 16);
83 i += 2;
84 }
85 ret[j++] = c;
86 }
87 *out_len = (size_t)j;
88
89 return ret;
90 }
91
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
92 /* setup headers */
239c900 @nicolasff HTTP version in reply.
authored
93 void
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
94 cmd_setup(struct cmd *cmd, struct http_client *client) {
95
96 int i;
97 cmd->keep_alive = client->keep_alive;
0f7d057 @nicolasff Started making writes async.
authored
98 cmd->w = client->w; /* keep track of the worker */
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
99
100 for(i = 0; i < client->header_count; ++i) {
101 if(strcasecmp(client->headers[i].key, "If-None-Match") == 0) {
102 cmd->if_none_match = calloc(1+client->headers[i].val_sz, 1);
103 memcpy(cmd->if_none_match, client->headers[i].val,
104 client->headers[i].val_sz);
105 } else if(strcasecmp(client->headers[i].key, "Connection") == 0 &&
106 strcasecmp(client->headers[i].val, "Keep-Alive") == 0) {
107 cmd->keep_alive = 1;
108 }
109 }
110
111 if(client->type) { /* transfer pointer ownership */
112 cmd->mime = client->type;
113 cmd->mime_free = 1;
114 client->type = NULL;
115 }
116
117 if(client->jsonp) { /* transfer pointer ownership */
118 cmd->jsonp = client->jsonp;
119 client->jsonp = NULL;
120 }
239c900 @nicolasff HTTP version in reply.
authored
121
006186f @nicolasff Added separator param for custom list responses.
authored
122 if(client->separator) { /* transfer pointer ownership */
123 cmd->separator = client->separator;
124 client->separator = NULL;
125 }
126
683cd77 @nicolasff Added Content-Disposition.
authored
127 if(client->filename) { /* transfer pointer ownership */
128 cmd->filename = client->filename;
129 client->filename = NULL;
130 }
131
239c900 @nicolasff HTTP version in reply.
authored
132 cmd->fd = client->fd;
133 cmd->http_version = client->http_version;
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
134 }
135
4448b0f @nicolasff Proper decoding of URL parameters.
authored
136
bf2f584 @nicolasff Fixed 503 responses when Redis is down.
authored
137 cmd_response_t
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
138 cmd_run(struct worker *w, struct http_client *client,
89bb00f @nicolasff Start sending HTTP replies.
authored
139 const char *uri, size_t uri_len,
140 const char *body, size_t body_len) {
2507fce @nicolasff Added JSON output.
authored
141
89bb00f @nicolasff Start sending HTTP replies.
authored
142 char *qmark = memchr(uri, '?', uri_len);
a3aa1a9 @nicolasff Bugfix in RAW mode, more code doc.
authored
143 char *slash;
3db6e26 @nicolasff Detect database number as first command parameter.
authored
144 const char *p, *cmd_name = uri;
2507fce @nicolasff Added JSON output.
authored
145 int cmd_len;
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
146 int param_count = 0, cur_param = 1;
2507fce @nicolasff Added JSON output.
authored
147
148 struct cmd *cmd;
1ea7cd0 @nicolasff Special GET formatter for key + content-type key.
authored
149 formatting_fun f_format;
2507fce @nicolasff Added JSON output.
authored
150
151 /* count arguments */
9d2beac @nicolasff Added JSONP.
authored
152 if(qmark) {
153 uri_len = qmark - uri;
154 }
2507fce @nicolasff Added JSON output.
authored
155 for(p = uri; p && p < uri + uri_len; param_count++) {
89bb00f @nicolasff Start sending HTTP replies.
authored
156 p = memchr(p+1, '/', uri_len - (p+1-uri));
2507fce @nicolasff Added JSON output.
authored
157 }
158
a3ee623 @nicolasff File uploads using HTTP PUT.
authored
159 if(body && body_len) { /* PUT request */
160 param_count++;
161 }
70d2e07 @nicolasff Fix memory access on empty commands.
authored
162 if(param_count == 0) {
bf2f584 @nicolasff Fixed 503 responses when Redis is down.
authored
163 return CMD_PARAM_ERROR;
70d2e07 @nicolasff Fix memory access on empty commands.
authored
164 }
a3ee623 @nicolasff File uploads using HTTP PUT.
authored
165
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
166 cmd = cmd_new(param_count);
167 cmd->fd = client->fd;
540b1a9 @nicolasff Create new connection for new separate DBs.
authored
168 cmd->database = w->s->cfg->database;
2507fce @nicolasff Added JSON output.
authored
169
469e516 @nicolasff Removed content-type in another key, added suffixes instead.
authored
170 /* get output formatting function */
bbec5c6 @nicolasff Bugfix, restored forced content-type.
authored
171 uri_len = cmd_select_format(client, cmd, uri, uri_len, &f_format);
469e516 @nicolasff Removed content-type in another key, added suffixes instead.
authored
172
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
173 /* add HTTP info */
174 cmd_setup(cmd, client);
175
469e516 @nicolasff Removed content-type in another key, added suffixes instead.
authored
176 /* check if we only have one command or more. */
a3aa1a9 @nicolasff Bugfix in RAW mode, more code doc.
authored
177 slash = memchr(uri, '/', uri_len);
2507fce @nicolasff Added JSON output.
authored
178 if(slash) {
3db6e26 @nicolasff Detect database number as first command parameter.
authored
179
180 /* detect DB number by checking if first arg is only numbers */
181 int has_db = 1;
540b1a9 @nicolasff Create new connection for new separate DBs.
authored
182 int db_num = 0;
3db6e26 @nicolasff Detect database number as first command parameter.
authored
183 for(p = uri; p < slash; ++p) {
184 if(*p < '0' || *p > '9') {
185 has_db = 0;
186 break;
187 }
188 db_num = db_num * 10 + (*p - '0');
189 }
190
191 /* shift to next arg if a db was set up */
192 if(has_db) {
193 char *next;
540b1a9 @nicolasff Create new connection for new separate DBs.
authored
194 cmd->database = db_num;
195 cmd->count--; /* overcounted earlier */
3db6e26 @nicolasff Detect database number as first command parameter.
authored
196 cmd_name = slash + 1;
197
198 if((next = memchr(cmd_name, '/', uri_len - (slash - uri)))) {
540b1a9 @nicolasff Create new connection for new separate DBs.
authored
199 cmd_len = next - cmd_name;
3db6e26 @nicolasff Detect database number as first command parameter.
authored
200 } else {
201 cmd_len = uri_len - (slash - uri + 1);
202 }
203 } else {
204 cmd_len = slash - uri;
205 }
2507fce @nicolasff Added JSON output.
authored
206 } else {
207 cmd_len = uri_len;
208 }
209
210 /* there is always a first parameter, it's the command name */
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
211 cmd->argv[0] = malloc(cmd_len);
3db6e26 @nicolasff Detect database number as first command parameter.
authored
212 memcpy(cmd->argv[0], cmd_name, cmd_len);
2507fce @nicolasff Added JSON output.
authored
213 cmd->argv_len[0] = cmd_len;
214
9ffc519 @nicolasff Fix ACLs
authored
215 /* check that the client is able to run this command */
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
216 if(!acl_allow_command(cmd, w->s->cfg, client)) {
513982d @nicolasff Added asynchronous reconnect, fixed memory leak.
authored
217 cmd_free(cmd);
bf2f584 @nicolasff Fixed 503 responses when Redis is down.
authored
218 return CMD_ACL_FAIL;
de5c283 @nicolasff Added IP range restriction.
authored
219 }
220
4676cfe @nicolasff Refactoring.
authored
221 if(cmd_is_subscribe(cmd)) {
bf2f584 @nicolasff Fixed 503 responses when Redis is down.
authored
222 /* create a new connection to Redis */
540b1a9 @nicolasff Create new connection for new separate DBs.
authored
223 cmd->ac = (redisAsyncContext*)pool_connect(w->pool, cmd->database, 0);
b01cb75 @nicolasff Disconnect broken SUBSCRIBE clients.
authored
224
225 /* register with the client, used upon disconnection */
226 client->pub_sub = cmd;
9b2a761 @nicolasff Fix for pub/sub clients.
authored
227 cmd->pub_sub_client = client;
540b1a9 @nicolasff Create new connection for new separate DBs.
authored
228 } else if(cmd->database != w->s->cfg->database) {
229 /* create a new connection to Redis for custom DBs */
230 cmd->ac = (redisAsyncContext*)pool_connect(w->pool, cmd->database, 0);
bf2f584 @nicolasff Fixed 503 responses when Redis is down.
authored
231 } else {
232 /* get a connection from the pool */
b01cb75 @nicolasff Disconnect broken SUBSCRIBE clients.
authored
233 cmd->ac = (redisAsyncContext*)pool_get_context(w->pool);
2c980a2 @nicolasff First try.
authored
234 }
235
a3aa1a9 @nicolasff Bugfix in RAW mode, more code doc.
authored
236 /* no args (e.g. INFO command) */
2507fce @nicolasff Added JSON output.
authored
237 if(!slash) {
b01cb75 @nicolasff Disconnect broken SUBSCRIBE clients.
authored
238 if(!cmd->ac) {
513982d @nicolasff Added asynchronous reconnect, fixed memory leak.
authored
239 cmd_free(cmd);
a91b7b1 @nicolasff Unavailability bugfix.
authored
240 return CMD_REDIS_UNAVAIL;
241 }
b01cb75 @nicolasff Disconnect broken SUBSCRIBE clients.
authored
242 redisAsyncCommandArgv(cmd->ac, f_format, cmd, 1,
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
243 (const char **)cmd->argv, cmd->argv_len);
bf2f584 @nicolasff Fixed 503 responses when Redis is down.
authored
244 return CMD_SENT;
2507fce @nicolasff Added JSON output.
authored
245 }
3db6e26 @nicolasff Detect database number as first command parameter.
authored
246 p = cmd_name + cmd_len + 1;
2507fce @nicolasff Added JSON output.
authored
247 while(p < uri + uri_len) {
248
249 const char *arg = p;
250 int arg_len;
d9d85ca @nicolasff Fix realloc bug.
authored
251 char *next = memchr(arg, '/', uri_len - (arg-uri));
5ca45a5 @nicolasff Working custom handler.
authored
252 if(!next || next > uri + uri_len) { /* last argument */
253 p = uri + uri_len;
254 arg_len = p - arg;
255 } else { /* found a slash */
2507fce @nicolasff Added JSON output.
authored
256 arg_len = next - arg;
257 p = next + 1;
258 }
259
260 /* record argument */
4448b0f @nicolasff Proper decoding of URL parameters.
authored
261 cmd->argv[cur_param] = decode_uri(arg, arg_len, &cmd->argv_len[cur_param], 1);
2507fce @nicolasff Added JSON output.
authored
262 cur_param++;
263 }
264
a3ee623 @nicolasff File uploads using HTTP PUT.
authored
265 if(body && body_len) { /* PUT request */
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
266 cmd->argv[cur_param] = malloc(body_len);
267 memcpy(cmd->argv[cur_param], body, body_len);
a3ee623 @nicolasff File uploads using HTTP PUT.
authored
268 cmd->argv_len[cur_param] = body_len;
269 }
270
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
271 /* send it off! */
b01cb75 @nicolasff Disconnect broken SUBSCRIBE clients.
authored
272 if(cmd->ac) {
273 cmd_send(cmd, f_format);
bf2f584 @nicolasff Fixed 503 responses when Redis is down.
authored
274 return CMD_SENT;
4448b0f @nicolasff Proper decoding of URL parameters.
authored
275 }
bf2f584 @nicolasff Fixed 503 responses when Redis is down.
authored
276 /* failed to find a suitable connection to Redis. */
513982d @nicolasff Added asynchronous reconnect, fixed memory leak.
authored
277 cmd_free(cmd);
9b2a761 @nicolasff Fix for pub/sub clients.
authored
278 client->pub_sub = NULL;
bf2f584 @nicolasff Fixed 503 responses when Redis is down.
authored
279 return CMD_REDIS_UNAVAIL;
2507fce @nicolasff Added JSON output.
authored
280 }
281
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
282 void
b01cb75 @nicolasff Disconnect broken SUBSCRIBE clients.
authored
283 cmd_send(struct cmd *cmd, formatting_fun f_format) {
284 redisAsyncCommandArgv(cmd->ac, f_format, cmd, cmd->count,
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
285 (const char **)cmd->argv, cmd->argv_len);
286 }
6e3c424 @nicolasff Cleanup.
authored
287
a3aa1a9 @nicolasff Bugfix in RAW mode, more code doc.
authored
288 /**
6e3c424 @nicolasff Cleanup.
authored
289 * Select Content-Type and processing function.
a3aa1a9 @nicolasff Bugfix in RAW mode, more code doc.
authored
290 */
469e516 @nicolasff Removed content-type in another key, added suffixes instead.
authored
291 int
bbec5c6 @nicolasff Bugfix, restored forced content-type.
authored
292 cmd_select_format(struct http_client *client, struct cmd *cmd,
293 const char *uri, size_t uri_len, formatting_fun *f_format) {
469e516 @nicolasff Removed content-type in another key, added suffixes instead.
authored
294
71bc9e3 @nicolasff Added a few content types, added support for ?type.
authored
295 const char *ext;
469e516 @nicolasff Removed content-type in another key, added suffixes instead.
authored
296 int ext_len = -1;
297 unsigned int i;
48b2658 @nicolasff Prevent truncate when the extension is unknown.
authored
298 int found = 0; /* did we match it to a predefined format? */
e2f2b36 @nicolasff Added RAW output.
authored
299
6e3c424 @nicolasff Cleanup.
authored
300 /* those are the available reply formats */
301 struct reply_format {
302 const char *s;
303 size_t sz;
304 formatting_fun f;
305 const char *ct;
306 };
469e516 @nicolasff Removed content-type in another key, added suffixes instead.
authored
307 struct reply_format funs[] = {
308 {.s = "json", .sz = 4, .f = json_reply, .ct = "application/json"},
309 {.s = "raw", .sz = 3, .f = raw_reply, .ct = "binary/octet-stream"},
4abc4fc @nicolasff Support for .bin as binary/octet-stream
authored
310
525c63d @nicolasff Added more msgpack support
authored
311 #ifdef MSGPACK
312 {.s = "msg", .sz = 3, .f = msgpack_reply, .ct = "application/x-msgpack"},
313 #endif
314
4abc4fc @nicolasff Support for .bin as binary/octet-stream
authored
315 {.s = "bin", .sz = 3, .f = custom_type_reply, .ct = "binary/octet-stream"},
469e516 @nicolasff Removed content-type in another key, added suffixes instead.
authored
316 {.s = "txt", .sz = 3, .f = custom_type_reply, .ct = "text/plain"},
317 {.s = "html", .sz = 4, .f = custom_type_reply, .ct = "text/html"},
71bc9e3 @nicolasff Added a few content types, added support for ?type.
authored
318 {.s = "xhtml", .sz = 5, .f = custom_type_reply, .ct = "application/xhtml+xml"},
319 {.s = "xml", .sz = 3, .f = custom_type_reply, .ct = "text/xml"},
320
469e516 @nicolasff Removed content-type in another key, added suffixes instead.
authored
321 {.s = "png", .sz = 3, .f = custom_type_reply, .ct = "image/png"},
71bc9e3 @nicolasff Added a few content types, added support for ?type.
authored
322 {.s = "jpg", .sz = 3, .f = custom_type_reply, .ct = "image/jpeg"},
323 {.s = "jpeg", .sz = 4, .f = custom_type_reply, .ct = "image/jpeg"},
1b6f36b @nicolasff Added Content-Type for JS and CSS files.
authored
324
7286cfe @nicolasff Send JS in JSON format
authored
325 {.s = "js", .sz = 2, .f = json_reply, .ct = "application/javascript"},
1b6f36b @nicolasff Added Content-Type for JS and CSS files.
authored
326 {.s = "css", .sz = 3, .f = custom_type_reply, .ct = "text/css"},
469e516 @nicolasff Removed content-type in another key, added suffixes instead.
authored
327 };
e2f2b36 @nicolasff Added RAW output.
authored
328
6e3c424 @nicolasff Cleanup.
authored
329 /* default */
1ea7cd0 @nicolasff Special GET formatter for key + content-type key.
authored
330 *f_format = json_reply;
469e516 @nicolasff Removed content-type in another key, added suffixes instead.
authored
331
332 /* find extension */
333 for(ext = uri + uri_len - 1; ext != uri && *ext != '/'; --ext) {
334 if(*ext == '.') {
335 ext++;
336 ext_len = uri + uri_len - ext;
e2f2b36 @nicolasff Added RAW output.
authored
337 break;
338 }
339 }
6e3c424 @nicolasff Cleanup.
authored
340 if(!ext_len) return uri_len; /* nothing found */
469e516 @nicolasff Removed content-type in another key, added suffixes instead.
authored
341
342 /* find function for the given extension */
343 for(i = 0; i < sizeof(funs)/sizeof(funs[0]); ++i) {
344 if(ext_len == (int)funs[i].sz && strncmp(ext, funs[i].s, ext_len) == 0) {
102f9fc @nicolasff Started adding Accept support.
authored
345
346 if(cmd->mime_free) free(cmd->mime);
347 cmd->mime = (char*)funs[i].ct;
348 cmd->mime_free = 0;
349
469e516 @nicolasff Removed content-type in another key, added suffixes instead.
authored
350 *f_format = funs[i].f;
48b2658 @nicolasff Prevent truncate when the extension is unknown.
authored
351 found = 1;
469e516 @nicolasff Removed content-type in another key, added suffixes instead.
authored
352 }
353 }
71bc9e3 @nicolasff Added a few content types, added support for ?type.
authored
354
6026811 @nicolasff Fix pub/sub. Valgrind is happy.
authored
355 /* the user can force it with ?type=some/thing */
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
356 if(client->type) {
bbec5c6 @nicolasff Bugfix, restored forced content-type.
authored
357 *f_format = custom_type_reply;
5b7aa50 @nicolasff Partial rewrite, adding WebSockets, threads, pool.
authored
358 cmd->mime = strdup(client->type);
bbec5c6 @nicolasff Bugfix, restored forced content-type.
authored
359 cmd->mime_free = 1;
71bc9e3 @nicolasff Added a few content types, added support for ?type.
authored
360 }
bbec5c6 @nicolasff Bugfix, restored forced content-type.
authored
361
48b2658 @nicolasff Prevent truncate when the extension is unknown.
authored
362 if(found) {
363 return uri_len - ext_len - 1;
364 } else {
365 /* no matching format, use default output with the full argument, extension included. */
366 return uri_len;
367 }
e2f2b36 @nicolasff Added RAW output.
authored
368 }
4676cfe @nicolasff Refactoring.
authored
369
370 int
371 cmd_is_subscribe(struct cmd *cmd) {
372
45ccf6a @nicolasff Remove check on argv[0] if argv[0] is null
authored
373 if(cmd->count >= 1 && cmd->argv[0] &&
981fd54 @nicolasff Fix Keep-Alive.
authored
374 (strncasecmp(cmd->argv[0], "SUBSCRIBE", cmd->argv_len[0]) == 0 ||
375 strncasecmp(cmd->argv[0], "PSUBSCRIBE", cmd->argv_len[0]) == 0)) {
4676cfe @nicolasff Refactoring.
authored
376 return 1;
377 }
378 return 0;
379 }
Something went wrong with that request. Please try again.