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 | |
61 | static 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 | |
80 | static 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 | |
88 | static 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 | |
96 | static 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 | |
106 | static 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 | |
117 | static 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 | |
130 | static 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 | |
138 | static 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 | |
146 | static 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 | |
164 | static 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 | |
172 | static 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 | |
187 | static 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 | |
198 | static 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. */ |
210 | int 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 | |