diff options
Diffstat (limited to 'src/resp_parser.c')
-rw-r--r-- | src/resp_parser.c | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/src/resp_parser.c b/src/resp_parser.c new file mode 100644 index 0000000..b92a74c --- /dev/null +++ b/src/resp_parser.c @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2009-2021, Redis Labs Ltd. + * 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. + */ + +/* ---------------------------------------------------------------------------------------- + * A RESP parser for parsing replies returned by RM_Call or Lua's + * 'redis.call()'. + * + * The parser introduces callbacks that need to be set by the user. Each + * callback represents a different reply type. Each callback gets a p_ctx that + * was given to the parseReply function. The callbacks also give the protocol + * (underlying blob) of the current reply and the size. + * + * Some callbacks also get the parser object itself: + * - array_callback + * - set_callback + * - map_callback + * + * These callbacks need to continue parsing by calling parseReply a number of + * times, according to the supplied length. Subsequent parseReply calls may use + * a different p_ctx, which will be used for nested CallReply objects. + * + * These callbacks also do not receive a proto_len, which is not known at the + * time of parsing. Callers may calculate it themselves after parsing the + * entire collection. + * + * NOTE: This parser is designed to only handle replies generated by Redis + * itself. It does not perform many required validations and thus NOT SAFE FOR + * PARSING USER INPUT. + * ---------------------------------------------------------------------------------------- + */ + +#include "resp_parser.h" +#include "server.h" + +static int parseBulk(ReplyParser *parser, void *p_ctx) { + const char *proto = parser->curr_location; + char *p = strchr(proto+1,'\r'); + long long bulklen; + parser->curr_location = p + 2; /* for \r\n */ + + string2ll(proto+1,p-proto-1,&bulklen); + if (bulklen == -1) { + parser->callbacks.null_bulk_string_callback(p_ctx, proto, parser->curr_location - proto); + } else { + const char *str = parser->curr_location; + parser->curr_location += bulklen; + parser->curr_location += 2; /* for \r\n */ + parser->callbacks.bulk_string_callback(p_ctx, str, bulklen, proto, parser->curr_location - proto); + } + + return C_OK; +} + +static int parseSimpleString(ReplyParser *parser, void *p_ctx) { + const char *proto = parser->curr_location; + char *p = strchr(proto+1,'\r'); + parser->curr_location = p + 2; /* for \r\n */ + parser->callbacks.simple_str_callback(p_ctx, proto+1, p-proto-1, proto, parser->curr_location - proto); + return C_OK; +} + +static int parseError(ReplyParser *parser, void *p_ctx) { + const char *proto = parser->curr_location; + char *p = strchr(proto+1,'\r'); + parser->curr_location = p + 2; // for \r\n + parser->callbacks.error_callback(p_ctx, proto+1, p-proto-1, proto, parser->curr_location - proto); + return C_OK; +} + +static int parseLong(ReplyParser *parser, void *p_ctx) { + const char *proto = parser->curr_location; + char *p = strchr(proto+1,'\r'); + parser->curr_location = p + 2; /* for \r\n */ + long long val; + string2ll(proto+1,p-proto-1,&val); + parser->callbacks.long_callback(p_ctx, val, proto, parser->curr_location - proto); + return C_OK; +} + +static int parseAttributes(ReplyParser *parser, void *p_ctx) { + const char *proto = parser->curr_location; + char *p = strchr(proto+1,'\r'); + long long len; + string2ll(proto+1,p-proto-1,&len); + p += 2; + parser->curr_location = p; + parser->callbacks.attribute_callback(parser, p_ctx, len, proto); + return C_OK; +} + +static int parseVerbatimString(ReplyParser *parser, void *p_ctx) { + const char *proto = parser->curr_location; + char *p = strchr(proto+1,'\r'); + long long bulklen; + parser->curr_location = p + 2; /* for \r\n */ + string2ll(proto+1,p-proto-1,&bulklen); + const char *format = parser->curr_location; + parser->curr_location += bulklen; + parser->curr_location += 2; /* for \r\n */ + parser->callbacks.verbatim_string_callback(p_ctx, format, format + 4, bulklen - 4, proto, parser->curr_location - proto); + return C_OK; +} + +static int parseBigNumber(ReplyParser *parser, void *p_ctx) { + const char *proto = parser->curr_location; + char *p = strchr(proto+1,'\r'); + parser->curr_location = p + 2; /* for \r\n */ + parser->callbacks.big_number_callback(p_ctx, proto+1, p-proto-1, proto, parser->curr_location - proto); + return C_OK; +} + +static int parseNull(ReplyParser *parser, void *p_ctx) { + const char *proto = parser->curr_location; + char *p = strchr(proto+1,'\r'); + parser->curr_location = p + 2; /* for \r\n */ + parser->callbacks.null_callback(p_ctx, proto, parser->curr_location - proto); + return C_OK; +} + +static int parseDouble(ReplyParser *parser, void *p_ctx) { + const char *proto = parser->curr_location; + char *p = strchr(proto+1,'\r'); + parser->curr_location = p + 2; /* for \r\n */ + char buf[MAX_LONG_DOUBLE_CHARS+1]; + size_t len = p-proto-1; + double d; + if (len <= MAX_LONG_DOUBLE_CHARS) { + memcpy(buf,proto+1,len); + buf[len] = '\0'; + d = strtod(buf,NULL); /* We expect a valid representation. */ + } else { + d = 0; + } + parser->callbacks.double_callback(p_ctx, d, proto, parser->curr_location - proto); + return C_OK; +} + +static int parseBool(ReplyParser *parser, void *p_ctx) { + const char *proto = parser->curr_location; + char *p = strchr(proto+1,'\r'); + parser->curr_location = p + 2; /* for \r\n */ + parser->callbacks.bool_callback(p_ctx, proto[1] == 't', proto, parser->curr_location - proto); + return C_OK; +} + +static int parseArray(ReplyParser *parser, void *p_ctx) { + const char *proto = parser->curr_location; + char *p = strchr(proto+1,'\r'); + long long len; + string2ll(proto+1,p-proto-1,&len); + p += 2; + parser->curr_location = p; + if (len == -1) { + parser->callbacks.null_array_callback(p_ctx, proto, parser->curr_location - proto); + } else { + parser->callbacks.array_callback(parser, p_ctx, len, proto); + } + return C_OK; +} + +static int parseSet(ReplyParser *parser, void *p_ctx) { + const char *proto = parser->curr_location; + char *p = strchr(proto+1,'\r'); + long long len; + string2ll(proto+1,p-proto-1,&len); + p += 2; + parser->curr_location = p; + parser->callbacks.set_callback(parser, p_ctx, len, proto); + return C_OK; +} + +static int parseMap(ReplyParser *parser, void *p_ctx) { + const char *proto = parser->curr_location; + char *p = strchr(proto+1,'\r'); + long long len; + string2ll(proto+1,p-proto-1,&len); + p += 2; + parser->curr_location = p; + parser->callbacks.map_callback(parser, p_ctx, len, proto); + return C_OK; +} + +/* Parse a reply pointed to by parser->curr_location. */ +int parseReply(ReplyParser *parser, void *p_ctx) { + switch (parser->curr_location[0]) { + case '$': return parseBulk(parser, p_ctx); + case '+': return parseSimpleString(parser, p_ctx); + case '-': return parseError(parser, p_ctx); + case ':': return parseLong(parser, p_ctx); + case '*': return parseArray(parser, p_ctx); + case '~': return parseSet(parser, p_ctx); + case '%': return parseMap(parser, p_ctx); + case '#': return parseBool(parser, p_ctx); + case ',': return parseDouble(parser, p_ctx); + case '_': return parseNull(parser, p_ctx); + case '(': return parseBigNumber(parser, p_ctx); + case '=': return parseVerbatimString(parser, p_ctx); + case '|': return parseAttributes(parser, p_ctx); + default: if (parser->callbacks.error) parser->callbacks.error(p_ctx); + } + return C_ERR; +} |