├── .gitignore ├── README.md ├── composer.json └── lib └── mega.class.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /.settings 3 | /.buildpath 4 | /.project 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MEGA PHP Client Library 2 | ======================= 3 | 4 | PHP client library for the [MEGA API](https://mega.co.nz/#developers). 5 | 6 | *Note: This library is still under development and incomplete, so the API is subject to change.* 7 | 8 | Requirements 9 | ------------ 10 | 11 | * PHP 5.x 12 | * PHP Mcrypt extension 13 | * PHP OpenSSL extension 14 | * PHP cURL extension 15 | 16 | Creating the client 17 | ------------------- 18 | 19 | ### Using the constructor 20 | 21 | ```php 22 | $mega = new MEGA(); 23 | ``` 24 | 25 | ### Using a static factory method 26 | 27 | ```php 28 | $mega = MEGA::create_from_login($email, $password); 29 | ``` 30 | 31 | This is equivalent to: 32 | 33 | ```php 34 | $mega = new MEGA(); 35 | $mega->user_login_session($email, $password); 36 | ``` 37 | 38 | ### Changing the default MEGA API server 39 | 40 | ```php 41 | MEGA::set_default_server(MEGA::SERVER_EUROPE); 42 | ``` 43 | 44 | Working with public files 45 | ------------------------- 46 | 47 | Download public files not require authentication. 48 | 49 | ### Gettings file info 50 | 51 | ```php 52 | $mega = new MEGA(); 53 | 54 | $file_info = $mega->public_file_info($ph, $key); 55 | var_dump($file_info); 56 | 57 | // Print filename and size 58 | echo 'Filename: ' . $file_info['at']['n']; 59 | echo 'Size: ' . $file_info['s']; 60 | ``` 61 | 62 | ### Using links 63 | 64 | ```php 65 | $file_info = $mega->public_file_info_from_link($link); 66 | ``` 67 | 68 | This is equivalents to: 69 | 70 | ```php 71 | $info = MEGA::parse_link($link); 72 | $file_info = $mega->public_file_info($info['ph'], $info['key']); 73 | ``` 74 | 75 | ### Downloading public files 76 | 77 | ```php 78 | // Save file to current directory. 79 | $filepath = $mega->public_file_save($ph, $key); 80 | echo 'File saved in ' . $filepath; 81 | 82 | // Equivalent using exported link 83 | $filepath = $mega->public_file_save_from_link($link); 84 | echo 'File saved in ' . $filepath; 85 | ``` 86 | 87 | See below for more examples. 88 | 89 | Downloading files 90 | ----------------- 91 | 92 | ### Using streams 93 | 94 | ```php 95 | // Write to file 96 | $fp = fopen($file, 'wb'); 97 | $size = $mega->public_file_download($ph, $key, $fp); 98 | fclose($fp); 99 | ``` 100 | 101 | ### Returning content 102 | 103 | ```php 104 | // Get content using temporary stream 105 | $content = $mega->public_file_download($ph, $key); 106 | ``` 107 | 108 | ### Saving to disk 109 | 110 | ```php 111 | // Save file to temporary directory. 112 | $tmp = sys_get_temp_dir(); 113 | $file = $mega->public_file_save($ph, $key, $tmp); 114 | echo 'File saved in ' . $file; 115 | ``` 116 | 117 | Private files 118 | ------------- 119 | 120 | ### Listing 121 | 122 | ```php 123 | $mega = MEGA::create_from_user($email, $password); 124 | 125 | $files = $mega->node_list(); 126 | print_r($files); 127 | 128 | // Get file info 129 | $file_info = $mega->node_file_info($files['f'][5]); 130 | print_r($file_info); 131 | ``` 132 | 133 | ### Downloading 134 | 135 | * The ```node_file_save()``` function is equivalent to ```public_file_save()``` 136 | * The ```node_file_download()``` function is equivalent to ```public_file_download()``` 137 | 138 | User session 139 | ------------ 140 | 141 | ### Saving session 142 | 143 | ```php 144 | $mega = MEGA::create_from_user($email, $password); 145 | 146 | // ... 147 | 148 | // Get current session as a base64 string 149 | $session = MEGA::session_save($mega); 150 | 151 | // Store in a safe place! 152 | db_store_session($session); 153 | ``` 154 | 155 | ### Restoring session 156 | 157 | ```php 158 | // Retrive saved session 159 | $session = db_get_session(); 160 | 161 | // Create client from previous session 162 | $mega = MEGA::create_from_session($session); 163 | 164 | // ... 165 | ``` 166 | 167 | Status 168 | ------ 169 | 170 | ### Operations that don't require user authentication 171 | 172 | | Method | Description | Status | 173 | | ------ | ----------- | ------ | 174 | | `public_file_info` | Request public file info | Implemented | 175 | | `public_file_info_from_link` | Request public file info | Implemented | 176 | | `public_file_download` | Download a public file | Implemented | 177 | | `public_file_download_from_link` | Download a public file | Implemented | 178 | | `public_file_save` | Download and save a public file to disk | Implemented | 179 | | `public_file_save_from_link` | Download and save a public file to disk | Implemented | 180 | 181 | ### Operations that require user authentication 182 | 183 | | Method | Description | Status | 184 | | ------ | ----------- | ------ | 185 | | `node_list`| Retrieve folder or user nodes | Implemented | 186 | | `node_file_info` | Request file node info | Implemented | 187 | | `node_file_download` | Download a file node | Implemented | 188 | | `node_file_save` | Download and save a file node to disk | Implemented | 189 | | `node_add`| Add/copy nodes | Not implemented | 190 | | `node_delete`| Delete node | Not implemented | 191 | | `node_move`| Move node | Not implemented | 192 | | `node_update`| Set node attributes | Not implemented | 193 | | `node_publish` / `node_unpublish`| Create/delete public handle | Not implemented | 194 | | `node_share`| Create/modify/delete outgoing share | Not implemented | 195 | 196 | Credits 197 | ------- 198 | 199 | * This library has been written by Sergio Martín ([@smartinm](http://twitter.com/smartinm)) as a port of official MEGA Javascript code. 200 | 201 | * Part of the code is based on the work done by [Julien Marchand](http://julien-marchand.fr/). 202 | 203 | * This projected is licensed under the terms of the MIT license. 204 | 205 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/smartinm/mega-php-client/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 206 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smartinm/mega-php-client", 3 | "description": "MEGA API Client Library for PHP.", 4 | "type": "library", 5 | "keywords": [ "mega", "api", "client", "mega.co.nz" ], 6 | "homepage": "https://github.com/smartinm/mega-php-client", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Sergio Martin Morillas", 11 | "email": "smartinmorillas@gmail.com", 12 | "homepage": "https://twitter.com/smartinm" 13 | } 14 | ], 15 | "require": { 16 | "php": ">=5.3.0", 17 | "ext-mcrypt": "*", 18 | "ext-openssl": "*", 19 | "ext-curl": "*" 20 | }, 21 | "autoload": { 22 | "classmap": ["lib/"] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/mega.class.php: -------------------------------------------------------------------------------- 1 | user_login_session($email, $password); 64 | return $client; 65 | } 66 | 67 | /** 68 | * Factory method for getting an instance of MEGA client with authentication 69 | * using an ephemeral account. 70 | * 71 | * @return MEGA 72 | */ 73 | public static function create_from_ephemeral() { 74 | // @todo 75 | } 76 | 77 | /** 78 | * Factory method for getting an instance of MEGA client restoring a previous 79 | * saved session. 80 | * 81 | * @param string $session 82 | * @return MEGA 83 | * 84 | * @see MEGA::save_session() 85 | */ 86 | public static function create_from_session($session) { 87 | $session = unserialize(base64_decode(chunk_split($session))); 88 | if (!$session || !is_array($session)) { 89 | return FALSE; 90 | } 91 | $client = new MEGA(); 92 | $client->u_k = $session['u_k']; 93 | $client->u_sid = $session['u_sid']; 94 | $client->u_privk = $session['u_privk']; 95 | $client->u_k_aes = MEGAUtil::a32_to_str($session['u_k']); 96 | return $client; 97 | } 98 | 99 | /** 100 | * Save current session as a base64 string. 101 | * 102 | * @param MEGA $client 103 | * @return string 104 | * 105 | * @see MEGA::create_from_session() 106 | */ 107 | public static function session_save($client) { 108 | return chunk_split(base64_encode(serialize(array( 109 | 'u_k' => $client->u_k, 110 | 'u_sid' => $client->u_sid, 111 | 'u_privk' => $client->u_privk, 112 | )))); 113 | } 114 | 115 | /** 116 | * Default constructor. 117 | * 118 | * @param bool $use_ssl 119 | * (optional) Use SSL for file transfers (default TRUE). 120 | * @param string $apipath 121 | * (optional) MEGA API path, if ommit use the default configured server. 122 | */ 123 | public function __construct($use_ssl = TRUE, $apipath = NULL) { 124 | $this->apipath = isset($apipath) ? $apipath : 'https://' . self::$server . '/'; 125 | $this->use_ssl = (bool) $use_ssl; 126 | $this->seqno = rand(0, PHP_INT_MAX); 127 | } 128 | 129 | /** 130 | * Request file info. 131 | * 132 | * This operation not require authentication. 133 | * 134 | * @param string $ph 135 | * The public file node handle. 136 | * @param string $key 137 | * The file node key. 138 | * @param boolean $dl_url 139 | * (optional) Requests a temporary download URL for the file node. 140 | * @param array $args 141 | * (optional) Extra API command arguments. 142 | * 143 | * @return array 144 | * An array of file information having the following entries: 145 | * - s: File size (bytes). 146 | * - at: An array of file attributes having the following entries: 147 | * - n: File name. 148 | * - g: Temporary download URL. 149 | * 150 | * @see public_file_info_from_link() 151 | */ 152 | public function public_file_info($ph, $key, $dl_url = FALSE, $args = array()) { 153 | $req = array('a' => 'g') + $args; 154 | $req += array('p' => $ph, 'g' => (int) $dl_url, 'ssl' => (int) $this->use_ssl); 155 | 156 | $res = $this->api_req(array($req)); 157 | if (!$res || !is_array($res)) { 158 | return FALSE; 159 | } 160 | 161 | $res = array_shift($res); 162 | if (isset($res['at'])) { 163 | $key = MEGAUtil::base64_to_a32($key); 164 | $attr = MEGAUtil::base64_to_str($res['at']); 165 | $res['at'] = MEGACrypto::dec_attr($attr, $key); 166 | } 167 | return $res; 168 | } 169 | 170 | /** 171 | * Request file info from link. 172 | * 173 | * This operation not require authentication. 174 | * 175 | * @see public_file_info() 176 | */ 177 | public function public_file_info_from_link($link, $dl_url = FALSE) { 178 | $file = self::parse_link($link); 179 | if (empty($file['ph'])) { 180 | throw new InvalidArgumentException('Public handle not found'); 181 | } 182 | if (empty($file['key'])) { 183 | throw new InvalidArgumentException('Private key not found'); 184 | } 185 | return $this->public_file_info($file['ph'], $file['key'], $dl_url); 186 | } 187 | 188 | /** 189 | * Download a public file. 190 | * 191 | * This operation not require authentication. 192 | * 193 | * @param string $ph 194 | * The public file node handle. 195 | * @param string $key 196 | * The file node key. 197 | * @param resource $dest 198 | * (optional) The destination stream. 199 | * 200 | * @return int|string 201 | * Returns the number of bytes written in destination stream. If $dest is 202 | * NULL, returns the file node content in a string. 203 | * 204 | * @see public_file_download_from_link() 205 | */ 206 | public function public_file_download($ph, $key, $dest = NULL) { 207 | // Requests a temporary download URL of the public file. 208 | $info = $this->public_file_info($ph, $key, TRUE); 209 | if (!$info || empty($info['g'])) { 210 | return FALSE; 211 | } 212 | 213 | if (is_null($dest)) { 214 | $handle = fopen('php://memory', 'wb'); 215 | } 216 | else { 217 | $handle = $dest; 218 | } 219 | 220 | $ret = $this->file_download_url($info['g'], $info['s'], MEGAUtil::base64_to_a32($key), $handle); 221 | 222 | if (is_null($dest)) { 223 | rewind($handle); 224 | $content = stream_get_contents($handle); 225 | fclose($handle); 226 | return $content; 227 | } 228 | 229 | return $ret; 230 | } 231 | 232 | /** 233 | * Download a public file from link. 234 | * 235 | * This operation not require authentication. 236 | * 237 | * @see public_file_download() 238 | */ 239 | public function public_file_download_from_link($link, $dest = NULL) { 240 | $file = self::parse_link($link); 241 | if (empty($file['ph'])) { 242 | throw new InvalidArgumentException('Public handle not found'); 243 | } 244 | if (empty($file['key'])) { 245 | throw new InvalidArgumentException('Private key not found'); 246 | } 247 | return $this->public_file_download($file['ph'], $file['key'], $dest); 248 | } 249 | 250 | /** 251 | * Download and save a public file to disk. 252 | * 253 | * This operation not require authentication. 254 | * 255 | * @param string $ph 256 | * The public file node handle. 257 | * @param string $key 258 | * The file node key. 259 | * @param string $dir_path 260 | * (optional) Target directory. 261 | * @param string $filename 262 | * (optional) File name. 263 | * 264 | * @return string 265 | * The full path of saved file. 266 | * 267 | * @see public_file_save_from_link() 268 | */ 269 | public function public_file_save($ph, $key, $dir_path = NULL, $filename = NULL) { 270 | // Requests a temporary download URL of the public file. 271 | $info = $this->public_file_info($ph, $key, TRUE); 272 | if (!$info || empty($info['g'])) { 273 | return FALSE; 274 | } 275 | 276 | $path = !empty($dir_path) ? rtrim($dir_path, '/\\') . '/' : ''; 277 | $path .= !empty($filename) ? $filename : $info['at']['n']; 278 | 279 | $stream = fopen($path, 'wb'); 280 | try { 281 | $this->log("Downloading {$info['at']['n']} (size: {$info['s']}), url = {$info['g']}"); 282 | $this->file_download_url($info['g'], $info['s'], MEGAUtil::base64_to_a32($key), $stream); 283 | } 284 | catch (MEGAException $e) { 285 | fclose($stream); 286 | throw $e; 287 | } 288 | fclose($stream); 289 | 290 | return $path; 291 | } 292 | 293 | /** 294 | * Download and save a public file to disk from link. 295 | * 296 | * This operation not require authentication. 297 | * 298 | * @see public_file_save() 299 | */ 300 | public function public_file_save_from_link($link, $dir_path = NULL, $filename = NULL) { 301 | $file = self::parse_link($link); 302 | if (empty($file['ph'])) { 303 | throw new InvalidArgumentException('Public handle not found'); 304 | } 305 | if (empty($file['key'])) { 306 | throw new InvalidArgumentException('Private key not found'); 307 | } 308 | return $this->public_file_save($file['ph'], $file['key'], $dir_path, $filename); 309 | } 310 | 311 | /** 312 | * Retrieve folder or user nodes. 313 | * 314 | * Returns the contents of the requested folder, or a full view of the 315 | * requesting user's three filesystem trees, contact list, incoming shares 316 | * and pending share key requests. 317 | * 318 | * @param string $handle 319 | * (optional) The public file or user node handle. 320 | * 321 | * @return array 322 | */ 323 | public function node_list($handle = NULL, $args = array()) { 324 | $req = array('a' => 'f') + $args; 325 | $req += array('c' => 1); 326 | if ($handle) { 327 | $req += array('n' => $handle); 328 | } 329 | 330 | $res = $this->api_req(array($req)); 331 | if (!$res) { 332 | return FALSE; 333 | } 334 | 335 | $res = array_shift($res); 336 | if (isset($res['f'])) { 337 | 338 | $nodes = &$res['f']; 339 | foreach ($nodes as $index => $node) { 340 | if ($node['t'] == 0 || $node['t'] == 1) { 341 | if (!empty($node['k'])) { 342 | if ($key = $this->node_decrypt_key($node['k'])) { 343 | $attr = MEGAUtil::base64_to_str($node['a']); 344 | $nodes[$index]['a'] = MEGACrypto::dec_attr($attr, $key); 345 | } 346 | } 347 | } 348 | } 349 | } 350 | return $res; 351 | } 352 | 353 | /** 354 | * Request file info. 355 | * 356 | * @param array $node 357 | * The file node handle. 358 | * @param boolean $dl_url 359 | * (optional) Set to TRUE to request a temporary download URL for the file. 360 | * @param array $args 361 | * (optional) Set extra API command arguments. 362 | * 363 | * @return array 364 | * An array of file information having the following entries: 365 | * - s: File size (bytes). 366 | * - at: An array of file attributes having the following entries: 367 | * - n: File name. 368 | * - g: Temporary download URL. 369 | */ 370 | public function node_file_info($node, $dl_url = FALSE, $args = array()) { 371 | if (empty($node['h']) || empty($node['k'])) { 372 | throw new InvalidArgumentException('Invalid file node handle'); 373 | } 374 | 375 | $req = array('a' => 'g') + $args; 376 | $req += array('n' => $node['h'], 'g' => (int) $dl_url, 'ssl' => (int) $this->use_ssl); 377 | 378 | $res = $this->api_req(array($req)); 379 | if (!$res || !is_array($res)) { 380 | return FALSE; 381 | } 382 | 383 | $res = array_shift($res); 384 | if (isset($res['at'])) { 385 | if ($key = $this->node_decrypt_key($node['k'])) { 386 | $attr = MEGAUtil::base64_to_str($res['at']); 387 | $res['at'] = MEGACrypto::dec_attr($attr, $key); 388 | } 389 | } 390 | return $res; 391 | } 392 | 393 | /** 394 | * Download file. 395 | * 396 | * @param array $node 397 | * The file node handle. 398 | * @param resource $dest 399 | * (optional) The destination stream. 400 | * 401 | * @return int|string 402 | * Returns the number of bytes written in destination stream. If $dest is 403 | * NULL, returns the file node content in a string. 404 | */ 405 | public function node_file_download($node, $dest = NULL) { 406 | // Requests a temporary download URL of the file node. 407 | $info = $this->node_file_info($node, TRUE); 408 | if (!$info || empty($info['g'])) { 409 | return FALSE; 410 | } 411 | 412 | $key = $this->node_decrypt_key($node['k']); 413 | if (!$key) { 414 | return FALSE; 415 | } 416 | 417 | if (is_null($dest)) { 418 | $handle = fopen('php://memory', 'wb'); 419 | } 420 | else { 421 | $handle = $dest; 422 | } 423 | 424 | $ret = $this->file_download_url($info['g'], $info['s'], $key, $handle); 425 | 426 | if (is_null($dest)) { 427 | rewind($handle); 428 | $content = stream_get_contents($handle); 429 | fclose($handle); 430 | return $content; 431 | } 432 | 433 | return $ret; 434 | } 435 | 436 | /** 437 | * Download and save file to disk. 438 | * 439 | * @param array $node 440 | * The file node handle. 441 | * @param string $dir_path 442 | * (optional) Target directory. 443 | * @param string $filename 444 | * (optional) File name. 445 | * @param array $args 446 | * 447 | * @return string 448 | * The full path of saved file. 449 | */ 450 | public function node_file_save($node, $dir_path = NULL, $filename = NULL, $args = array()) { 451 | // Requests a temporary download URL of the file node. 452 | $info = $this->node_file_info($node, TRUE, $args); 453 | if (!$info || empty($info['g'])) { 454 | return FALSE; 455 | } 456 | 457 | $key = $this->node_decrypt_key($node['k']); 458 | if (!$key) { 459 | return FALSE; 460 | } 461 | 462 | $path = !empty($dir_path) ? rtrim($dir_path, '/\\') . '/' : ''; 463 | $path .= !empty($filename) ? $filename : $info['at']['n']; 464 | 465 | $stream = fopen($path, 'wb'); 466 | try { 467 | $this->log("Downloading {$info['at']['n']} (size: {$info['s']}), url = {$info['g']}"); 468 | $this->file_download_url($info['g'], $info['s'], $key, $stream); 469 | } 470 | catch (MEGAException $e) { 471 | fclose($stream); 472 | throw $e; 473 | } 474 | fclose($stream); 475 | 476 | return $path; 477 | } 478 | 479 | /** 480 | * Add/copy nodes. 481 | * 482 | * Adds new nodes. Copies existing files and adds completed uploads to a 483 | * user's filesystem. 484 | */ 485 | public function node_add() { 486 | throw Exception('Not implemented'); 487 | } 488 | 489 | /** 490 | * Delete node. 491 | * 492 | * Deletes a node, including all of its subnodes. 493 | */ 494 | public function node_delete() { 495 | throw Exception('Not implemented'); 496 | } 497 | 498 | /** 499 | * Move node. 500 | * 501 | * Moves a node to a new parent node. 502 | */ 503 | public function node_move() { 504 | throw Exception('Not implemented'); 505 | } 506 | 507 | /** 508 | * Set node attributes. 509 | * 510 | * Updates the encrypted node attributes object. 511 | */ 512 | public function node_update() { 513 | throw Exception('Not implemented'); 514 | } 515 | 516 | /** 517 | * Create/delete public handle. 518 | * 519 | * Enables or disables the public handle for a node. 520 | */ 521 | public function node_publish($op) { 522 | throw Exception('Not implemented'); 523 | } 524 | 525 | public function node_unpublish($op) { 526 | throw Exception('Not implemented'); 527 | } 528 | 529 | /** 530 | * Create/modify/delete outgoing share. 531 | * 532 | * Controls the sharing status of a node. 533 | */ 534 | public function node_share($op) { 535 | throw Exception('Not implemented'); 536 | } 537 | 538 | /** 539 | * Login session challenge/response. 540 | * 541 | * Establishes a user session based on the response to a cryptographic challenge. 542 | * 543 | * @see user.js::u_login() 544 | * @see user.js::api_getsid() 545 | */ 546 | public function user_login_session($email, $password, $args = array()) { 547 | $this->log("Preparing user key..."); 548 | $pk = MEGACrypto::prepare_key_pw($password); 549 | 550 | $this->log("Preparing user hash..."); 551 | $uh = MEGACrypto::stringhash(strtolower($email), $pk); 552 | 553 | $req = array('a' => 'us') + $args; 554 | $req += array('user' => $email, 'uh' => $uh); 555 | 556 | $res = $this->api_req(array($req)); 557 | if (!$res || !is_array($res)) { 558 | return FALSE; 559 | } 560 | 561 | $res = array_shift($res); 562 | if (isset($res['k'])) { 563 | // decrypt master key 564 | $k = MEGAUtil::base64_to_a32($res['k']); 565 | if (count($k) == 4) { 566 | $k = MEGACrypto::decrypt_key($pk, $k); 567 | if (isset($res['tsid'])) { 568 | // @todo 569 | } 570 | else if (isset($res['csid'])) { 571 | $privk = MEGACrypto::decrypt_key(MEGAUtil::a32_to_str($k), MEGAUtil::base64_to_a32($res['privk'])); 572 | $privk = MEGAUtil::a32_to_str($privk); 573 | 574 | $rsa_privk = array(); 575 | // decompose private key 576 | for ($i = 0; $i < 4; $i++) { 577 | $l = ((ord($privk[0]) * 256 + ord($privk[1]) + 7) >> 3) + 2; 578 | $rsa_privk[$i] = MEGAUtil::mpi2b(substr($privk, 0, $l)); 579 | $privk = substr($privk, $l); 580 | } 581 | 582 | $t = MEGAUtil::base64urldecode($res['csid']); 583 | $t = MEGAUtil::mpi2b($t); 584 | 585 | $sid = MEGARsa::rsa_decrypt($t, $rsa_privk[0], $rsa_privk[1], $rsa_privk[2]); 586 | $sid = MEGAUtil::base64urlencode(substr(strrev($sid), 0, 43)); 587 | 588 | // check format 589 | if ($i == 4 && strlen($privk) < 16) { 590 | // @@@ check remaining padding for added early wrong password detection likelihood 591 | $r = array( 592 | $k, 593 | $sid, 594 | $rsa_privk, 595 | ); 596 | $this->u_k = $k; 597 | $this->u_k_aes = MEGAUtil::a32_to_str($this->u_k); 598 | $this->u_sid = $sid; 599 | $this->u_privk = $rsa_privk; 600 | return $r; 601 | } 602 | } 603 | } 604 | } 605 | return FALSE; 606 | } 607 | 608 | /** 609 | * 610 | */ 611 | public function user_add($args = array()) { 612 | $master_key = array(); 613 | $password_key = array(); 614 | $session_self_challenge = array(); 615 | 616 | foreach (range(0, 3) as $n) { 617 | $master_key[] = rand(0, PHP_INT_MAX); 618 | $password_key[] = rand(0, PHP_INT_MAX); 619 | $session_self_challenge[] = rand(0, PHP_INT_MAX); 620 | } 621 | 622 | $k = MEGACrypto::encrypt_key(MEGAUtil::a32_to_str($password_key), $master_key); 623 | $ts = MEGACrypto::encrypt_key(MEGAUtil::a32_to_str($master_key), $session_self_challenge); 624 | $ts = MEGAUtil::a32_to_str($session_self_challenge) . MEGAUtil::a32_to_str($ts); 625 | 626 | $req = array('a' => 'up') + $args; 627 | $req += array('k' => MEGAUtil::a32_to_base64($k), 'ts' => MEGAUtil::base64urlencode($ts)); 628 | 629 | $res = $this->api_req(array($req)); 630 | if (!$res || !is_array($res)) { 631 | return FALSE; 632 | } 633 | return array_shift($res); 634 | } 635 | 636 | /** 637 | * Get user. 638 | * 639 | * Retrieves user details. 640 | */ 641 | public function user_get_details() { 642 | } 643 | 644 | /** 645 | * Download a file node from requested temporary download URL. 646 | * 647 | * @param array $info 648 | * The file info returned by node_file_info() or public_file_info(), 649 | * with requested temporary download URL. 650 | * @param resource $stream 651 | * Stream resource. 652 | * @param string $key 653 | * The file node key. 654 | * 655 | * @return int 656 | * Returns the number of bytes written in destination stream. 657 | * 658 | * @todo Add range support 659 | * @todo Add integrity check 660 | */ 661 | protected function file_download_url($url, $size, $key, $dest) { 662 | // Open the cipher 663 | $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', 'ctr', ''); 664 | 665 | // Create key 666 | //$key = MEGAUtil::base64_to_a32($key); 667 | $aeskey = array($key[0] ^ $key[4], $key[1] ^ $key[5], $key[2] ^ $key[6], $key[3] ^ $key[7]); 668 | $aeskey = MEGAUtil::a32_to_str($aeskey); 669 | 670 | // Create the IV 671 | $iv = array($key[4], $key[5], 0, 0); 672 | $iv = MEGAUtil::a32_to_str($iv); 673 | 674 | // Initialize encryption module for decryption 675 | mcrypt_generic_init($td, $aeskey, $iv); 676 | 677 | $chunks = $this->get_chunks($size); 678 | $stream = $this->http_open_stream($url); 679 | 680 | // Fetch response. Due to PHP bugs like http://bugs.php.net/bug.php?id=43782 681 | // and http://bugs.php.net/bug.php?id=46049 we can't rely on feof(), but 682 | // instead must invoke stream_get_meta_data() each iteration. 683 | $info = stream_get_meta_data($stream); 684 | $alive = !$info['eof']; 685 | 686 | $ret = 0; 687 | $buffer = ''; 688 | foreach ($chunks as $chunk_start => $chunk_size) { 689 | // Read chunk from network 690 | $bytes = strlen($buffer); 691 | while ($bytes < $chunk_size && $alive) { 692 | $data = fread($stream, min(1024, $chunk_size - $bytes)); 693 | $buffer .= $data; 694 | 695 | $bytes = strlen($buffer); 696 | $info = stream_get_meta_data($stream); 697 | $alive = !$info['eof'] && $data; 698 | } 699 | 700 | $chunk = substr($buffer, 0, $chunk_size); 701 | $buffer = $bytes > $chunk_size ? substr($buffer, $chunk_size) : ''; 702 | 703 | // Decrypt encrypted chunk 704 | $chunk = mdecrypt_generic($td, $chunk); 705 | if ($bytes = fwrite($dest, $chunk)) { 706 | $ret += $bytes; 707 | } 708 | } 709 | 710 | // Terminate decryption handle and close module 711 | mcrypt_generic_deinit($td); 712 | mcrypt_module_close($td); 713 | fclose($stream); 714 | 715 | // Returns the number of bytes written 716 | return $ret; 717 | } 718 | 719 | protected function get_chunks($size) { 720 | $chunks = array(); 721 | $p = $pp = 0; 722 | $i = 1; 723 | 724 | while ($i <= 8 && $p < ($size - $i * 0x20000)) { 725 | $chunks[$p] = $i * 0x20000; 726 | $pp = $p; 727 | $p += $chunks[$p]; 728 | $i += 1; 729 | } 730 | 731 | while ($p < $size) { 732 | $chunks[$p] = 0x100000; 733 | $pp = $p; 734 | $p += $chunks[$p]; 735 | } 736 | 737 | $chunks[$pp] = $size - $pp; 738 | if (empty($chunks[$pp])) { 739 | unset($chunks[$pp]); 740 | } 741 | 742 | return $chunks; 743 | } 744 | 745 | protected function node_decrypt_key($k) { 746 | static $cache = array(); 747 | if (!isset($cache[$k])) { 748 | $keys = explode('/', $k); 749 | list (, $key) = explode(':', $keys[0]); 750 | if (!empty($key)) { 751 | $key = MEGAUtil::base64_to_a32($key); 752 | $key = MEGACrypto::decrypt_key($this->u_k_aes, $key); 753 | $cache[$k] = $key; 754 | } 755 | } 756 | return $cache[$k]; 757 | } 758 | 759 | protected function api_req($req, $params = array()) { 760 | $this->api_req_alter($req); 761 | 762 | $payload = is_string($req) ? $req : json_encode($req); 763 | 764 | $url = $this->apipath . 'cs?id=' . $this->seqno; 765 | if (!empty($this->u_sid)) { 766 | $url .= '&sid=' . $this->u_sid; 767 | } 768 | 769 | $this->log("Making API request: " . $payload); 770 | $this->seqno ++; 771 | 772 | $response = $this->http_do_request($url, $payload); 773 | /* 774 | if ($response->error) { 775 | $this->log("API request error (" . $response->code . ")"); 776 | return FALSE; 777 | } 778 | */ 779 | 780 | $this->log('API response: ' . $response); 781 | 782 | return json_decode($response, TRUE); 783 | } 784 | 785 | protected function api_req_alter(&$req) { } 786 | 787 | /** 788 | * 789 | * 790 | */ 791 | protected function http_do_request($url, $payload) { 792 | //$url = ($this->ssl ? 'https' : 'http') . '://' . $this->endpoint . '/cs?id=' . $this->sequence_number; 793 | 794 | $curl_handle = curl_init(); 795 | $curl_options = array( 796 | CURLOPT_URL => $url, 797 | CURLOPT_FOLLOWLOCATION => TRUE, 798 | CURLOPT_RETURNTRANSFER => TRUE, 799 | CURLOPT_SSL_VERIFYPEER => FALSE, // Required to run on https. 800 | CURLOPT_SSL_VERIFYHOST => FALSE, // Required to run on https. 801 | //CURLOPT_HEADERFUNCTION => array(&$this, 'curlHeaderCallback'), 802 | //CURLOPT_USERAGENT => $this->databasePrefix, 803 | CURLOPT_POSTFIELDS => $payload, 804 | CURLOPT_HTTPHEADER => array('Content-Type' => 'application/json'), 805 | ); 806 | 807 | //print_r($curl_options); 808 | 809 | curl_setopt_array($curl_handle, $curl_options); 810 | 811 | $content = curl_exec($curl_handle); 812 | 813 | //$status = curl_getinfo($curl_handle, CURLINFO_HTTP_CODE); 814 | //print "Status: $status\n"; 815 | 816 | curl_close($curl_handle); 817 | 818 | return $content; 819 | } 820 | 821 | protected function http_open_stream($url, $options = array()) { 822 | $scheme = parse_url($url, PHP_URL_SCHEME); 823 | 824 | /* 825 | $header = ''; 826 | foreach ($options['headers'] as $name => $value) { 827 | $header .= $name . ': ' . trim($value) . "\r\n"; 828 | } 829 | */ 830 | 831 | $opts = array( 832 | $scheme => array( 833 | 'method' => 'GET', 834 | //'header' => $header, 835 | //'content' => $options['data'], 836 | //'max_redirects' => $options['max_redirects'], 837 | //'timeout' => (float) $options['timeout'], 838 | ) 839 | ); 840 | 841 | $context = stream_context_create($opts); 842 | return fopen($url, 'rb', FALSE, $context); 843 | } 844 | 845 | protected function log($message) { 846 | if (self::DEBUG) { 847 | echo "[DEBUG] MEGA::$message\n"; 848 | } 849 | } 850 | 851 | /** 852 | * 853 | * @param string $link 854 | * @return array 855 | */ 856 | public static function parse_link($link, $component = NULL) { 857 | $fragment = parse_url($link, PHP_URL_FRAGMENT); 858 | if (empty($fragment)) { 859 | return FALSE; 860 | } 861 | $matches = array(); 862 | if (preg_match('/^(F?)\\!([a-zA-Z0-9]+)(?:\\!([a-zA-Z0-9_,\\-]+))?/', $fragment, $matches)) { 863 | //return count($matches) > 2 ? array($matches[1], $matches[3]) : array($matches[1]); 864 | return array( 865 | 'type' => $matches[1] == 'F' ? 'folder' : 'file', 866 | 'ph' => $matches[2], 867 | ) + (!empty($matches[3]) ? array('key' => $matches[3]) : array()); 868 | } 869 | return FALSE; 870 | } 871 | } 872 | 873 | class MEGAException extends Exception { 874 | 875 | const EINTERNAL = -1; 876 | const EARGS = -2; 877 | const EAGAIN = -3; 878 | const ERATELIMIT = -4; 879 | const EFAILED = -5; 880 | const ETOOMANY = -6; 881 | const ERANGE = -7; 882 | const EEXPIRED = -8; 883 | const ENOENT = -9; 884 | const ECIRCULAR = -10; 885 | const EACCESS = -11; 886 | const EEXIST = -12; 887 | const EINCOMPLETE = -13; 888 | const EKEY = -14; 889 | const ESID = -15; 890 | const EBLOCKED = -16; 891 | const EOVERQUOTA = -17; 892 | const ETEMPUNAVAIL = -18; 893 | 894 | public function __construct($code) { 895 | $message = NULL; 896 | switch ($code) { 897 | case self::EINTERNAL: 898 | $message = 'An internal error has occurred. Please submit a bug report, detailing the exact circumstances in which this error occurred'; 899 | break; 900 | case self::EARGS: 901 | $message = 'You have passed invalid arguments to this command'; 902 | break; 903 | case self::EAGAIN: 904 | $message = 'A temporary congestion or server malfunction prevented your request from being processed. No data was altered. Retry. Retries must be spaced with exponential backoff'; 905 | break; 906 | case self::ERATELIMIT: 907 | $message = 'You have exceeded your command weight per time quota. Please wait a few seconds, then try again (this should never happen in sane real-life applications)'; 908 | break; 909 | case self::EFAILED: 910 | $message = 'The upload failed. Please restart it from scratch'; 911 | break; 912 | case self::ETOOMANY: 913 | $message = 'Too many concurrent IP addresses are accessing this upload target URL'; 914 | break; 915 | case self::ERANGE: 916 | $message = 'The upload file packet is out of range or not starting and ending on a chunk boundary'; 917 | break; 918 | case self::EEXPIRED: 919 | $message = 'The upload target URL you are trying to access has expired. Please request a fresh one'; 920 | break; 921 | case self::ENOENT: 922 | $message = 'Object (typically, node or user) not found'; 923 | break; 924 | case self::ECIRCULAR: 925 | $message = 'Circular linkage attempted'; 926 | break; 927 | case self::EACCESS: 928 | $message = 'Access violation (e.g., trying to write to a read-only share)'; 929 | break; 930 | case self::EEXIST: 931 | $message = 'Trying to create an object that already exists'; 932 | break; 933 | case self::EINCOMPLETE: 934 | $message = 'Trying to access an incomplete resource'; 935 | break; 936 | case self::EKEY: 937 | $message = 'A decryption operation failed (never returned by the API)'; 938 | break; 939 | case self::ESID: 940 | $message = 'Invalid or expired user session, please relogin'; 941 | break; 942 | case self::EBLOCKED: 943 | $message = 'User blocked'; 944 | break; 945 | case self::EOVERQUOTA: 946 | $message = 'Request over quota'; 947 | break; 948 | case self::ETEMPUNAVAIL: 949 | $message = 'Resource temporarily not available, please try again later'; 950 | break; 951 | } 952 | parent::__construct($message, $code); 953 | } 954 | } 955 | 956 | // === crypto_2.js == 957 | 958 | /** 959 | * PHP port of MEGA Javascript crypto functions. 960 | * 961 | * @see http://eu.static.mega.co.nz/crypto_N.js 962 | */ 963 | class MEGACrypto { 964 | 965 | /** 966 | * Convert user-supplied password array. 967 | * 968 | * @param array $a 969 | * The user password array of 32-bit words. 970 | * 971 | * @return string 972 | * The AES user password key. 973 | */ 974 | public static function prepare_key($a) { 975 | $pkey = MEGAUtil::a32_to_str(array(0x93C467E3, 0x7DB0C7A4, 0xD1BE3F81, 0x0152CB56)); 976 | $total = count($a); 977 | for ($r = 65536; $r--; ) { 978 | for ($j = 0; $j < $total; $j += 4) { 979 | $key = array(0, 0, 0, 0); 980 | for ($i = 0; $i < 4; $i++) { 981 | if ($i + $j < $total) { 982 | $key[$i] = $a[$i + $j]; 983 | } 984 | } 985 | $pkey = self::encrypt_aes_cbc(MEGAUtil::a32_to_str($key), $pkey); 986 | } 987 | } 988 | return $pkey; 989 | } 990 | 991 | // prepare_key with string input 992 | public static function prepare_key_pw($password) { 993 | return self::prepare_key(MEGAUtil::str_to_a32($password)); 994 | } 995 | 996 | public static function stringhash($s, $aeskey) { 997 | $s32 = MEGAUtil::str_to_a32($s); 998 | $h32 = array(0, 0, 0, 0); 999 | 1000 | for ($i = 0; $i < count($s32); $i++) { 1001 | $h32[$i & 3] ^= $s32[$i]; 1002 | } 1003 | 1004 | $h32 = MEGAUtil::a32_to_str($h32); 1005 | for ($i = 16384; $i--;) { 1006 | $h32 = self::encrypt_aes_cbc($aeskey, $h32); 1007 | } 1008 | 1009 | $h32 = MEGAUtil::str_to_a32($h32); 1010 | return MEGAUtil::a32_to_base64(array($h32[0], $h32[2])); 1011 | } 1012 | 1013 | // AES encrypt in CBC mode (zero IV) 1014 | public static function encrypt_aes_cbc($key, $data) { 1015 | $iv = str_repeat("\0", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC)); 1016 | return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv); 1017 | } 1018 | 1019 | // AES decrypt in CBC mode (zero IV) 1020 | public static function decrypt_aes_cbc($key, $data) { 1021 | $iv = str_repeat("\0", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC)); 1022 | return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv); 1023 | } 1024 | 1025 | // AES encrypt in CBC mode (zero IV) 1026 | public static function encrypt_aes_cbc_a32($key, $a) { 1027 | return MEGAUtil::str_to_a32(self::encrypt_aes_cbc($key, MEGAUtil::a32_to_str($a))); 1028 | } 1029 | 1030 | // AES decrypt in CBC mode (zero IV) 1031 | public static function decrypt_aes_cbc_a32($key, $a) { 1032 | return MEGAUtil::str_to_a32(self::decrypt_aes_cbc($key, MEGAUtil::a32_to_str($a))); 1033 | } 1034 | 1035 | // encrypt 4- or 8-element 32-bit integer array 1036 | public static function encrypt_key($key, $a) { 1037 | if (count($a) == 4) { 1038 | return self::encrypt_aes_cbc_a32($key, $a); 1039 | } 1040 | $x = array(); 1041 | for ($i = 0; $i < count($a); $i += 4) { 1042 | $x[] = self::encrypt_aes_cbc_a32($key, array($a[$i], $a[$i + 1], $a[$i + 2], $a[$i + 3])); 1043 | } 1044 | return $x; 1045 | } 1046 | 1047 | /** 1048 | * decrypt 4- or 8-element 32-bit integer array 1049 | * 1050 | * @param string $key 1051 | * @param array $a 1052 | * @return array 1053 | */ 1054 | public static function decrypt_key($key, $a) { 1055 | if (count($a) == 4) { 1056 | return self::decrypt_aes_cbc_a32($key, $a); 1057 | } 1058 | $x = array(); 1059 | for ($i = 0; $i < count($a); $i += 4) { 1060 | //$x[] = self::decrypt_aes_cbc_a32($key, array($a[$i], $a[$i + 1], $a[$i + 2], $a[$i + 3])); 1061 | $y = self::decrypt_aes_cbc_a32($key, array($a[$i], $a[$i + 1], $a[$i + 2], $a[$i + 3])); 1062 | $x = array_merge($x, $y); 1063 | } 1064 | return $x; 1065 | } 1066 | 1067 | // generate attributes block using AES-CBC with MEGA canary 1068 | // attr = Object, key = [] (four-word random key will be generated) or Array(8) (lower four words will be used) 1069 | // returns [ArrayBuffer data,Array key] 1070 | public static function enc_attr($attr, $key) { 1071 | } 1072 | 1073 | // decrypt attributes block using AES-CBC, check for MEGA canary 1074 | // attr = ab, key as with enc_attr 1075 | // returns [Object] or false 1076 | public static function dec_attr($attr, $key) { 1077 | if (count($key) != 4) { 1078 | $key = array($key[0] ^ $key[4], $key[1] ^ $key[5], $key[2] ^ $key[6], $key[3] ^ $key[7]); 1079 | } 1080 | $key = MEGAUtil::a32_to_str($key); 1081 | 1082 | $attr = self::decrypt_aes_cbc($key, $attr); 1083 | $attr = MEGAUtil::str_depad($attr); 1084 | 1085 | if (substr($attr, 0, 6) != 'MEGA{"') { 1086 | return FALSE; 1087 | } 1088 | 1089 | // @todo protect against syntax errors 1090 | $attr = json_decode(MEGAUtil::from8(substr($attr, 4)), TRUE); 1091 | if (is_null($attr)) { 1092 | $attr = new stdClass(); 1093 | $attr['n'] = 'MALFORMED_ATTRIBUTES'; 1094 | } 1095 | return $attr; 1096 | } 1097 | } 1098 | 1099 | /** 1100 | * RSA-related stuff -- taken from PEAR Crypt_RSA package 1101 | * http://pear.php.net/package/Crypt_RSA 1102 | */ 1103 | class MEGARsa { 1104 | 1105 | public static function rsa_decrypt($enc_data, $p, $q, $d) { 1106 | $enc_data = self::int2bin($enc_data); 1107 | $exp = $d; 1108 | $modulus = bcmul($p, $q); 1109 | $data_len = strlen($enc_data); 1110 | $chunk_len = self::bitLen($modulus) - 1; 1111 | $block_len = (int) ceil($chunk_len / 8); 1112 | $curr_pos = 0; 1113 | $bit_pos = 0; 1114 | $plain_data = 0; 1115 | 1116 | while ($curr_pos < $data_len) { 1117 | $tmp = self::bin2int(substr($enc_data, $curr_pos, $block_len)); 1118 | $tmp = bcpowmod($tmp, $exp, $modulus); 1119 | $plain_data = self::bitOr($plain_data, $tmp, $bit_pos); 1120 | $bit_pos += $chunk_len; 1121 | $curr_pos += $block_len; 1122 | } 1123 | 1124 | return self::int2bin($plain_data); 1125 | } 1126 | 1127 | private static function bin2int($str) { 1128 | $result = 0; 1129 | $n = strlen($str); 1130 | do { 1131 | $result = bcadd(bcmul($result, 256), ord($str[--$n])); 1132 | } while ($n > 0); 1133 | return $result; 1134 | } 1135 | 1136 | private static function int2bin($num) { 1137 | $result = ''; 1138 | do { 1139 | $result .= chr(bcmod($num, 256)); 1140 | $num = bcdiv($num, 256); 1141 | } while (bccomp($num, 0)); 1142 | return $result; 1143 | } 1144 | 1145 | private static function bitOr($num1, $num2, $start_pos) { 1146 | $start_byte = intval($start_pos / 8); 1147 | $start_bit = $start_pos % 8; 1148 | $tmp1 = self::int2bin($num1); 1149 | 1150 | $num2 = bcmul($num2, 1 << $start_bit); 1151 | $tmp2 = self::int2bin($num2); 1152 | if ($start_byte < strlen($tmp1)) { 1153 | $tmp2 |= substr($tmp1, $start_byte); 1154 | $tmp1 = substr($tmp1, 0, $start_byte) . $tmp2; 1155 | } else { 1156 | $tmp1 = str_pad($tmp1, $start_byte, '\0') . $tmp2; 1157 | } 1158 | return self::bin2int($tmp1); 1159 | } 1160 | 1161 | private static function bitLen($num) { 1162 | $tmp = self::int2bin($num); 1163 | $bit_len = strlen($tmp) * 8; 1164 | $tmp = ord($tmp[strlen($tmp) - 1]); 1165 | if (!$tmp) { 1166 | $bit_len -= 8; 1167 | } else { 1168 | while (!($tmp & 0x80)) { 1169 | $bit_len--; 1170 | $tmp <<= 1; 1171 | } 1172 | } 1173 | return $bit_len; 1174 | } 1175 | } 1176 | 1177 | /** 1178 | * PHP port of MEGA Javascript util functions. 1179 | */ 1180 | class MEGAUtil { 1181 | 1182 | // unsubstitute standard base64 special characters, restore padding. 1183 | public static function base64urldecode($data) { 1184 | $data .= substr('==', (2 - strlen($data) * 3) & 3); 1185 | $data = str_replace(array('-', '_', ','), array('+', '/', ''), $data); 1186 | return base64_decode($data); 1187 | } 1188 | 1189 | // substitute standard base64 special characters to prevent JSON escaping, remove padding 1190 | public static function base64urlencode($data) { 1191 | $data = base64_encode($data); 1192 | return str_replace(array('+', '/', '='), array('-', '_', ''), $data); 1193 | } 1194 | 1195 | // array of 32-bit words to string (big endian) 1196 | public static function a32_to_str($a) { 1197 | return call_user_func_array('pack', array_merge(array('N*'), $a)); 1198 | } 1199 | 1200 | public static function a32_to_base64($a) { 1201 | return self::base64urlencode(self::a32_to_str($a)); 1202 | } 1203 | 1204 | // string to array of 32-bit words (big endian) 1205 | public static function str_to_a32($b) { 1206 | $padding = (((strlen($b) + 3) >> 2) * 4) - strlen($b); 1207 | if ($padding > 0) { 1208 | $b .= str_repeat("\0", $padding); 1209 | } 1210 | return array_values(unpack('N*', $b)); 1211 | } 1212 | 1213 | public static function base64_to_a32($s) { 1214 | return self::str_to_a32(self::base64urldecode($s)); 1215 | } 1216 | 1217 | // string to binary string (ab_to_base64) 1218 | public static function str_to_base64($ab) { 1219 | return self::base64urlencode($ab); 1220 | } 1221 | 1222 | // binary string to string, 0-padded to AES block size (base64_to_ab) 1223 | public static function base64_to_str($a) { 1224 | return self::str_pad(self::base64urldecode($a)); 1225 | } 1226 | 1227 | // binary string depadding (ab_to_str_depad) 1228 | public static function str_depad($b) { 1229 | for ($i = strlen($b); $i-- && !uniord($b[$i]); ); 1230 | $b = substr($b, 0, $i + 1); 1231 | return $b; 1232 | } 1233 | 1234 | // binary string 0-padded to AES block size (str_to_ab) 1235 | public static function str_pad($b) { 1236 | $padding = 16 - ((strlen($b) - 1) & 15); 1237 | return $b . str_repeat("\0", $padding - 1); 1238 | } 1239 | 1240 | public static function mpi2b($s) { 1241 | $s = bin2hex(substr($s, 2)); 1242 | $len = strlen($s); 1243 | $n = 0; 1244 | for ($i = 0; $i < $len; $i++) { 1245 | $n = bcadd($n, bcmul(hexdec($s[$i]), bcpow(16, $len - $i - 1))); 1246 | } 1247 | return $n; 1248 | } 1249 | 1250 | public static function to8($unicode) { 1251 | return $unicode; 1252 | //return unescape(self::encodeURIComponent($unicode)); 1253 | } 1254 | 1255 | public static function from8($utf8) { 1256 | return $utf8; 1257 | //return decodeURIComponent(escape($utf8)); 1258 | } 1259 | 1260 | /* 1261 | public static function encodeURIComponent($str) { 1262 | $revert = array('%21'=>'!', '%2A'=>'*', '%27'=>"'", '%28'=>'(', '%29'=>')'); 1263 | return strtr(rawurlencode($str), $revert); 1264 | } 1265 | */ 1266 | } 1267 | 1268 | function uniord($u) { 1269 | return hexdec(bin2hex($u)); 1270 | } 1271 | --------------------------------------------------------------------------------