1<?php 2 3// Protocol Buffers - Google's data interchange format 4// Copyright 2008 Google Inc. All rights reserved. 5// 6// Use of this source code is governed by a BSD-style 7// license that can be found in the LICENSE file or at 8// https://developers.google.com/open-source/licenses/bsd 9 10namespace Google\Protobuf\Internal; 11 12class GPBJsonWire 13{ 14 15 public static function serializeFieldToStream( 16 $value, 17 $field, 18 &$output, $has_field_name = true) 19 { 20 if ($has_field_name) { 21 $output->writeRaw("\"", 1); 22 $field_name = GPBJsonWire::formatFieldName($field); 23 $output->writeRaw($field_name, strlen($field_name)); 24 $output->writeRaw("\":", 2); 25 } 26 return static::serializeFieldValueToStream( 27 $value, 28 $field, 29 $output, 30 !$has_field_name); 31 } 32 33 public static function serializeFieldValueToStream( 34 $values, 35 $field, 36 &$output, 37 $is_well_known = false) 38 { 39 if ($field->isMap()) { 40 $output->writeRaw("{", 1); 41 $first = true; 42 $map_entry = $field->getMessageType(); 43 $key_field = $map_entry->getFieldByNumber(1); 44 $value_field = $map_entry->getFieldByNumber(2); 45 46 switch ($key_field->getType()) { 47 case GPBType::STRING: 48 case GPBType::SFIXED64: 49 case GPBType::INT64: 50 case GPBType::SINT64: 51 case GPBType::FIXED64: 52 case GPBType::UINT64: 53 $additional_quote = false; 54 break; 55 default: 56 $additional_quote = true; 57 } 58 59 foreach ($values as $key => $value) { 60 if ($first) { 61 $first = false; 62 } else { 63 $output->writeRaw(",", 1); 64 } 65 if ($additional_quote) { 66 $output->writeRaw("\"", 1); 67 } 68 if (!static::serializeSingularFieldValueToStream( 69 $key, 70 $key_field, 71 $output, 72 $is_well_known)) { 73 return false; 74 } 75 if ($additional_quote) { 76 $output->writeRaw("\"", 1); 77 } 78 $output->writeRaw(":", 1); 79 if (!static::serializeSingularFieldValueToStream( 80 $value, 81 $value_field, 82 $output, 83 $is_well_known)) { 84 return false; 85 } 86 } 87 $output->writeRaw("}", 1); 88 return true; 89 } elseif ($field->isRepeated()) { 90 $output->writeRaw("[", 1); 91 $first = true; 92 foreach ($values as $value) { 93 if ($first) { 94 $first = false; 95 } else { 96 $output->writeRaw(",", 1); 97 } 98 if (!static::serializeSingularFieldValueToStream( 99 $value, 100 $field, 101 $output, 102 $is_well_known)) { 103 return false; 104 } 105 } 106 $output->writeRaw("]", 1); 107 return true; 108 } else { 109 return static::serializeSingularFieldValueToStream( 110 $values, 111 $field, 112 $output, 113 $is_well_known); 114 } 115 } 116 117 private static function serializeSingularFieldValueToStream( 118 $value, 119 $field, 120 &$output, $is_well_known = false) 121 { 122 switch ($field->getType()) { 123 case GPBType::SFIXED32: 124 case GPBType::SINT32: 125 case GPBType::INT32: 126 $str_value = strval($value); 127 $output->writeRaw($str_value, strlen($str_value)); 128 break; 129 case GPBType::FIXED32: 130 case GPBType::UINT32: 131 if ($value < 0) { 132 $value = bcadd($value, "4294967296"); 133 } 134 $str_value = strval($value); 135 $output->writeRaw($str_value, strlen($str_value)); 136 break; 137 case GPBType::FIXED64: 138 case GPBType::UINT64: 139 if ($value < 0) { 140 $value = bcadd($value, "18446744073709551616"); 141 } 142 // Intentional fall through. 143 case GPBType::SFIXED64: 144 case GPBType::INT64: 145 case GPBType::SINT64: 146 $output->writeRaw("\"", 1); 147 $str_value = strval($value); 148 $output->writeRaw($str_value, strlen($str_value)); 149 $output->writeRaw("\"", 1); 150 break; 151 case GPBType::FLOAT: 152 if (is_nan($value)) { 153 $str_value = "\"NaN\""; 154 } elseif ($value === INF) { 155 $str_value = "\"Infinity\""; 156 } elseif ($value === -INF) { 157 $str_value = "\"-Infinity\""; 158 } else { 159 $str_value = sprintf("%.8g", $value); 160 } 161 $output->writeRaw($str_value, strlen($str_value)); 162 break; 163 case GPBType::DOUBLE: 164 if (is_nan($value)) { 165 $str_value = "\"NaN\""; 166 } elseif ($value === INF) { 167 $str_value = "\"Infinity\""; 168 } elseif ($value === -INF) { 169 $str_value = "\"-Infinity\""; 170 } else { 171 $str_value = sprintf("%.17g", $value); 172 } 173 $output->writeRaw($str_value, strlen($str_value)); 174 break; 175 case GPBType::ENUM: 176 $enum_desc = $field->getEnumType(); 177 if ($enum_desc->getClass() === "Google\Protobuf\NullValue") { 178 $output->writeRaw("null", 4); 179 break; 180 } 181 $enum_value_desc = $enum_desc->getValueByNumber($value); 182 if (!is_null($enum_value_desc)) { 183 $str_value = $enum_value_desc->getName(); 184 $output->writeRaw("\"", 1); 185 $output->writeRaw($str_value, strlen($str_value)); 186 $output->writeRaw("\"", 1); 187 } else { 188 $str_value = strval($value); 189 $output->writeRaw($str_value, strlen($str_value)); 190 } 191 break; 192 case GPBType::BOOL: 193 if ($value) { 194 $output->writeRaw("true", 4); 195 } else { 196 $output->writeRaw("false", 5); 197 } 198 break; 199 case GPBType::BYTES: 200 $bytes_value = base64_encode($value); 201 $output->writeRaw("\"", 1); 202 $output->writeRaw($bytes_value, strlen($bytes_value)); 203 $output->writeRaw("\"", 1); 204 break; 205 case GPBType::STRING: 206 $value = json_encode($value, JSON_UNESCAPED_UNICODE); 207 $output->writeRaw($value, strlen($value)); 208 break; 209 // case GPBType::GROUP: 210 // echo "GROUP\xA"; 211 // trigger_error("Not implemented.", E_ERROR); 212 // break; 213 case GPBType::MESSAGE: 214 $value->serializeToJsonStream($output); 215 break; 216 default: 217 user_error("Unsupported type."); 218 return false; 219 } 220 return true; 221 } 222 223 private static function formatFieldName($field) 224 { 225 return $field->getJsonName(); 226 } 227 228 // Used for escaping control chars in strings. 229 private static $k_control_char_limit = 0x20; 230 231 private static function jsonNiceEscape($c) 232 { 233 switch ($c) { 234 case '"': return "\\\""; 235 case '\\': return "\\\\"; 236 case '/': return "\\/"; 237 case '\b': return "\\b"; 238 case '\f': return "\\f"; 239 case '\n': return "\\n"; 240 case '\r': return "\\r"; 241 case '\t': return "\\t"; 242 default: return NULL; 243 } 244 } 245 246 private static function isJsonEscaped($c) 247 { 248 // See RFC 4627. 249 return $c < chr($k_control_char_limit) || $c === "\"" || $c === "\\"; 250 } 251 252 public static function escapedJson($value) 253 { 254 $escaped_value = ""; 255 $unescaped_run = ""; 256 for ($i = 0; $i < strlen($value); $i++) { 257 $c = $value[$i]; 258 // Handle escaping. 259 if (static::isJsonEscaped($c)) { 260 // Use a "nice" escape, like \n, if one exists for this 261 // character. 262 $escape = static::jsonNiceEscape($c); 263 if (is_null($escape)) { 264 $escape = "\\u00" . bin2hex($c); 265 } 266 if ($unescaped_run !== "") { 267 $escaped_value .= $unescaped_run; 268 $unescaped_run = ""; 269 } 270 $escaped_value .= $escape; 271 } else { 272 if ($unescaped_run === "") { 273 $unescaped_run .= $c; 274 } 275 } 276 } 277 $escaped_value .= $unescaped_run; 278 return $escaped_value; 279 } 280 281} 282