1 // rTorrent - BitTorrent client
2 // Copyright (C) 2005-2011, Jari Sundell
3 //
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation; either version 2 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17 //
18 // In addition, as a special exception, the copyright holders give
19 // permission to link the code of portions of this program with the
20 // OpenSSL library under certain conditions as described in each
21 // individual source file, and distribute linked combinations
22 // including the two.
23 //
24 // You must obey the GNU General Public License in all respects for
25 // all of the code used other than OpenSSL.  If you modify file(s)
26 // with this exception, you may extend this exception to your version
27 // of the file(s), but you are not obligated to do so.  If you do not
28 // wish to do so, delete this exception statement from your version.
29 // If you delete this exception statement from all source files in the
30 // program, then also delete it here.
31 //
32 // Contact:  Jari Sundell <jaris@ifi.uio.no>
33 //
34 //           Skomakerveien 33
35 //           3185 Skoppum, NORWAY
36 
37 #include "config.h"
38 
39 #include <cstring>
40 #include <cstdio>
41 #include <locale>
42 #include <rak/path.h>
43 #include <torrent/exceptions.h>
44 
45 #include "parse.h"
46 
47 namespace rpc {
48 
49 const char*
parse_skip_wspace(const char * first,const char * last)50 parse_skip_wspace(const char* first, const char* last) {
51   while (first != last && parse_is_space(*first))
52     first++;
53 
54   return first;
55 }
56 
57 const char*
parse_skip_wspace(const char * first)58 parse_skip_wspace(const char* first) {
59   while (parse_is_space(*first))
60     first++;
61 
62   return first;
63 }
64 
65 const char*
parse_string(const char * first,const char * last,std::string * dest,bool (* delim)(const char))66 parse_string(const char* first, const char* last, std::string* dest, bool (*delim)(const char)) {
67   if (first == last)
68     return first;
69 
70   if (parse_is_quote(*first)) {
71     first++;
72 
73     while (first != last) {
74       if (parse_is_quote(*first))
75         return ++first;
76 
77       if (parse_is_escape(*first) && ++first == last)
78         throw torrent::input_error("Escape character at end of input.");
79 
80       dest->push_back(*first++);
81     }
82 
83     throw torrent::input_error("Missing closing quote.");
84 
85   } else {
86     while (first != last) {
87       if (delim(*first))
88         return first;
89 
90       if (parse_is_escape(*first) && ++first == last)
91         throw torrent::input_error("Escape character at end of input.");
92 
93       dest->push_back(*first++);
94     }
95 
96     return first;
97   }
98 }
99 
100 void
parse_whole_string(const char * first,const char * last,std::string * dest)101 parse_whole_string(const char* first, const char* last, std::string* dest) {
102   first = parse_skip_wspace(first, last);
103   first = parse_string(first, last, dest);
104   first = parse_skip_wspace(first, last);
105 
106   if (first != last)
107     throw torrent::input_error("Junk at end of input.");
108 }
109 
110 const char*
parse_value(const char * src,int64_t * value,int base,int unit)111 parse_value(const char* src, int64_t* value, int base, int unit) {
112   const char* last = parse_value_nothrow(src, value, base, unit);
113 
114   if (last == src)
115     throw torrent::input_error("Could not convert string to value.");
116 
117   return last;
118 }
119 
120 void
parse_whole_value(const char * src,int64_t * value,int base,int unit)121 parse_whole_value(const char* src, int64_t* value, int base, int unit) {
122   const char* last = parse_value_nothrow(src, value, base, unit);
123 
124   if (last == src || *parse_skip_wspace(last) != '\0')
125     throw torrent::input_error("Could not convert string to value.");
126 }
127 
128 bool
parse_whole_value_nothrow(const char * src,int64_t * value,int base,int unit)129 parse_whole_value_nothrow(const char* src, int64_t* value, int base, int unit) {
130   const char* last = parse_value_nothrow(src, value, base, unit);
131 
132   if (last == src || *parse_skip_wspace(last) != '\0')
133     return false;
134 
135   return true;
136 }
137 
138 const char*
parse_value_nothrow(const char * src,int64_t * value,int base,int unit)139 parse_value_nothrow(const char* src, int64_t* value, int base, int unit) {
140   if (unit <= 0)
141     throw torrent::input_error("Command::string_to_value_unit(...) received unit <= 0.");
142 
143   char* last;
144   *value = strtoll(src, &last, base);
145 
146   if (last == src) {
147     if (strcasecmp(src, "no") == 0) { *value = 0; return src + strlen("no"); }
148     if (strcasecmp(src, "yes") == 0) { *value = 1; return src + strlen("yes"); }
149     if (strcasecmp(src, "true") == 0) { *value = 1; return src + strlen("true"); }
150     if (strcasecmp(src, "false") == 0) { *value = 0; return src + strlen("false"); }
151 
152     return src;
153   }
154 
155   switch (*last) {
156   case 'b':
157   case 'B': ++last; break;
158   case 'k':
159   case 'K': *value = *value << 10; ++last; break;
160   case 'm':
161   case 'M': *value = *value << 20; ++last; break;
162   case 'g':
163   case 'G': *value = *value << 30; ++last; break;
164 //   case ' ':
165 //   case '\0': *value = *value * unit; break;
166 //   default: throw torrent::input_error("Could not parse value.");
167   default: *value = *value * unit; break;
168   }
169 
170   return last;
171 }
172 
173 // Somewhat ugly...
174 const char*
parse_object(const char * first,const char * last,torrent::Object * dest,bool (* delim)(const char))175 parse_object(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char)) {
176   if (*first == '{') {
177     *dest = torrent::Object::create_list();
178     first = parse_list(first + 1, last, dest, &parse_is_delim_block);
179     first = parse_skip_wspace(first, last);
180 
181     if (first == last || *first != '}')
182       throw torrent::input_error("Could not find closing '}'.");
183 
184     return ++first;
185 
186   } else if (*first == '(') {
187     int32_t depth = 1;
188 
189     while (first + 1 != last && *(first + 1) == '(') {
190       first++;
191       depth++;
192     }
193 
194     if (depth > 3)
195       throw torrent::input_error("Max 3 parantheses per object allowed.");
196 
197     *dest = torrent::Object::create_dict_key();
198     dest->set_flags(torrent::Object::flag_function << (depth - 1));
199 
200     first = parse_string(first + 1, last, &dest->as_dict_key(), &parse_is_delim_func);
201     first = parse_skip_wspace(first, last);
202 
203     if (first == last || !parse_is_delim_func(*first))
204       throw torrent::input_error("Could not find closing ')'.");
205 
206     if (*first == ',') {
207       // This will always create a list even for single argument functions...
208       dest->as_dict_obj() = torrent::Object::create_list();
209       first = parse_list(first + 1, last, &dest->as_dict_obj(), &parse_is_delim_func);
210       first = parse_skip_wspace(first, last);
211     }
212 
213     while (depth != 0 && first != last && *first == ')') {
214       first++;
215       depth--;
216     }
217 
218     if (depth != 0)
219       throw torrent::input_error("Parantheses mismatch.");
220 
221     return first;
222 
223   } else {
224     *dest = std::string();
225 
226     return parse_string(first, last, &dest->as_string(), delim);
227   }
228 }
229 
230 const char*
parse_list(const char * first,const char * last,torrent::Object * dest,bool (* delim)(const char))231 parse_list(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char)) {
232   if (!dest->is_list())
233     throw torrent::internal_error("parse_list(...) !dest->is_list().");
234 
235   while (true) {
236     torrent::Object tmp;
237 
238     first = parse_skip_wspace(first, last);
239     first = parse_object(first, last, &tmp, delim);
240     first = parse_skip_wspace(first, last);
241 
242     dest->as_list().push_back(tmp);
243 
244     if (first == last || !parse_is_seperator(*first))
245       break;
246 
247     first++;
248   }
249 
250   return first;
251 }
252 
253 const char*
parse_whole_list(const char * first,const char * last,torrent::Object * dest,bool (* delim)(const char))254 parse_whole_list(const char* first, const char* last, torrent::Object* dest, bool (*delim)(const char)) {
255   first = parse_skip_wspace(first, last);
256   first = parse_object(first, last, dest, delim);
257   first = parse_skip_wspace(first, last);
258 
259   if (first != last && parse_is_seperator(*first)) {
260     torrent::Object tmp = torrent::Object::create_list();
261     tmp.swap(*dest);
262 
263     dest->as_list().push_back(tmp);
264     first = parse_list(++first, last, dest, delim);
265   }
266 
267   return first;
268 }
269 
270 std::string
convert_to_string(const torrent::Object & rawSrc)271 convert_to_string(const torrent::Object& rawSrc) {
272   const torrent::Object& src = convert_to_single_argument(rawSrc);
273 
274   switch (src.type()) {
275   case torrent::Object::TYPE_VALUE: {
276     char buffer[64];
277     snprintf(buffer, 64, "%lli", (long long int)src.as_value());
278     return std::string(buffer);
279   }
280   case torrent::Object::TYPE_STRING: return src.as_string();
281   case torrent::Object::TYPE_NONE:   return std::string();
282 
283   case torrent::Object::TYPE_RAW_BENCODE:
284     if (src.as_raw_bencode().is_empty())
285       return std::string();
286 
287     if (src.as_raw_bencode().is_raw_string())
288       return src.as_raw_bencode().as_raw_string().as_string();
289 
290     if (src.as_raw_bencode().is_value())
291       return src.as_raw_bencode().as_value_string();
292 
293   default: throw torrent::input_error("Not a string.");
294   }
295 }
296 
297 std::string
convert_list_to_string(const torrent::Object & src)298 convert_list_to_string(const torrent::Object& src) {
299   if (!src.is_list())
300     throw torrent::internal_error("convert_list_to_string(...) !src->is_list().");
301 
302   return convert_list_to_string(src.as_list().begin(), src.as_list().end());
303 }
304 
305 std::string
convert_list_to_string(torrent::Object::list_const_iterator first,torrent::Object::list_const_iterator last)306 convert_list_to_string(torrent::Object::list_const_iterator first,
307                        torrent::Object::list_const_iterator last) {
308   std::string dest;
309 
310   while (first != last) {
311     if (!first->is_string())
312       throw torrent::input_error("Could not convert non-string list element to string.");
313 
314     // Meh.
315     if (!dest.empty())
316       dest += ",\"";
317     else
318       dest += '"';
319 
320     std::string::size_type quoteItr = dest.size();
321     dest += first->as_string();
322 
323     // Finding a quote inside the string should be relatively rare, so
324     // use something that is fast in the general case and ignore the
325     // cost of the unusual one.
326     while (quoteItr != dest.size()) {
327       if (dest[quoteItr] == '"' || dest[quoteItr] == '\\')
328         dest.insert(quoteItr++, 1, '\\');
329 
330       quoteItr++;
331     }
332 
333     dest += '"';
334     first++;
335   }
336 
337   return dest;
338 }
339 
340 std::string
convert_list_to_command(torrent::Object::list_const_iterator first,torrent::Object::list_const_iterator last)341 convert_list_to_command(torrent::Object::list_const_iterator first,
342                         torrent::Object::list_const_iterator last) {
343   if (first == last)
344     throw torrent::input_error("Too few arguments.");
345 
346   std::string dest = (first++)->as_string();
347   std::string::size_type quoteItr = dest.find('=');
348 
349   if (quoteItr == std::string::npos)
350     throw torrent::input_error("Could not find '=' in command.");
351 
352   // We should only escape backslash, not quote here as the string
353   // will start with the command name which isn't quoted.
354   while ((quoteItr = dest.find('\\', quoteItr + 1)) != std::string::npos)
355     dest.insert(quoteItr++, 1, '\\');
356 
357   while (first != last) {
358     if (!first->is_string())
359       throw torrent::input_error("Could not convert non-string list element to string.");
360 
361     dest += ",\"";
362 
363     std::string::size_type quoteItr = dest.size();
364     dest += first->as_string();
365 
366     // Finding a quote inside the string should be relatively rare, so
367     // use something that is fast in the general case and ignore the
368     // cost of the unusual one.
369     while (quoteItr != dest.size()) {
370       if (dest[quoteItr] == '"' || dest[quoteItr] == '\\')
371         dest.insert(quoteItr++, 1, '\\');
372 
373       quoteItr++;
374     }
375 
376     dest += '"';
377     first++;
378   }
379 
380   return dest;
381 }
382 
383 int64_t
convert_to_value(const torrent::Object & src,int base,int unit)384 convert_to_value(const torrent::Object& src, int base, int unit) {
385   int64_t value;
386 
387   if (!convert_to_value_nothrow(src, &value, base, unit))
388     throw torrent::input_error("Not convertible to a value.");
389 
390   return value;
391 }
392 
393 bool
convert_to_value_nothrow(const torrent::Object & src,int64_t * value,int base,int unit)394 convert_to_value_nothrow(const torrent::Object& src, int64_t* value, int base, int unit) {
395   const torrent::Object& unpacked = (src.is_list() && src.as_list().size() == 1) ? src.as_list().front() : src;
396 
397   switch (unpacked.type()) {
398   case torrent::Object::TYPE_VALUE:
399     *value = unpacked.as_value();
400     break;
401 
402   case torrent::Object::TYPE_STRING:
403     return parse_skip_wspace(parse_value(unpacked.as_string().c_str(), value, base, unit),
404                              unpacked.as_string().c_str() + unpacked.as_string().size())
405       == unpacked.as_string().c_str() + unpacked.as_string().size();
406 
407   case torrent::Object::TYPE_RAW_STRING: {
408     const torrent::raw_string& str = src.as_raw_string();
409 
410     char buffer[str.size() + 1];
411     std::memcpy(buffer, str.data(), str.size());
412     buffer[str.size()] = '\0';
413 
414     return parse_skip_wspace(parse_value(buffer, value, base, unit), buffer + str.size())
415       == buffer + str.size();
416   }
417   case torrent::Object::TYPE_NONE:
418     *value = 0;
419     break;
420 
421   default:
422     return false;
423   }
424 
425   return true;
426 }
427 
428 char*
print_object(char * first,char * last,const torrent::Object * src,int flags)429 print_object(char* first, char* last, const torrent::Object* src, int flags) {
430   switch (src->type()) {
431   case torrent::Object::TYPE_STRING:
432   {
433     const std::string& str = src->as_string();
434 
435     if ((flags & print_expand_tilde) && *str.c_str() == '~') {
436       return rak::path_expand(str.c_str(), first, last);
437 
438     } else {
439       if (first == last)
440         return first;
441 
442       size_t n = std::min<size_t>(str.size(), std::distance(first, last) - 1);
443 
444       std::memcpy(first, str.c_str(), n);
445       *(first += n) = '\0';
446 
447       return first;
448     }
449   }
450 
451   case torrent::Object::TYPE_VALUE:
452     return std::min(first + snprintf(first, std::distance(first, last), "%lli", (long long int)src->as_value()), last);
453 
454   case torrent::Object::TYPE_LIST:
455     if (first != last)
456       *first = '\0';
457 
458     for (torrent::Object::list_const_iterator itr = src->as_list().begin(), itrEnd = src->as_list().end(); itr != itrEnd; itr++) {
459       first = print_object(first, last, &*itr, flags);
460 
461       // Don't expand tilde after the first element in the list.
462       flags &= ~print_expand_tilde;
463     }
464 
465     return first;
466 
467   case torrent::Object::TYPE_NONE:
468     if (first != last)
469       *first = '\0';
470 
471     return first;
472   default:
473     throw torrent::input_error("Invalid type.");
474   }
475 }
476 
477 void
print_object_std(std::string * dest,const torrent::Object * src,int flags)478 print_object_std(std::string* dest, const torrent::Object* src, int flags) {
479   switch (src->type()) {
480   case torrent::Object::TYPE_STRING:
481   {
482     const std::string& str = src->as_string();
483 
484     if ((flags & print_expand_tilde) && *str.c_str() == '~')
485       *dest += rak::path_expand(str);
486     else
487       *dest += str;
488 
489     return;
490   }
491   case torrent::Object::TYPE_VALUE:
492   {
493     char buffer[64];
494     snprintf(buffer, 64, "%lli", (long long int)src->as_value());
495 
496     *dest += buffer;
497     return;
498   }
499   case torrent::Object::TYPE_LIST:
500     for (torrent::Object::list_const_iterator itr = src->as_list().begin(), itrEnd = src->as_list().end(); itr != itrEnd; itr++) {
501       print_object_std(dest, &*itr, flags);
502 
503       // Don't expand tilde after the first element in the list.
504       flags &= ~print_expand_tilde;
505     }
506 
507     return;
508 
509   case torrent::Object::TYPE_NONE:
510     return;
511   default:
512     throw torrent::input_error("Invalid type.");
513   }
514 }
515 
516 }
517