1/*
2 * Copyright (c) 2009-2021, Redis Labs Ltd.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * * Neither the name of Redis nor the names of its contributors may be used
14 * to endorse or promote products derived from this software without
15 * specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 */
29
30/* ----------------------------------------------------------------------------------------
31 * A RESP parser for parsing replies returned by RM_Call or Lua's
32 * 'redis.call()'.
33 *
34 * The parser introduces callbacks that need to be set by the user. Each
35 * callback represents a different reply type. Each callback gets a p_ctx that
36 * was given to the parseReply function. The callbacks also give the protocol
37 * (underlying blob) of the current reply and the size.
38 *
39 * Some callbacks also get the parser object itself:
40 * - array_callback
41 * - set_callback
42 * - map_callback
43 *
44 * These callbacks need to continue parsing by calling parseReply a number of
45 * times, according to the supplied length. Subsequent parseReply calls may use
46 * a different p_ctx, which will be used for nested CallReply objects.
47 *
48 * These callbacks also do not receive a proto_len, which is not known at the
49 * time of parsing. Callers may calculate it themselves after parsing the
50 * entire collection.
51 *
52 * NOTE: This parser is designed to only handle replies generated by Redis
53 * itself. It does not perform many required validations and thus NOT SAFE FOR
54 * PARSING USER INPUT.
55 * ----------------------------------------------------------------------------------------
56 */
57
58#include "resp_parser.h"
59#include "server.h"
60
61static int parseBulk(ReplyParser *parser, void *p_ctx) {
62 const char *proto = parser->curr_location;
63 char *p = strchr(proto+1,'\r');
64 long long bulklen;
65 parser->curr_location = p + 2; /* for \r\n */
66
67 string2ll(proto+1,p-proto-1,&bulklen);
68 if (bulklen == -1) {
69 parser->callbacks.null_bulk_string_callback(p_ctx, proto, parser->curr_location - proto);
70 } else {
71 const char *str = parser->curr_location;
72 parser->curr_location += bulklen;
73 parser->curr_location += 2; /* for \r\n */
74 parser->callbacks.bulk_string_callback(p_ctx, str, bulklen, proto, parser->curr_location - proto);
75 }
76
77 return C_OK;
78}
79
80static int parseSimpleString(ReplyParser *parser, void *p_ctx) {
81 const char *proto = parser->curr_location;
82 char *p = strchr(proto+1,'\r');
83 parser->curr_location = p + 2; /* for \r\n */
84 parser->callbacks.simple_str_callback(p_ctx, proto+1, p-proto-1, proto, parser->curr_location - proto);
85 return C_OK;
86}
87
88static int parseError(ReplyParser *parser, void *p_ctx) {
89 const char *proto = parser->curr_location;
90 char *p = strchr(proto+1,'\r');
91 parser->curr_location = p + 2; // for \r\n
92 parser->callbacks.error_callback(p_ctx, proto+1, p-proto-1, proto, parser->curr_location - proto);
93 return C_OK;
94}
95
96static int parseLong(ReplyParser *parser, void *p_ctx) {
97 const char *proto = parser->curr_location;
98 char *p = strchr(proto+1,'\r');
99 parser->curr_location = p + 2; /* for \r\n */
100 long long val;
101 string2ll(proto+1,p-proto-1,&val);
102 parser->callbacks.long_callback(p_ctx, val, proto, parser->curr_location - proto);
103 return C_OK;
104}
105
106static int parseAttributes(ReplyParser *parser, void *p_ctx) {
107 const char *proto = parser->curr_location;
108 char *p = strchr(proto+1,'\r');
109 long long len;
110 string2ll(proto+1,p-proto-1,&len);
111 p += 2;
112 parser->curr_location = p;
113 parser->callbacks.attribute_callback(parser, p_ctx, len, proto);
114 return C_OK;
115}
116
117static int parseVerbatimString(ReplyParser *parser, void *p_ctx) {
118 const char *proto = parser->curr_location;
119 char *p = strchr(proto+1,'\r');
120 long long bulklen;
121 parser->curr_location = p + 2; /* for \r\n */
122 string2ll(proto+1,p-proto-1,&bulklen);
123 const char *format = parser->curr_location;
124 parser->curr_location += bulklen;
125 parser->curr_location += 2; /* for \r\n */
126 parser->callbacks.verbatim_string_callback(p_ctx, format, format + 4, bulklen - 4, proto, parser->curr_location - proto);
127 return C_OK;
128}
129
130static int parseBigNumber(ReplyParser *parser, void *p_ctx) {
131 const char *proto = parser->curr_location;
132 char *p = strchr(proto+1,'\r');
133 parser->curr_location = p + 2; /* for \r\n */
134 parser->callbacks.big_number_callback(p_ctx, proto+1, p-proto-1, proto, parser->curr_location - proto);
135 return C_OK;
136}
137
138static int parseNull(ReplyParser *parser, void *p_ctx) {
139 const char *proto = parser->curr_location;
140 char *p = strchr(proto+1,'\r');
141 parser->curr_location = p + 2; /* for \r\n */
142 parser->callbacks.null_callback(p_ctx, proto, parser->curr_location - proto);
143 return C_OK;
144}
145
146static int parseDouble(ReplyParser *parser, void *p_ctx) {
147 const char *proto = parser->curr_location;
148 char *p = strchr(proto+1,'\r');
149 parser->curr_location = p + 2; /* for \r\n */
150 char buf[MAX_LONG_DOUBLE_CHARS+1];
151 size_t len = p-proto-1;
152 double d;
153 if (len <= MAX_LONG_DOUBLE_CHARS) {
154 memcpy(buf,proto+1,len);
155 buf[len] = '\0';
156 d = strtod(buf,NULL); /* We expect a valid representation. */
157 } else {
158 d = 0;
159 }
160 parser->callbacks.double_callback(p_ctx, d, proto, parser->curr_location - proto);
161 return C_OK;
162}
163
164static int parseBool(ReplyParser *parser, void *p_ctx) {
165 const char *proto = parser->curr_location;
166 char *p = strchr(proto+1,'\r');
167 parser->curr_location = p + 2; /* for \r\n */
168 parser->callbacks.bool_callback(p_ctx, proto[1] == 't', proto, parser->curr_location - proto);
169 return C_OK;
170}
171
172static int parseArray(ReplyParser *parser, void *p_ctx) {
173 const char *proto = parser->curr_location;
174 char *p = strchr(proto+1,'\r');
175 long long len;
176 string2ll(proto+1,p-proto-1,&len);
177 p += 2;
178 parser->curr_location = p;
179 if (len == -1) {
180 parser->callbacks.null_array_callback(p_ctx, proto, parser->curr_location - proto);
181 } else {
182 parser->callbacks.array_callback(parser, p_ctx, len, proto);
183 }
184 return C_OK;
185}
186
187static int parseSet(ReplyParser *parser, void *p_ctx) {
188 const char *proto = parser->curr_location;
189 char *p = strchr(proto+1,'\r');
190 long long len;
191 string2ll(proto+1,p-proto-1,&len);
192 p += 2;
193 parser->curr_location = p;
194 parser->callbacks.set_callback(parser, p_ctx, len, proto);
195 return C_OK;
196}
197
198static int parseMap(ReplyParser *parser, void *p_ctx) {
199 const char *proto = parser->curr_location;
200 char *p = strchr(proto+1,'\r');
201 long long len;
202 string2ll(proto+1,p-proto-1,&len);
203 p += 2;
204 parser->curr_location = p;
205 parser->callbacks.map_callback(parser, p_ctx, len, proto);
206 return C_OK;
207}
208
209/* Parse a reply pointed to by parser->curr_location. */
210int parseReply(ReplyParser *parser, void *p_ctx) {
211 switch (parser->curr_location[0]) {
212 case '$': return parseBulk(parser, p_ctx);
213 case '+': return parseSimpleString(parser, p_ctx);
214 case '-': return parseError(parser, p_ctx);
215 case ':': return parseLong(parser, p_ctx);
216 case '*': return parseArray(parser, p_ctx);
217 case '~': return parseSet(parser, p_ctx);
218 case '%': return parseMap(parser, p_ctx);
219 case '#': return parseBool(parser, p_ctx);
220 case ',': return parseDouble(parser, p_ctx);
221 case '_': return parseNull(parser, p_ctx);
222 case '(': return parseBigNumber(parser, p_ctx);
223 case '=': return parseVerbatimString(parser, p_ctx);
224 case '|': return parseAttributes(parser, p_ctx);
225 default: if (parser->callbacks.error) parser->callbacks.error(p_ctx);
226 }
227 return C_ERR;
228}
229