blob: dd076c5f1ab5ff2ee157d694ec4de00c3817afab [file] [log] [blame]
Joshua Haberman093faeb2020-06-25 12:21:01 -07001// Protocol Buffers - Google's data interchange format
2// Copyright 2008 Google Inc. All rights reserved.
3// https://developers.google.com/protocol-buffers/
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are
7// met:
8//
9// * Redistributions of source code must retain the above copyright
10// notice, this list of conditions and the following disclaimer.
11// * Redistributions in binary form must reproduce the above
12// copyright notice, this list of conditions and the following disclaimer
13// in the documentation and/or other materials provided with the
14// distribution.
15// * Neither the name of Google Inc. nor the names of its
16// contributors may be used to endorse or promote products derived from
17// this software without specific prior written permission.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31#include "convert.h"
32
33#include <php.h>
34
35// This is not self-contained: it must be after other Zend includes.
36#include <Zend/zend_exceptions.h>
37
38#include "array.h"
39#include "map.h"
40#include "message.h"
41#include "php-upb.h"
42#include "protobuf.h"
43
44// -----------------------------------------------------------------------------
45// GPBUtil
46// -----------------------------------------------------------------------------
47
48static zend_class_entry* GPBUtil_class_entry;
49
50// The implementation of type checking for primitive fields is empty. This is
51// because type checking is done when direct assigning message fields (e.g.,
52// foo->a = 1). Functions defined here are place holders in generated code for
53// pure PHP implementation (c extension and pure PHP share the same generated
54// code).
55
56PHP_METHOD(Util, checkInt32) {}
57PHP_METHOD(Util, checkUint32) {}
58PHP_METHOD(Util, checkInt64) {}
59PHP_METHOD(Util, checkUint64) {}
60PHP_METHOD(Util, checkEnum) {}
61PHP_METHOD(Util, checkFloat) {}
62PHP_METHOD(Util, checkDouble) {}
63PHP_METHOD(Util, checkBool) {}
64PHP_METHOD(Util, checkString) {}
65PHP_METHOD(Util, checkBytes) {}
66PHP_METHOD(Util, checkMessage) {}
67
68// The result of checkMapField() is assigned, so we need to return the first
69// param:
70// $arr = GPBUtil::checkMapField($var,
71// \Google\Protobuf\Internal\GPBType::INT64,
72// \Google\Protobuf\Internal\GPBType::INT32);
73PHP_METHOD(Util, checkMapField) {
74 zval *val, *key_type, *val_type, *klass;
75 if (zend_parse_parameters(ZEND_NUM_ARGS(), "zzz|z", &val, &key_type,
76 &val_type, &klass) == FAILURE) {
77 return;
78 }
79 RETURN_ZVAL(val, 1, 0);
80}
81
82// The result of checkRepeatedField() is assigned, so we need to return the
83// first param:
84// $arr = GPBUtil::checkRepeatedField(
85// $var, \Google\Protobuf\Internal\GPBType::STRING);
86PHP_METHOD(Util, checkRepeatedField) {
87 zval *val, *type, *klass;
88 if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz|z", &val, &type, &klass) ==
89 FAILURE) {
90 return;
91 }
92 RETURN_ZVAL(val, 1, 0);
93}
94
95static zend_function_entry util_methods[] = {
96 PHP_ME(Util, checkInt32, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
97 PHP_ME(Util, checkUint32, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
98 PHP_ME(Util, checkInt64, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
99 PHP_ME(Util, checkUint64, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
100 PHP_ME(Util, checkEnum, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
101 PHP_ME(Util, checkFloat, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
102 PHP_ME(Util, checkDouble, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
103 PHP_ME(Util, checkBool, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
104 PHP_ME(Util, checkString, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
105 PHP_ME(Util, checkBytes, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
106 PHP_ME(Util, checkMessage, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
107 PHP_ME(Util, checkMapField, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
108 PHP_ME(Util, checkRepeatedField, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
109 ZEND_FE_END
110};
111
112// -----------------------------------------------------------------------------
113// Conversion functions used from C
114// -----------------------------------------------------------------------------
115
116upb_fieldtype_t pbphp_dtype_to_type(upb_descriptortype_t type) {
117 switch (type) {
118#define CASE(descriptor_type, type) \
119 case UPB_DESCRIPTOR_TYPE_##descriptor_type: \
120 return UPB_TYPE_##type;
121
122 CASE(FLOAT, FLOAT);
123 CASE(DOUBLE, DOUBLE);
124 CASE(BOOL, BOOL);
125 CASE(STRING, STRING);
126 CASE(BYTES, BYTES);
127 CASE(MESSAGE, MESSAGE);
128 CASE(GROUP, MESSAGE);
129 CASE(ENUM, ENUM);
130 CASE(INT32, INT32);
131 CASE(INT64, INT64);
132 CASE(UINT32, UINT32);
133 CASE(UINT64, UINT64);
134 CASE(SINT32, INT32);
135 CASE(SINT64, INT64);
136 CASE(FIXED32, UINT32);
137 CASE(FIXED64, UINT64);
138 CASE(SFIXED32, INT32);
139 CASE(SFIXED64, INT64);
140
141#undef CASE
142
143 }
144
145 zend_error(E_ERROR, "Unknown field type.");
146 return 0;
147}
148
149static bool buftouint64(const char *ptr, const char *end, uint64_t *val) {
150 uint64_t u64 = 0;
151 while (ptr < end) {
152 unsigned ch = (unsigned)(*ptr - '0');
153 if (ch >= 10) break;
154 if (u64 > UINT64_MAX / 10 || u64 * 10 > UINT64_MAX - ch) {
155 return false;
156 }
157 u64 *= 10;
158 u64 += ch;
159 ptr++;
160 }
161
162 if (ptr != end) {
163 // In PHP tradition, we allow truncation: "1.1" -> 1.
164 // But we don't allow 'e', eg. '1.1e2' or any other non-numeric chars.
165 if (*ptr++ != '.') return false;
166
167 for (;ptr < end; ptr++) {
168 if (*ptr < '0' || *ptr > '9') {
169 return false;
170 }
171 }
172 }
173
174 *val = u64;
175 return true;
176}
177
178static bool buftoint64(const char *ptr, const char *end, int64_t *val) {
179 bool neg = false;
180 uint64_t u64;
181
182 if (ptr != end && *ptr == '-') {
183 ptr++;
184 neg = true;
185 }
186
187 if (!buftouint64(ptr, end, &u64) ||
188 u64 > (uint64_t)INT64_MAX + neg) {
189 return false;
190 }
191
192 *val = neg ? -u64 : u64;
193 return true;
194}
195
196static void throw_conversion_exception(const char *to, const zval *zv) {
197 zval tmp;
198 ZVAL_COPY(&tmp, zv);
199 convert_to_string(&tmp);
200
201 zend_throw_exception_ex(NULL, 0, "Cannot convert '%s' to %s",
202 Z_STRVAL_P(&tmp), to);
203
204 zval_ptr_dtor(&tmp);
205}
206
207bool Convert_PhpToInt64(const zval *php_val, int64_t *i64) {
208 switch (Z_TYPE_P(php_val)) {
209 case IS_LONG:
210 *i64 = Z_LVAL_P(php_val);
211 return true;
212 case IS_DOUBLE: {
213 double dbl = Z_DVAL_P(php_val);
214 if (dbl > 9223372036854774784.0 || dbl < -9223372036854775808.0) {
215 zend_throw_exception_ex(NULL, 0, "Out of range");
216 return false;
217 }
218 *i64 = dbl; /* must be guarded, overflow here is UB */
219 return true;
220 }
221 case IS_STRING: {
222 const char *buf = Z_STRVAL_P(php_val);
223 // PHP would accept scientific notation here, but we're going to be a
224 // little more discerning and only accept pure integers.
225 bool ok = buftoint64(buf, buf + Z_STRLEN_P(php_val), i64);
226 if (!ok) {
227 throw_conversion_exception("integer", php_val);
228 }
229 return ok;
230 }
231 default:
232 throw_conversion_exception("integer", php_val);
233 return false;
234 }
235}
236
237static bool to_double(zval *php_val, double *dbl) {
238 switch (Z_TYPE_P(php_val)) {
239 case IS_LONG:
240 *dbl = Z_LVAL_P(php_val);
241 return true;
242 case IS_DOUBLE:
243 *dbl = Z_DVAL_P(php_val);
244 return true;
245 case IS_STRING: {
246 zend_long lval;
247 switch (is_numeric_string(Z_STRVAL_P(php_val), Z_STRLEN_P(php_val), &lval,
248 dbl, false)) {
249 case IS_LONG:
250 *dbl = lval;
251 return true;
252 case IS_DOUBLE:
253 return true;
254 default:
255 goto fail;
256 }
257 }
258 default:
259 fail:
260 throw_conversion_exception("double", php_val);
261 return false;
262 }
263}
264
265static bool to_bool(zval* from, bool* to) {
266 switch (Z_TYPE_P(from)) {
267 case IS_TRUE:
268 *to = true;
269 return true;
270 case IS_FALSE:
271 *to = false;
272 return true;
273 case IS_LONG:
274 *to = (Z_LVAL_P(from) != 0);
275 return true;
276 case IS_DOUBLE:
277 *to = (Z_LVAL_P(from) != 0);
278 return true;
279 case IS_STRING:
280 if (Z_STRLEN_P(from) == 0 ||
281 (Z_STRLEN_P(from) == 1 && Z_STRVAL_P(from)[0] == '0')) {
282 *to = false;
283 } else {
284 *to = true;
285 }
286 return true;
287 default:
288 throw_conversion_exception("bool", from);
289 return false;
290 }
291}
292
293static bool to_string(zval* from) {
294 if (Z_ISREF_P(from)) {
295 ZVAL_DEREF(from);
296 }
297
298 switch (Z_TYPE_P(from)) {
299 case IS_STRING:
300 return true;
301 case IS_TRUE:
302 case IS_FALSE:
303 case IS_LONG:
304 case IS_DOUBLE: {
305 zval tmp;
306 zend_make_printable_zval(from, &tmp);
307 ZVAL_COPY_VALUE(from, &tmp);
308 return true;
309 }
310 default:
311 throw_conversion_exception("string", from);
312 return false;
313 }
314}
315
316bool Convert_PhpToUpb(zval *php_val, upb_msgval *upb_val, upb_fieldtype_t type,
317 const Descriptor *desc, upb_arena *arena) {
318 int64_t i64;
319
320 if (Z_ISREF_P(php_val)) {
321 ZVAL_DEREF(php_val);
322 }
323
324 switch (type) {
325 case UPB_TYPE_INT64:
326 return Convert_PhpToInt64(php_val, &upb_val->int64_val);
327 case UPB_TYPE_INT32:
328 case UPB_TYPE_ENUM:
329 if (!Convert_PhpToInt64(php_val, &i64)) {
330 return false;
331 }
332 upb_val->int32_val = i64;
333 return true;
334 case UPB_TYPE_UINT64:
335 if (!Convert_PhpToInt64(php_val, &i64)) {
336 return false;
337 }
338 upb_val->uint64_val = i64;
339 return true;
340 case UPB_TYPE_UINT32:
341 if (!Convert_PhpToInt64(php_val, &i64)) {
342 return false;
343 }
344 upb_val->uint32_val = i64;
345 return true;
346 case UPB_TYPE_DOUBLE:
347 return to_double(php_val, &upb_val->double_val);
348 case UPB_TYPE_FLOAT:
349 if (!to_double(php_val, &upb_val->double_val)) return false;
350 upb_val->float_val = upb_val->double_val;
351 return true;
352 case UPB_TYPE_BOOL:
353 return to_bool(php_val, &upb_val->bool_val);
354 case UPB_TYPE_STRING:
355 case UPB_TYPE_BYTES: {
356 char *ptr;
357 size_t size;
358
359 if (!to_string(php_val)) return false;
360
361 size = Z_STRLEN_P(php_val);
362
363 // If arena is NULL we reference the input zval.
364 // The resulting upb_strview will only be value while the zval is alive.
365 if (arena) {
366 ptr = upb_arena_malloc(arena, size);
367 memcpy(ptr, Z_STRVAL_P(php_val), size);
368 } else {
369 ptr = Z_STRVAL_P(php_val);
370 }
371
372 upb_val->str_val = upb_strview_make(ptr, size);
373 return true;
374 }
375 case UPB_TYPE_MESSAGE:
376 PBPHP_ASSERT(desc);
377 return Message_GetUpbMessage(php_val, desc, arena,
378 (upb_msg **)&upb_val->msg_val);
379 }
380
381 return false;
382}
383
384void Convert_UpbToPhp(upb_msgval upb_val, zval *php_val, upb_fieldtype_t type,
385 const Descriptor *desc, zval *arena) {
386 switch (type) {
387 case UPB_TYPE_INT64:
388#if SIZEOF_ZEND_LONG == 8
389 ZVAL_LONG(php_val, upb_val.int64_val);
390#else
391 {
392 char buf[20];
393 int size = sprintf(buf, "%lld", upb_val.int64_val);
394 ZVAL_NEW_STR(php_val, zend_string_init(buf, size, 0));
395 }
396#endif
397 break;
398 case UPB_TYPE_UINT64:
399#if SIZEOF_ZEND_LONG == 8
400 ZVAL_LONG(php_val, upb_val.uint64_val);
401#else
402 {
403 char buf[20];
404 int size = sprintf(buf, "%lld", (int64_t)upb_val.uint64_val);
405 ZVAL_NEW_STR(php_val, zend_string_init(buf, size, 0));
406 }
407#endif
408 break;
409 case UPB_TYPE_INT32:
410 case UPB_TYPE_ENUM:
411 ZVAL_LONG(php_val, upb_val.int32_val);
412 break;
413 case UPB_TYPE_UINT32: {
414 // Sign-extend for consistency between 32/64-bit builds.
415 zend_long val = (int32_t)upb_val.uint32_val;
416 ZVAL_LONG(php_val, val);
417 break;
418 }
419 case UPB_TYPE_DOUBLE:
420 ZVAL_DOUBLE(php_val, upb_val.double_val);
421 break;
422 case UPB_TYPE_FLOAT:
423 ZVAL_DOUBLE(php_val, upb_val.float_val);
424 break;
425 case UPB_TYPE_BOOL:
426 ZVAL_BOOL(php_val, upb_val.bool_val);
427 break;
428 case UPB_TYPE_STRING:
429 case UPB_TYPE_BYTES: {
430 upb_strview str = upb_val.str_val;
431 ZVAL_NEW_STR(php_val, zend_string_init(str.data, str.size, 0));
432 break;
433 }
434 case UPB_TYPE_MESSAGE:
435 PBPHP_ASSERT(desc);
436 Message_GetPhpWrapper(php_val, desc, (upb_msg*)upb_val.msg_val, arena);
437 break;
438 }
439}
440
441bool Convert_PhpToUpbAutoWrap(zval *val, upb_msgval *upb_val,
442 upb_fieldtype_t type, const Descriptor *desc,
443 upb_arena *arena) {
444 const upb_msgdef *subm = desc ? desc->msgdef : NULL;
445 if (subm && upb_msgdef_iswrapper(subm) && Z_TYPE_P(val) != IS_OBJECT) {
446 // Assigning a scalar to a wrapper-typed value. We will automatically wrap
447 // the value, so the user doesn't need to create a FooWrapper(['value': X])
448 // message manually.
449 upb_msg *wrapper = upb_msg_new(subm, arena);
450 const upb_fielddef *val_f = upb_msgdef_itof(subm, 1);
451 upb_fieldtype_t type_f = upb_fielddef_type(val_f);
452 upb_msgval msgval;
453 if (!Convert_PhpToUpb(val, &msgval, type_f, NULL, arena)) return false;
454 upb_msg_set(wrapper, val_f, msgval, arena);
455 upb_val->msg_val = wrapper;
456 return true;
457 } else {
458 // Convert_PhpToUpb doesn't auto-construct messages. This means that we only
459 // allow:
460 // ['foo_submsg': new Foo(['a' => 1])]
461 // not:
462 // ['foo_submsg': ['a' => 1]]
463 return Convert_PhpToUpb(val, upb_val, type, desc, arena);
464 }
465}
466
467void Convert_ModuleInit(void) {
468 const char *prefix_name = "TYPE_URL_PREFIX";
469 zend_class_entry class_type;
470
471 INIT_CLASS_ENTRY(class_type, "Google\\Protobuf\\Internal\\GPBUtil",
472 util_methods);
473 GPBUtil_class_entry = zend_register_internal_class(&class_type);
474
475 zend_declare_class_constant_string(GPBUtil_class_entry, prefix_name,
476 strlen(prefix_name),
477 "type.googleapis.com/");
478}