• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1<?php
2
3// Protocol Buffers - Google's data interchange format
4// Copyright 2008 Google Inc.  All rights reserved.
5// https://developers.google.com/protocol-buffers/
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are
9// met:
10//
11//     * Redistributions of source code must retain the above copyright
12// notice, this list of conditions and the following disclaimer.
13//     * Redistributions in binary form must reproduce the above
14// copyright notice, this list of conditions and the following disclaimer
15// in the documentation and/or other materials provided with the
16// distribution.
17//     * Neither the name of Google Inc. nor the names of its
18// contributors may be used to endorse or promote products derived from
19// this software without specific prior written permission.
20//
21// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
33namespace Google\Protobuf\Internal;
34
35use Google\Protobuf\Duration;
36use Google\Protobuf\FieldMask;
37use Google\Protobuf\Internal\GPBType;
38use Google\Protobuf\Internal\RepeatedField;
39use Google\Protobuf\Internal\MapField;
40use function bccomp;
41
42function camel2underscore($input) {
43    preg_match_all(
44        '!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!',
45        $input,
46        $matches);
47    $ret = $matches[0];
48    foreach ($ret as &$match) {
49        $match = $match == strtoupper($match) ? strtolower($match) : lcfirst($match);
50    }
51    return implode('_', $ret);
52}
53
54class GPBUtil
55{
56    const NANOS_PER_MILLISECOND = 1000000;
57    const NANOS_PER_MICROSECOND = 1000;
58    const TYPE_URL_PREFIX = 'type.googleapis.com/';
59
60    public static function divideInt64ToInt32($value, &$high, &$low, $trim = false)
61    {
62        $isNeg = (bccomp($value, 0) < 0);
63        if ($isNeg) {
64            $value = bcsub(0, $value);
65        }
66
67        $high = bcdiv($value, 4294967296);
68        $low = bcmod($value, 4294967296);
69        if (bccomp($high, 2147483647) > 0) {
70            $high = (int) bcsub($high, 4294967296);
71        } else {
72            $high = (int) $high;
73        }
74        if (bccomp($low, 2147483647) > 0) {
75            $low = (int) bcsub($low, 4294967296);
76        } else {
77            $low = (int) $low;
78        }
79
80        if ($isNeg) {
81            $high = ~$high;
82            $low = ~$low;
83            $low++;
84            if (!$low) {
85                $high = (int)($high + 1);
86            }
87        }
88
89        if ($trim) {
90            $high = 0;
91        }
92    }
93
94    public static function checkString(&$var, $check_utf8)
95    {
96        if (is_array($var) || is_object($var)) {
97            throw new \InvalidArgumentException("Expect string.");
98        }
99        if (!is_string($var)) {
100            $var = strval($var);
101        }
102        if ($check_utf8 && !preg_match('//u', $var)) {
103            throw new \Exception("Expect utf-8 encoding.");
104        }
105    }
106
107    public static function checkEnum(&$var)
108    {
109      static::checkInt32($var);
110    }
111
112    public static function checkInt32(&$var)
113    {
114        if (is_numeric($var)) {
115            $var = intval($var);
116        } else {
117            throw new \Exception("Expect integer.");
118        }
119    }
120
121    public static function checkUint32(&$var)
122    {
123        if (is_numeric($var)) {
124            if (PHP_INT_SIZE === 8) {
125                $var = intval($var);
126                $var |= ((-(($var >> 31) & 0x1)) & ~0xFFFFFFFF);
127            } else {
128                if (bccomp($var, 0x7FFFFFFF) > 0) {
129                    $var = bcsub($var, "4294967296");
130                }
131                $var = (int) $var;
132            }
133        } else {
134            throw new \Exception("Expect integer.");
135        }
136    }
137
138    public static function checkInt64(&$var)
139    {
140        if (is_numeric($var)) {
141            if (PHP_INT_SIZE == 8) {
142                $var = intval($var);
143            } else {
144                if (is_float($var) ||
145                    is_integer($var) ||
146                    (is_string($var) &&
147                         bccomp($var, "9223372036854774784") < 0)) {
148                    $var = number_format($var, 0, ".", "");
149                }
150            }
151        } else {
152            throw new \Exception("Expect integer.");
153        }
154    }
155
156    public static function checkUint64(&$var)
157    {
158        if (is_numeric($var)) {
159            if (PHP_INT_SIZE == 8) {
160                $var = intval($var);
161            } else {
162                $var = number_format($var, 0, ".", "");
163            }
164        } else {
165            throw new \Exception("Expect integer.");
166        }
167    }
168
169    public static function checkFloat(&$var)
170    {
171        if (is_float($var) || is_numeric($var)) {
172            $var = unpack("f", pack("f", $var))[1];
173        } else {
174            throw new \Exception("Expect float.");
175        }
176    }
177
178    public static function checkDouble(&$var)
179    {
180        if (is_float($var) || is_numeric($var)) {
181            $var = floatval($var);
182        } else {
183            throw new \Exception("Expect float.");
184        }
185    }
186
187    public static function checkBool(&$var)
188    {
189        if (is_array($var) || is_object($var)) {
190            throw new \Exception("Expect boolean.");
191        }
192        $var = boolval($var);
193    }
194
195    public static function checkMessage(&$var, $klass, $newClass = null)
196    {
197        if (!$var instanceof $klass && !is_null($var)) {
198            throw new \Exception("Expect $klass.");
199        }
200    }
201
202    public static function checkRepeatedField(&$var, $type, $klass = null)
203    {
204        if (!$var instanceof RepeatedField && !is_array($var)) {
205            throw new \Exception("Expect array.");
206        }
207        if (is_array($var)) {
208            $tmp = new RepeatedField($type, $klass);
209            foreach ($var as $value) {
210                $tmp[] = $value;
211            }
212            return $tmp;
213        } else {
214            if ($var->getType() != $type) {
215                throw new \Exception(
216                    "Expect repeated field of different type.");
217            }
218            if ($var->getType() === GPBType::MESSAGE &&
219                $var->getClass() !== $klass &&
220                $var->getLegacyClass() !== $klass) {
221                throw new \Exception(
222                    "Expect repeated field of " . $klass . ".");
223            }
224            return $var;
225        }
226    }
227
228    public static function checkMapField(&$var, $key_type, $value_type, $klass = null)
229    {
230        if (!$var instanceof MapField && !is_array($var)) {
231            throw new \Exception("Expect dict.");
232        }
233        if (is_array($var)) {
234            $tmp = new MapField($key_type, $value_type, $klass);
235            foreach ($var as $key => $value) {
236                $tmp[$key] = $value;
237            }
238            return $tmp;
239        } else {
240            if ($var->getKeyType() != $key_type) {
241                throw new \Exception("Expect map field of key type.");
242            }
243            if ($var->getValueType() != $value_type) {
244                throw new \Exception("Expect map field of value type.");
245            }
246            if ($var->getValueType() === GPBType::MESSAGE &&
247                $var->getValueClass() !== $klass &&
248                $var->getLegacyValueClass() !== $klass) {
249                throw new \Exception(
250                    "Expect map field of " . $klass . ".");
251            }
252            return $var;
253        }
254    }
255
256    public static function Int64($value)
257    {
258        return new Int64($value);
259    }
260
261    public static function Uint64($value)
262    {
263        return new Uint64($value);
264    }
265
266    public static function getClassNamePrefix(
267        $classname,
268        $file_proto)
269    {
270        $option = $file_proto->getOptions();
271        $prefix = is_null($option) ? "" : $option->getPhpClassPrefix();
272        if ($prefix !== "") {
273            return $prefix;
274        }
275
276        $reserved_words = array(
277            "abstract"=>0, "and"=>0, "array"=>0, "as"=>0, "break"=>0,
278            "callable"=>0, "case"=>0, "catch"=>0, "class"=>0, "clone"=>0,
279            "const"=>0, "continue"=>0, "declare"=>0, "default"=>0, "die"=>0,
280            "do"=>0, "echo"=>0, "else"=>0, "elseif"=>0, "empty"=>0,
281            "enddeclare"=>0, "endfor"=>0, "endforeach"=>0, "endif"=>0,
282            "endswitch"=>0, "endwhile"=>0, "eval"=>0, "exit"=>0, "extends"=>0,
283            "final"=>0, "finally"=>0, "fn"=>0, "for"=>0, "foreach"=>0,
284            "function"=>0, "global"=>0, "goto"=>0, "if"=>0, "implements"=>0,
285            "include"=>0, "include_once"=>0, "instanceof"=>0, "insteadof"=>0,
286            "interface"=>0, "isset"=>0, "list"=>0, "match"=>0, "namespace"=>0,
287            "new"=>0, "or"=>0, "parent"=>0, "print"=>0, "private"=>0,
288            "protected"=>0,"public"=>0, "require"=>0, "require_once"=>0,
289            "return"=>0, "self"=>0, "static"=>0, "switch"=>0, "throw"=>0,
290            "trait"=>0, "try"=>0,"unset"=>0, "use"=>0, "var"=>0, "while"=>0,
291            "xor"=>0, "yield"=>0, "int"=>0, "float"=>0, "bool"=>0, "string"=>0,
292            "true"=>0, "false"=>0, "null"=>0, "void"=>0, "iterable"=>0
293        );
294
295        if (array_key_exists(strtolower($classname), $reserved_words)) {
296            if ($file_proto->getPackage() === "google.protobuf") {
297                return "GPB";
298            } else {
299                return "PB";
300            }
301        }
302
303        return "";
304    }
305
306    public static function getLegacyClassNameWithoutPackage(
307        $name,
308        $file_proto)
309    {
310        $classname = implode('_', explode('.', $name));
311        return static::getClassNamePrefix($classname, $file_proto) . $classname;
312    }
313
314    public static function getClassNameWithoutPackage(
315        $name,
316        $file_proto)
317    {
318        $parts = explode('.', $name);
319        foreach ($parts as $i => $part) {
320            $parts[$i] = static::getClassNamePrefix($parts[$i], $file_proto) . $parts[$i];
321        }
322        return implode('\\', $parts);
323    }
324
325    public static function getFullClassName(
326        $proto,
327        $containing,
328        $file_proto,
329        &$message_name_without_package,
330        &$classname,
331        &$legacy_classname,
332        &$fullname)
333    {
334        // Full name needs to start with '.'.
335        $message_name_without_package = $proto->getName();
336        if ($containing !== "") {
337            $message_name_without_package =
338                $containing . "." . $message_name_without_package;
339        }
340
341        $package = $file_proto->getPackage();
342        if ($package === "") {
343            $fullname = $message_name_without_package;
344        } else {
345            $fullname = $package . "." . $message_name_without_package;
346        }
347
348        $class_name_without_package =
349            static::getClassNameWithoutPackage($message_name_without_package, $file_proto);
350        $legacy_class_name_without_package =
351            static::getLegacyClassNameWithoutPackage(
352                $message_name_without_package, $file_proto);
353
354        $option = $file_proto->getOptions();
355        if (!is_null($option) && $option->hasPhpNamespace()) {
356            $namespace = $option->getPhpNamespace();
357            if ($namespace !== "") {
358                $classname = $namespace . "\\" . $class_name_without_package;
359                $legacy_classname =
360                    $namespace . "\\" . $legacy_class_name_without_package;
361                return;
362            } else {
363                $classname = $class_name_without_package;
364                $legacy_classname = $legacy_class_name_without_package;
365                return;
366            }
367        }
368
369        if ($package === "") {
370            $classname = $class_name_without_package;
371            $legacy_classname = $legacy_class_name_without_package;
372        } else {
373            $parts = array_map('ucwords', explode('.', $package));
374            foreach ($parts as $i => $part) {
375                $parts[$i] = self::getClassNamePrefix($part, $file_proto).$part;
376            }
377            $classname =
378                implode('\\', $parts) .
379                "\\".self::getClassNamePrefix($class_name_without_package,$file_proto).
380                $class_name_without_package;
381            $legacy_classname =
382                implode('\\', array_map('ucwords', explode('.', $package))).
383                "\\".$legacy_class_name_without_package;
384        }
385    }
386
387    public static function combineInt32ToInt64($high, $low)
388    {
389        $isNeg = $high < 0;
390        if ($isNeg) {
391            $high = ~$high;
392            $low = ~$low;
393            $low++;
394            if (!$low) {
395                $high = (int) ($high + 1);
396            }
397        }
398        $result = bcadd(bcmul($high, 4294967296), $low);
399        if ($low < 0) {
400            $result = bcadd($result, 4294967296);
401        }
402        if ($isNeg) {
403          $result = bcsub(0, $result);
404        }
405        return $result;
406    }
407
408    public static function parseTimestamp($timestamp)
409    {
410        // prevent parsing timestamps containing with the non-existent year "0000"
411        // DateTime::createFromFormat parses without failing but as a nonsensical date
412        if (substr($timestamp, 0, 4) === "0000") {
413            throw new \Exception("Year cannot be zero.");
414        }
415        // prevent parsing timestamps ending with a lowercase z
416        if (substr($timestamp, -1, 1) === "z") {
417            throw new \Exception("Timezone cannot be a lowercase z.");
418        }
419
420        $nanoseconds = 0;
421        $periodIndex = strpos($timestamp, ".");
422        if ($periodIndex !== false) {
423            $nanosecondsLength = 0;
424            // find the next non-numeric character in the timestamp to calculate
425            // the length of the nanoseconds text
426            for ($i = $periodIndex + 1, $length = strlen($timestamp); $i < $length; $i++) {
427                if (!is_numeric($timestamp[$i])) {
428                    $nanosecondsLength = $i - ($periodIndex + 1);
429                    break;
430                }
431            }
432            if ($nanosecondsLength % 3 !== 0) {
433                throw new \Exception("Nanoseconds must be disible by 3.");
434            }
435            if ($nanosecondsLength > 9) {
436                throw new \Exception("Nanoseconds must be in the range of 0 to 999,999,999 nanoseconds.");
437            }
438            if ($nanosecondsLength > 0) {
439                $nanoseconds = substr($timestamp, $periodIndex + 1, $nanosecondsLength);
440                $nanoseconds = intval($nanoseconds);
441
442                // remove the nanoseconds and preceding period from the timestamp
443                $date = substr($timestamp, 0, $periodIndex);
444                $timezone = substr($timestamp, $periodIndex + $nanosecondsLength + 1);
445                $timestamp = $date.$timezone;
446            }
447        }
448
449        $date = \DateTime::createFromFormat(\DateTime::RFC3339, $timestamp, new \DateTimeZone("UTC"));
450        if ($date === false) {
451            throw new \Exception("Invalid RFC 3339 timestamp.");
452        }
453
454        $value = new \Google\Protobuf\Timestamp();
455        $seconds = $date->format("U");
456        $value->setSeconds($seconds);
457        $value->setNanos($nanoseconds);
458        return $value;
459    }
460
461    public static function formatTimestamp($value)
462    {
463        if (bccomp($value->getSeconds(), "253402300800") != -1) {
464          throw new GPBDecodeException("Duration number too large.");
465        }
466        if (bccomp($value->getSeconds(), "-62135596801") != 1) {
467          throw new GPBDecodeException("Duration number too small.");
468        }
469        $nanoseconds = static::getNanosecondsForTimestamp($value->getNanos());
470        if (!empty($nanoseconds)) {
471            $nanoseconds = ".".$nanoseconds;
472        }
473        $date = new \DateTime('@'.$value->getSeconds(), new \DateTimeZone("UTC"));
474        return $date->format("Y-m-d\TH:i:s".$nanoseconds."\Z");
475    }
476
477    public static function parseDuration($value)
478    {
479        if (strlen($value) < 2 || substr($value, -1) !== "s") {
480          throw new GPBDecodeException("Missing s after duration string");
481        }
482        $number = substr($value, 0, -1);
483        if (bccomp($number, "315576000001") != -1) {
484          throw new GPBDecodeException("Duration number too large.");
485        }
486        if (bccomp($number, "-315576000001") != 1) {
487          throw new GPBDecodeException("Duration number too small.");
488        }
489        $pos = strrpos($number, ".");
490        if ($pos !== false) {
491            $seconds = substr($number, 0, $pos);
492            if (bccomp($seconds, 0) < 0) {
493                $nanos = bcmul("0" . substr($number, $pos), -1000000000);
494            } else {
495                $nanos = bcmul("0" . substr($number, $pos), 1000000000);
496            }
497        } else {
498            $seconds = $number;
499            $nanos = 0;
500        }
501        $duration = new Duration();
502        $duration->setSeconds($seconds);
503        $duration->setNanos($nanos);
504        return $duration;
505    }
506
507    public static function formatDuration($value)
508    {
509        if (bccomp($value->getSeconds(), '315576000001') != -1) {
510            throw new GPBDecodeException('Duration number too large.');
511        }
512        if (bccomp($value->getSeconds(), '-315576000001') != 1) {
513            throw new GPBDecodeException('Duration number too small.');
514        }
515
516        $nanos = $value->getNanos();
517        if ($nanos === 0) {
518            return (string) $value->getSeconds();
519        }
520
521        if ($nanos % 1000000 === 0) {
522            $digits = 3;
523        } elseif ($nanos % 1000 === 0) {
524            $digits = 6;
525        } else {
526            $digits = 9;
527        }
528
529        $nanos = bcdiv($nanos, '1000000000', $digits);
530        return bcadd($value->getSeconds(), $nanos, $digits);
531    }
532
533    public static function parseFieldMask($paths_string)
534    {
535        $field_mask = new FieldMask();
536        if (strlen($paths_string) === 0) {
537            return $field_mask;
538        }
539        $path_strings = explode(",", $paths_string);
540        $paths = $field_mask->getPaths();
541        foreach($path_strings as &$path_string) {
542            $field_strings = explode(".", $path_string);
543            foreach($field_strings as &$field_string) {
544                $field_string = camel2underscore($field_string);
545            }
546            $path_string = implode(".", $field_strings);
547            $paths[] = $path_string;
548        }
549        return $field_mask;
550    }
551
552    public static function formatFieldMask($field_mask)
553    {
554        $converted_paths = [];
555        foreach($field_mask->getPaths() as $path) {
556            $fields = explode('.', $path);
557            $converted_path = [];
558            foreach ($fields as $field) {
559                $segments = explode('_', $field);
560                $start = true;
561                $converted_segments = "";
562                foreach($segments as $segment) {
563                  if (!$start) {
564                    $converted = ucfirst($segment);
565                  } else {
566                    $converted = $segment;
567                    $start = false;
568                  }
569                  $converted_segments .= $converted;
570                }
571                $converted_path []= $converted_segments;
572            }
573            $converted_path = implode(".", $converted_path);
574            $converted_paths []= $converted_path;
575        }
576        return implode(",", $converted_paths);
577    }
578
579    public static function getNanosecondsForTimestamp($nanoseconds)
580    {
581        if ($nanoseconds == 0) {
582            return '';
583        }
584        if ($nanoseconds % static::NANOS_PER_MILLISECOND == 0) {
585            return sprintf('%03d', $nanoseconds / static::NANOS_PER_MILLISECOND);
586        }
587        if ($nanoseconds % static::NANOS_PER_MICROSECOND == 0) {
588            return sprintf('%06d', $nanoseconds / static::NANOS_PER_MICROSECOND);
589        }
590        return sprintf('%09d', $nanoseconds);
591    }
592
593    public static function hasSpecialJsonMapping($msg)
594    {
595        return is_a($msg, 'Google\Protobuf\Any')         ||
596               is_a($msg, "Google\Protobuf\ListValue")   ||
597               is_a($msg, "Google\Protobuf\Struct")      ||
598               is_a($msg, "Google\Protobuf\Value")       ||
599               is_a($msg, "Google\Protobuf\Duration")    ||
600               is_a($msg, "Google\Protobuf\Timestamp")   ||
601               is_a($msg, "Google\Protobuf\FieldMask")   ||
602               static::hasJsonValue($msg);
603    }
604
605    public static function hasJsonValue($msg)
606    {
607        return is_a($msg, "Google\Protobuf\DoubleValue") ||
608               is_a($msg, "Google\Protobuf\FloatValue")  ||
609               is_a($msg, "Google\Protobuf\Int64Value")  ||
610               is_a($msg, "Google\Protobuf\UInt64Value") ||
611               is_a($msg, "Google\Protobuf\Int32Value")  ||
612               is_a($msg, "Google\Protobuf\UInt32Value") ||
613               is_a($msg, "Google\Protobuf\BoolValue")   ||
614               is_a($msg, "Google\Protobuf\StringValue") ||
615               is_a($msg, "Google\Protobuf\BytesValue");
616    }
617}
618