Use fixed-width format specifiers in serializeStructToString
[oweals/minetest.git] / src / util / serialize.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "serialize.h"
21 #include "pointer.h"
22 #include "porting.h"
23 #include "util/string.h"
24 #include "../exceptions.h"
25 #include "../irrlichttypes.h"
26
27 #include <inttypes.h>  // For PRIxN, cinttypes is C++11-only
28 #include <sstream>
29 #include <iomanip>
30 #include <vector>
31
32 // Creates a string with the length as the first two bytes
33 std::string serializeString(const std::string &plain)
34 {
35         //assert(plain.size() <= 65535);
36         if(plain.size() > 65535)
37                 throw SerializationError("String too long for serializeString");
38         char buf[2];
39         writeU16((u8*)&buf[0], plain.size());
40         std::string s;
41         s.append(buf, 2);
42         s.append(plain);
43         return s;
44 }
45
46 // Creates a string with the length as the first two bytes from wide string
47 std::string serializeWideString(const std::wstring &plain)
48 {
49         //assert(plain.size() <= 65535);
50         if(plain.size() > 65535)
51                 throw SerializationError("String too long for serializeString");
52         char buf[2];
53         writeU16((u8*)buf, plain.size());
54         std::string s;
55         s.append(buf, 2);
56         for(u32 i=0; i<plain.size(); i++)
57         {
58                 writeU16((u8*)buf, plain[i]);
59                 s.append(buf, 2);
60         }
61         return s;
62 }
63
64 // Reads a string with the length as the first two bytes
65 std::string deSerializeString(std::istream &is)
66 {
67         char buf[2];
68         is.read(buf, 2);
69         if(is.gcount() != 2)
70                 throw SerializationError("deSerializeString: size not read");
71         u16 s_size = readU16((u8*)buf);
72         if(s_size == 0)
73                 return "";
74         Buffer<char> buf2(s_size);
75         is.read(&buf2[0], s_size);
76         std::string s;
77         s.reserve(s_size);
78         s.append(&buf2[0], s_size);
79         return s;
80 }
81
82 // Reads a wide string with the length as the first two bytes
83 std::wstring deSerializeWideString(std::istream &is)
84 {
85         char buf[2];
86         is.read(buf, 2);
87         if(is.gcount() != 2)
88                 throw SerializationError("deSerializeString: size not read");
89         u16 s_size = readU16((u8*)buf);
90         if(s_size == 0)
91                 return L"";
92         std::wstring s;
93         s.reserve(s_size);
94         for(u32 i=0; i<s_size; i++)
95         {
96                 is.read(&buf[0], 2);
97                 wchar_t c16 = readU16((u8*)buf);
98                 s.append(&c16, 1);
99         }
100         return s;
101 }
102
103 // Creates a string with the length as the first four bytes
104 std::string serializeLongString(const std::string &plain)
105 {
106         char buf[4];
107         writeU32((u8*)&buf[0], plain.size());
108         std::string s;
109         s.append(buf, 4);
110         s.append(plain);
111         return s;
112 }
113
114 // Reads a string with the length as the first four bytes
115 std::string deSerializeLongString(std::istream &is)
116 {
117         char buf[4];
118         is.read(buf, 4);
119         if(is.gcount() != 4)
120                 throw SerializationError("deSerializeLongString: size not read");
121         u32 s_size = readU32((u8*)buf);
122         if(s_size == 0)
123                 return "";
124         Buffer<char> buf2(s_size);
125         is.read(&buf2[0], s_size);
126         std::string s;
127         s.reserve(s_size);
128         s.append(&buf2[0], s_size);
129         return s;
130 }
131
132 // Creates a string encoded in JSON format (almost equivalent to a C string literal)
133 std::string serializeJsonString(const std::string &plain)
134 {
135         std::ostringstream os(std::ios::binary);
136         os<<"\"";
137         for(size_t i = 0; i < plain.size(); i++)
138         {
139                 char c = plain[i];
140                 switch(c)
141                 {
142                         case '"': os<<"\\\""; break;
143                         case '\\': os<<"\\\\"; break;
144                         case '/': os<<"\\/"; break;
145                         case '\b': os<<"\\b"; break;
146                         case '\f': os<<"\\f"; break;
147                         case '\n': os<<"\\n"; break;
148                         case '\r': os<<"\\r"; break;
149                         case '\t': os<<"\\t"; break;
150                         default:
151                         {
152                                 if(c >= 32 && c <= 126)
153                                 {
154                                         os<<c;
155                                 }
156                                 else
157                                 {
158                                         u32 cnum = (u32) (u8) c;
159                                         os<<"\\u"<<std::hex<<std::setw(4)<<std::setfill('0')<<cnum;
160                                 }
161                                 break;
162                         }
163                 }
164         }
165         os<<"\"";
166         return os.str();
167 }
168
169 // Reads a string encoded in JSON format
170 std::string deSerializeJsonString(std::istream &is)
171 {
172         std::ostringstream os(std::ios::binary);
173         char c, c2;
174
175         // Parse initial doublequote
176         is >> c;
177         if(c != '"')
178                 throw SerializationError("JSON string must start with doublequote");
179
180         // Parse characters
181         for(;;)
182         {
183                 c = is.get();
184                 if(is.eof())
185                         throw SerializationError("JSON string ended prematurely");
186                 if(c == '"')
187                 {
188                         return os.str();
189                 }
190                 else if(c == '\\')
191                 {
192                         c2 = is.get();
193                         if(is.eof())
194                                 throw SerializationError("JSON string ended prematurely");
195                         switch(c2)
196                         {
197                                 default:  os<<c2; break;
198                                 case 'b': os<<'\b'; break;
199                                 case 'f': os<<'\f'; break;
200                                 case 'n': os<<'\n'; break;
201                                 case 'r': os<<'\r'; break;
202                                 case 't': os<<'\t'; break;
203                                 case 'u':
204                                 {
205                                         char hexdigits[4+1];
206                                         is.read(hexdigits, 4);
207                                         if(is.eof())
208                                                 throw SerializationError("JSON string ended prematurely");
209                                         hexdigits[4] = 0;
210                                         std::istringstream tmp_is(hexdigits, std::ios::binary);
211                                         int hexnumber;
212                                         tmp_is >> std::hex >> hexnumber;
213                                         os<<((char)hexnumber);
214                                         break;
215                                 }
216                         }
217                 }
218                 else
219                 {
220                         os<<c;
221                 }
222         }
223         return os.str();
224 }
225
226
227 bool deSerializeStringToStruct(std::string valstr,
228         std::string format, void *out, size_t olen)
229 {
230         size_t len = olen;
231         std::vector<std::string *> strs_alloced;
232         std::string *str;
233         char *f, *snext;
234         size_t pos;
235
236         char *s = &valstr[0];
237         char *buf = new char[len];
238         char *bufpos = buf;
239
240         char *fmtpos, *fmt = &format[0];
241         while ((f = strtok_r(fmt, ",", &fmtpos)) && s) {
242                 fmt = NULL;
243
244                 bool is_unsigned = false;
245                 int width = 0;
246                 char valtype = *f;
247
248                 width = (int)strtol(f + 1, &f, 10);
249                 if (width && valtype == 's')
250                         valtype = 'i';
251
252                 switch (valtype) {
253                         case 'u':
254                                 is_unsigned = true;
255                                 /* FALLTHROUGH */
256                         case 'i':
257                                 if (width == 16) {
258                                         bufpos += PADDING(bufpos, u16);
259                                         if ((bufpos - buf) + sizeof(u16) <= len) {
260                                                 if (is_unsigned)
261                                                         *(u16 *)bufpos = (u16)strtoul(s, &s, 10);
262                                                 else
263                                                         *(s16 *)bufpos = (s16)strtol(s, &s, 10);
264                                         }
265                                         bufpos += sizeof(u16);
266                                 } else if (width == 32) {
267                                         bufpos += PADDING(bufpos, u32);
268                                         if ((bufpos - buf) + sizeof(u32) <= len) {
269                                                 if (is_unsigned)
270                                                         *(u32 *)bufpos = (u32)strtoul(s, &s, 10);
271                                                 else
272                                                         *(s32 *)bufpos = (s32)strtol(s, &s, 10);
273                                         }
274                                         bufpos += sizeof(u32);
275                                 } else if (width == 64) {
276                                         bufpos += PADDING(bufpos, u64);
277                                         if ((bufpos - buf) + sizeof(u64) <= len) {
278                                                 if (is_unsigned)
279                                                         *(u64 *)bufpos = (u64)strtoull(s, &s, 10);
280                                                 else
281                                                         *(s64 *)bufpos = (s64)strtoll(s, &s, 10);
282                                         }
283                                         bufpos += sizeof(u64);
284                                 }
285                                 s = strchr(s, ',');
286                                 break;
287                         case 'b':
288                                 snext = strchr(s, ',');
289                                 if (snext)
290                                         *snext++ = 0;
291
292                                 bufpos += PADDING(bufpos, bool);
293                                 if ((bufpos - buf) + sizeof(bool) <= len)
294                                         *(bool *)bufpos = is_yes(std::string(s));
295                                 bufpos += sizeof(bool);
296
297                                 s = snext;
298                                 break;
299                         case 'f':
300                                 bufpos += PADDING(bufpos, float);
301                                 if ((bufpos - buf) + sizeof(float) <= len)
302                                         *(float *)bufpos = strtof(s, &s);
303                                 bufpos += sizeof(float);
304
305                                 s = strchr(s, ',');
306                                 break;
307                         case 's':
308                                 while (*s == ' ' || *s == '\t')
309                                         s++;
310                                 if (*s++ != '"') //error, expected string
311                                         goto fail;
312                                 snext = s;
313
314                                 while (snext[0] && !(snext[-1] != '\\' && snext[0] == '"'))
315                                         snext++;
316                                 *snext++ = 0;
317
318                                 bufpos += PADDING(bufpos, std::string *);
319
320                                 str = new std::string(s);
321                                 pos = 0;
322                                 while ((pos = str->find("\\\"", pos)) != std::string::npos)
323                                         str->erase(pos, 1);
324
325                                 if ((bufpos - buf) + sizeof(std::string *) <= len)
326                                         *(std::string **)bufpos = str;
327                                 bufpos += sizeof(std::string *);
328                                 strs_alloced.push_back(str);
329
330                                 s = *snext ? snext + 1 : NULL;
331                                 break;
332                         case 'v':
333                                 while (*s == ' ' || *s == '\t')
334                                         s++;
335                                 if (*s++ != '(') //error, expected vector
336                                         goto fail;
337
338                                 if (width == 2) {
339                                         bufpos += PADDING(bufpos, v2f);
340
341                                         if ((bufpos - buf) + sizeof(v2f) <= len) {
342                                         v2f *v = (v2f *)bufpos;
343                                                 v->X = strtof(s, &s);
344                                                 s++;
345                                                 v->Y = strtof(s, &s);
346                                         }
347
348                                         bufpos += sizeof(v2f);
349                                 } else if (width == 3) {
350                                         bufpos += PADDING(bufpos, v3f);
351                                         if ((bufpos - buf) + sizeof(v3f) <= len) {
352                                                 v3f *v = (v3f *)bufpos;
353                                                 v->X = strtof(s, &s);
354                                                 s++;
355                                                 v->Y = strtof(s, &s);
356                                                 s++;
357                                                 v->Z = strtof(s, &s);
358                                         }
359
360                                         bufpos += sizeof(v3f);
361                                 }
362                                 s = strchr(s, ',');
363                                 break;
364                         default: //error, invalid format specifier
365                                 goto fail;
366                 }
367
368                 if (s && *s == ',')
369                         s++;
370
371                 if ((size_t)(bufpos - buf) > len) //error, buffer too small
372                         goto fail;
373         }
374
375         if (f && *f) { //error, mismatched number of fields and values
376 fail:
377                 for (size_t i = 0; i != strs_alloced.size(); i++)
378                         delete strs_alloced[i];
379                 delete[] buf;
380                 return false;
381         }
382
383         memcpy(out, buf, olen);
384         delete[] buf;
385         return true;
386 }
387
388
389 bool serializeStructToString(std::string *outstr,
390         std::string format, void *value)
391 {
392         char sbuf[2048];
393         int sbuflen = sizeof(sbuf) - 1;
394         sbuf[sbuflen] = 0;
395         std::string str;
396         int pos = 0;
397         size_t fpos;
398         char *f;
399
400         char *bufpos = (char *)value;
401         char *fmtpos, *fmt = &format[0];
402         while ((f = strtok_r(fmt, ",", &fmtpos))) {
403                 fmt = NULL;
404                 bool is_unsigned = false;
405                 int width = 0, nprinted = 0;
406                 char valtype = *f;
407
408                 width = (int)strtol(f + 1, &f, 10);
409                 if (width && valtype == 's')
410                         valtype = 'i';
411
412                 switch (valtype) {
413                         case 'u':
414                                 is_unsigned = true;
415                                 /* FALLTHROUGH */
416                         case 'i':
417                                 if (width == 16) {
418                                         bufpos += PADDING(bufpos, u16);
419                                         nprinted = snprintf(sbuf + pos, sbuflen,
420                                                                 is_unsigned ? "%" PRIu16 ", " : "%" PRIi16 ", ",
421                                                                 *((u16 *)bufpos));
422                                         bufpos += sizeof(u16);
423                                 } else if (width == 32) {
424                                         bufpos += PADDING(bufpos, u32);
425                                         nprinted = snprintf(sbuf + pos, sbuflen,
426                                                                 is_unsigned ? "%" PRIu32 ", " : "%" PRIi32 ", ",
427                                                                 *((u32 *)bufpos));
428                                         bufpos += sizeof(u32);
429                                 } else if (width == 64) {
430                                         bufpos += PADDING(bufpos, u64);
431                                         nprinted = snprintf(sbuf + pos, sbuflen,
432                                                                 is_unsigned ? "%" PRIu64 ", " : "%" PRIi64 ", ",
433                                                                 *((u64 *)bufpos));
434                                         bufpos += sizeof(u64);
435                                 }
436                                 break;
437                         case 'b':
438                                 bufpos += PADDING(bufpos, bool);
439                                 nprinted = snprintf(sbuf + pos, sbuflen, "%s, ",
440                                                                         *((bool *)bufpos) ? "true" : "false");
441                                 bufpos += sizeof(bool);
442                                 break;
443                         case 'f':
444                                 bufpos += PADDING(bufpos, float);
445                                 nprinted = snprintf(sbuf + pos, sbuflen, "%f, ",
446                                                                         *((float *)bufpos));
447                                 bufpos += sizeof(float);
448                                 break;
449                         case 's':
450                                 bufpos += PADDING(bufpos, std::string *);
451                                 str = **((std::string **)bufpos);
452
453                                 fpos = 0;
454                                 while ((fpos = str.find('"', fpos)) != std::string::npos) {
455                                         str.insert(fpos, 1, '\\');
456                                         fpos += 2;
457                                 }
458
459                                 nprinted = snprintf(sbuf + pos, sbuflen, "\"%s\", ",
460                                                                         (*((std::string **)bufpos))->c_str());
461                                 bufpos += sizeof(std::string *);
462                                 break;
463                         case 'v':
464                                 if (width == 2) {
465                                         bufpos += PADDING(bufpos, v2f);
466                                         v2f *v = (v2f *)bufpos;
467                                         nprinted = snprintf(sbuf + pos, sbuflen,
468                                                                                 "(%f, %f), ", v->X, v->Y);
469                                         bufpos += sizeof(v2f);
470                                 } else {
471                                         bufpos += PADDING(bufpos, v3f);
472                                         v3f *v = (v3f *)bufpos;
473                                         nprinted = snprintf(sbuf + pos, sbuflen,
474                                                                                 "(%f, %f, %f), ", v->X, v->Y, v->Z);
475                                         bufpos += sizeof(v3f);
476                                 }
477                                 break;
478                         default:
479                                 return false;
480                 }
481                 if (nprinted < 0) //error, buffer too small
482                         return false;
483                 pos     += nprinted;
484                 sbuflen -= nprinted;
485         }
486
487         // this is to trim off the trailing comma
488         if (pos >= 2)
489                 sbuf[pos - 2] = 0;
490
491         *outstr = sbuf;
492
493         return true;
494 }