Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
15.56% |
14 / 90 |
|
12.50% |
1 / 8 |
CRAP | |
0.00% |
0 / 1 |
| BaseMessage | |
15.56% |
14 / 90 |
|
12.50% |
1 / 8 |
954.89 | |
0.00% |
0 / 1 |
| toJSON | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| fromJSON | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
42 | |||
| fromEncoded | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
20 | |||
| fromEncodedArray | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
| encode | |
39.39% |
13 / 33 |
|
0.00% |
0 / 1 |
50.62 | |||
| decode | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
132 | |||
| clearFields | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
| setCipherParams | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| 1 | <?php |
| 2 | namespace Ably\Models; |
| 3 | |
| 4 | use Ably\Exceptions\AblyException; |
| 5 | use Ably\Utils\Crypto; |
| 6 | use Ably\Log; |
| 7 | |
| 8 | /** |
| 9 | * Base class for messages sent over channels. |
| 10 | * Provides automatic encoding and decoding. |
| 11 | */ |
| 12 | abstract class BaseMessage { |
| 13 | |
| 14 | /** |
| 15 | * @var string Unique ID for this message. Populated by the system. |
| 16 | */ |
| 17 | public $id; |
| 18 | /** |
| 19 | * @var mixed|null The message payload. |
| 20 | */ |
| 21 | public $data; |
| 22 | /** |
| 23 | * @var string|null The clientId of the client that published the message. |
| 24 | * This property is populated by the system, where the clientId is known. |
| 25 | */ |
| 26 | public $clientId; |
| 27 | /** |
| 28 | * @var string|null ID of the connection. |
| 29 | * This property is populated by the system. |
| 30 | */ |
| 31 | public $connectionId; |
| 32 | /** |
| 33 | * @var int The timestamp of this message. Populated by the system. |
| 34 | */ |
| 35 | public $timestamp; |
| 36 | /** |
| 37 | * @var string|null Transformations to be applied to this message. |
| 38 | * If specified for new messages, it is assumed that $data is already encoded |
| 39 | * in the specified format, including any encryption. |
| 40 | * Otherwise the encoding is automatically inferred from $data. |
| 41 | */ |
| 42 | public $encoding; |
| 43 | /** |
| 44 | * @var mixed Original received data, without any transformations, ignored when sending. |
| 45 | */ |
| 46 | public $originalData; |
| 47 | /** |
| 48 | * @var mixed Original received encoding, ignored when sending. |
| 49 | */ |
| 50 | public $originalEncoding; |
| 51 | |
| 52 | /** |
| 53 | * @var CipherParams|null Cipher parameters for encrypted messages. |
| 54 | */ |
| 55 | protected $cipherParams; |
| 56 | |
| 57 | /** |
| 58 | * Creates a JSON representation of this message, ready to be sent to the API. |
| 59 | * |
| 60 | * If there is an $encoding specified, the data will be left untouched. |
| 61 | * If not specified the encoding is inferred from the type of $data and any transformations |
| 62 | * such as base64 encoding for binary data and encryption are applied. |
| 63 | */ |
| 64 | public function toJSON() { |
| 65 | return json_encode( $this->encode() ); |
| 66 | } |
| 67 | |
| 68 | /** |
| 69 | * Populates the message from JSON and automatically decodes data. |
| 70 | * @param string|stdClass $json JSON string or an already decoded object. |
| 71 | * @param bool $keepOriginal When set to true, the message won't be decoded or decrypted |
| 72 | * @throws AblyException |
| 73 | */ |
| 74 | public function fromJSON( $json, $keepOriginal = false ) { |
| 75 | $this->clearFields(); |
| 76 | |
| 77 | if (is_object( $json )) { |
| 78 | $obj = $json; |
| 79 | } else { |
| 80 | $obj = @json_decode($json); |
| 81 | if (!$obj) { |
| 82 | throw new AblyException( 'Invalid object or JSON encoded object' ); |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | $class = get_class( $this ); |
| 87 | foreach ($obj as $key => $value) { |
| 88 | if (property_exists( $class, $key )) { |
| 89 | $this->$key = $value; |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | if ($keepOriginal) return; |
| 94 | |
| 95 | $this->decode(); |
| 96 | } |
| 97 | |
| 98 | /** |
| 99 | * Creates and returns a new message from the given encoded message like object |
| 100 | * @param stdClass $obj Message-like object |
| 101 | * @param CipherParams|null $cipherParams |
| 102 | */ |
| 103 | public static function fromEncoded( $obj, CipherParams $cipherParams = null ) { |
| 104 | $class = get_called_class(); |
| 105 | |
| 106 | $msg = new $class(); |
| 107 | if ($cipherParams != null) { |
| 108 | $msg->setCipherParams( $cipherParams ); |
| 109 | } |
| 110 | |
| 111 | foreach ($obj as $key => $value) { |
| 112 | if (property_exists( $class, $key )) { |
| 113 | $msg->$key = $value; |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | $msg->decode(); |
| 118 | |
| 119 | return $msg; |
| 120 | } |
| 121 | |
| 122 | /** |
| 123 | * Creates and returns a new message from the given encoded message like object |
| 124 | * @param array $objs Array of Message-Like objects |
| 125 | * @param CipherParams|null $cipherParams |
| 126 | */ |
| 127 | public static function fromEncodedArray( $objs, CipherParams $cipherParams = null ) { |
| 128 | return array_map( |
| 129 | function( $obj ) use ($cipherParams) { return static::fromEncoded($obj, $cipherParams); }, |
| 130 | $objs |
| 131 | ); |
| 132 | } |
| 133 | |
| 134 | /** |
| 135 | * Returns an encoded message as a stdClass ready for stringifying |
| 136 | */ |
| 137 | protected function encode() { |
| 138 | $msg = new \stdClass(); |
| 139 | |
| 140 | if ($this->encoding) { |
| 141 | $msg->encoding = $this->encoding; |
| 142 | $msg->data = $this->data; |
| 143 | |
| 144 | return $msg; |
| 145 | } |
| 146 | |
| 147 | if ($this->clientId) { |
| 148 | $msg->clientId = $this->clientId; |
| 149 | } |
| 150 | |
| 151 | $isBinary = false; |
| 152 | $encodings = []; |
| 153 | |
| 154 | if ( is_array( $this->data ) || $this->data instanceof \stdClass ) { |
| 155 | $encodings[] = 'json'; |
| 156 | $msg->data = json_encode($this->data); |
| 157 | } else if ( is_string( $this->data ) ){ |
| 158 | if ( mb_check_encoding( $this->data, 'UTF-8' ) ) { // it's a UTF-8 string |
| 159 | $msg->data = $this->data; |
| 160 | } else { // not UTF-8, assuming it's a binary string |
| 161 | $msg->data = $this->data; |
| 162 | $isBinary = true; |
| 163 | } |
| 164 | } else if ( !isset( $this->data ) || $this->data === null ) { |
| 165 | return $msg; |
| 166 | } else { |
| 167 | throw new AblyException( 'Message data must be either, string, string with binary data, JSON-encodable array or object, or null.', 40003, 400 ); |
| 168 | } |
| 169 | |
| 170 | if ( $this->cipherParams ) { |
| 171 | if ( !$isBinary ) { |
| 172 | $encodings[] = 'utf-8'; |
| 173 | } |
| 174 | |
| 175 | $msg->data = base64_encode( Crypto::encrypt( $msg->data, $this->cipherParams ) ); |
| 176 | $encodings[] = 'cipher+' . $this->cipherParams->getAlgorithmString(); |
| 177 | $encodings[] = 'base64'; |
| 178 | } else { |
| 179 | if ( $isBinary ) { |
| 180 | $msg->data = base64_encode( $this->data ); |
| 181 | $encodings[] = 'base64'; |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | if ( count( $encodings ) ) { |
| 186 | $msg->encoding = implode( '/', $encodings ); |
| 187 | } else { |
| 188 | $msg->encoding = ''; |
| 189 | } |
| 190 | |
| 191 | return $msg; |
| 192 | } |
| 193 | |
| 194 | /** |
| 195 | * Decodes message's data field according to encoding |
| 196 | * @throws AblyException |
| 197 | */ |
| 198 | protected function decode() { |
| 199 | $this->originalData = $this->data; |
| 200 | $this->originalEncoding = $this->encoding; |
| 201 | |
| 202 | if (!empty( $this->encoding )) { |
| 203 | $encodings = explode( '/', $this->encoding ); |
| 204 | |
| 205 | foreach (array_reverse( $encodings ) as $encoding) { |
| 206 | if ($encoding == 'base64') { |
| 207 | $this->data = base64_decode( $this->data ); |
| 208 | |
| 209 | if ($this->data === false) { |
| 210 | throw new AblyException( 'Could not base64-decode message data' ); |
| 211 | } |
| 212 | |
| 213 | array_pop( $encodings ); |
| 214 | } else if ($encoding == 'json') { |
| 215 | $this->data = json_decode( $this->data ); |
| 216 | |
| 217 | if ($this->data === null) { |
| 218 | throw new AblyException( 'Could not JSON-decode message data' ); |
| 219 | } |
| 220 | |
| 221 | array_pop( $encodings ); |
| 222 | } else if (strpos( $encoding, 'cipher+' ) === 0) { |
| 223 | if (!$this->cipherParams) { |
| 224 | Log::e( 'Could not decrypt message data, no cipherParams provided' ); |
| 225 | break; |
| 226 | } |
| 227 | |
| 228 | $data = Crypto::decrypt( $this->data, $this->cipherParams ); |
| 229 | |
| 230 | if ($data === false) { |
| 231 | Log::e( 'Could not decrypt message data' ); |
| 232 | break; |
| 233 | } |
| 234 | |
| 235 | $this->data = $data; |
| 236 | array_pop( $encodings ); |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | $this->encoding = count( $encodings ) ? implode( '/', $encodings ) : null; |
| 241 | } |
| 242 | } |
| 243 | |
| 244 | /** |
| 245 | * Sets all the public fields to null |
| 246 | */ |
| 247 | protected function clearFields() { |
| 248 | $fields = get_object_vars( $this ); |
| 249 | unset( $fields['cipherParams'] ); |
| 250 | |
| 251 | foreach ($fields as $key => $value) { |
| 252 | $this->$key = null; |
| 253 | } |
| 254 | } |
| 255 | |
| 256 | /** |
| 257 | * Sets cipher parameters for this message for automatic encryption and decryption. |
| 258 | * @param CipherParams $cipherParams |
| 259 | */ |
| 260 | public function setCipherParams( CipherParams $cipherParams ) { |
| 261 | $this->cipherParams = $cipherParams; |
| 262 | } |
| 263 | } |