├── servicemanager ├── servicemanager.exe ├── servicemanager_mac ├── servicemanager_nix_32 ├── servicemanager_nix_64 ├── servicemanager_nix.systemd ├── servicemanager_mac.launchd └── servicemanager_nix.sysvinit ├── .gitignore ├── support ├── phpseclib │ ├── openssl.cnf │ ├── license.txt │ └── Crypt │ │ ├── AES.php │ │ └── RC4.php ├── event_manager.php ├── crc32_stream.php ├── str_basics.php ├── servicemanager.php ├── remotedapi_web_server.php ├── multi_async_helper.php ├── webroute.php ├── deflate_stream.php ├── random.php ├── cli.php └── utf_utils.php ├── sdks └── php │ ├── sdk_remotedapi.php │ ├── crc32_stream.php │ ├── sdk_cloud_storage_server_files.php │ ├── webroute.php │ ├── deflate_stream.php │ ├── sdk_cloud_storage_server_api_base.php │ ├── random.php │ └── utf_utils.php ├── manage.php └── README.md /servicemanager/servicemanager.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cubiclesoft/cloud-storage-server/master/servicemanager/servicemanager.exe -------------------------------------------------------------------------------- /servicemanager/servicemanager_mac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cubiclesoft/cloud-storage-server/master/servicemanager/servicemanager_mac -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /data/* 2 | /test.php 3 | /user_init/* 4 | /server_exts/scripts.php 5 | /server_exts/feeds.php 6 | /support/createprocess.exe 7 | -------------------------------------------------------------------------------- /servicemanager/servicemanager_nix_32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cubiclesoft/cloud-storage-server/master/servicemanager/servicemanager_nix_32 -------------------------------------------------------------------------------- /servicemanager/servicemanager_nix_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cubiclesoft/cloud-storage-server/master/servicemanager/servicemanager_nix_64 -------------------------------------------------------------------------------- /support/phpseclib/openssl.cnf: -------------------------------------------------------------------------------- 1 | # minimalist openssl.cnf file for use with phpseclib 2 | 3 | HOME = . 4 | RANDFILE = $ENV::HOME/.rnd 5 | 6 | [ v3_ca ] 7 | -------------------------------------------------------------------------------- /servicemanager/servicemanager_nix.systemd: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=@SERVICENAME@ 3 | After=syslog.target network.target remote-fs.target nss-lookup.target 4 | 5 | [Service] 6 | Type=forking 7 | PIDFile=@SERVICEPIDFILE@ 8 | ExecStart='@SERVICEMANAGER@' start '@SERVICENAME@' 9 | ExecReload='@SERVICEMANAGER@' reload '@SERVICENAME@' 10 | ExecStop='@SERVICEMANAGER@' stop '@SERVICENAME@' 11 | Restart=no 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /servicemanager/servicemanager_mac.launchd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Label 6 | com.servicemanager.@SERVICENAME@ 7 | ProgramArguments 8 | 9 | @SERVICEMANAGER@ 10 | run 11 | @SERVICENAME@ 12 | 13 | KeepAlive 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /servicemanager/servicemanager_nix.sysvinit: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: @SERVICENAME@ 4 | # Required-Start: $local_fs $remote_fs $network 5 | # Required-Stop: $local_fs $remote_fs $network 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: starts @SERVICENAME@ 9 | # Description: starts @SERVICENAME@ 10 | ### END INIT INFO 11 | 12 | '@SERVICEMANAGER@' $1 '@SERVICENAME@' 13 | 14 | if [ "$?" != 0 ] ; then 15 | echo 16 | echo "Service Manager returned a failure code." 17 | echo 18 | echo "Usage: $0 {start|stop|restart|reload|status|configfile|custom-action-name|uninstall}" 19 | 20 | exit 1 21 | fi 22 | -------------------------------------------------------------------------------- /support/phpseclib/license.txt: -------------------------------------------------------------------------------- 1 | Copyright 2007-2016 TerraFrost and other contributors 2 | http://phpseclib.sourceforge.net/ 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /support/event_manager.php: -------------------------------------------------------------------------------- 1 | events = array(); 12 | $this->nextid = 1; 13 | } 14 | 15 | public function Register($eventname, $objorfuncname, $funcname = false) 16 | { 17 | if ($objorfuncname === false) 18 | { 19 | $objorfuncname = $funcname; 20 | $funcname = false; 21 | } 22 | 23 | if (!isset($this->events[$eventname])) $this->events[$eventname] = array("used" => 0, "callbacks" => array()); 24 | $this->events[$eventname]["callbacks"][$this->nextid] = ($funcname === false ? $objorfuncname : array($objorfuncname, $funcname)); 25 | 26 | $id = $this->nextid; 27 | $this->nextid++; 28 | 29 | return $id; 30 | } 31 | 32 | public function Unregister($eventname, $id) 33 | { 34 | if (isset($this->events[$eventname])) unset($this->events[$eventname]["callbacks"][$id]); 35 | } 36 | 37 | public function GetAllUsedCounts() 38 | { 39 | $result = array(); 40 | foreach ($this->events as $eventname => $info) $result[$eventname] = $info["used"]; 41 | 42 | return $result; 43 | } 44 | 45 | public function GetUsedCount($eventname) 46 | { 47 | return (isset($this->events[$eventname]) ? $this->events[$eventname]["used"] : 0); 48 | } 49 | 50 | public function Fire($eventname, $options) 51 | { 52 | $results = array(); 53 | if ($eventname !== "" && isset($this->events[$eventname])) 54 | { 55 | foreach ($this->events[$eventname]["callbacks"] as $id => $func) 56 | { 57 | if (!is_callable($func)) unset($this->events[$eventname]["callbacks"][$id]); 58 | else 59 | { 60 | $result = call_user_func_array($func, $options); 61 | if (isset($result)) $results[] = $result; 62 | } 63 | } 64 | 65 | if (count($this->events[$eventname]["callbacks"])) $this->events[$eventname]["used"]++; 66 | else unset($this->events[$eventname]); 67 | } 68 | 69 | if (isset($this->events[""])) 70 | { 71 | foreach ($this->events[""]["callbacks"] as $id => $func) 72 | { 73 | if (!is_callable($func)) unset($this->events[""]["callbacks"][$id]); 74 | else 75 | { 76 | $result = call_user_func_array($func, array_merge(array($eventname), $options)); 77 | if (isset($result)) $results[] = $result; 78 | } 79 | } 80 | 81 | if (count($this->events[""]["callbacks"])) $this->events[""]["used"]++; 82 | else unset($this->events[""]); 83 | } 84 | 85 | return $results; 86 | } 87 | } 88 | ?> -------------------------------------------------------------------------------- /sdks/php/sdk_remotedapi.php: -------------------------------------------------------------------------------- 1 | false, "error" => WebRoute::WRTranslate("Invalid Remoted API URL scheme."), "errorcode" => "invalid_scheme"); 45 | 46 | $result["url"] = $url; 47 | 48 | return $result; 49 | } 50 | 51 | if ($url2["loginusername"] === "") return array("success" => false, "error" => WebRoute::WRTranslate("Remoted API URL is missing client key."), "errorcode" => "missing_client_key"); 52 | 53 | $options["headers"]["X-Remoted-APIKey"] = $url2["loginusername"]; 54 | 55 | $url2["scheme"] = ($url2["scheme"] === "rwr" ? "wr" : "wrs"); 56 | unset($url2["loginusername"]); 57 | unset($url2["login"]); 58 | 59 | $url = HTTP::CondenseURL($url2); 60 | 61 | $result = $wr->Connect($url, false, $timeout, $options, $web); 62 | if (!$result["success"]) return $result; 63 | 64 | $options["fp"] = $result["fp"]; 65 | } 66 | 67 | return $result; 68 | } 69 | } 70 | ?> -------------------------------------------------------------------------------- /sdks/php/crc32_stream.php: -------------------------------------------------------------------------------- 1 | 0x04C11DB7, "start" => 0xFFFFFFFF, "xor" => 0xFFFFFFFF, "refdata" => 1, "refcrc" => 1); 14 | 15 | public function __construct() 16 | { 17 | $this->open = false; 18 | } 19 | 20 | public function Init($options = false) 21 | { 22 | if ($options === false && function_exists("hash_init")) $this->hash = hash_init("crc32b"); 23 | else 24 | { 25 | if ($options === false) $options = self::$default; 26 | 27 | $this->hash = false; 28 | $this->crctable = array(); 29 | $poly = $this->LIM32($options["poly"]); 30 | for ($x = 0; $x < 256; $x++) 31 | { 32 | $c = $this->SHL32($x, 24); 33 | for ($y = 0; $y < 8; $y++) $c = $this->SHL32($c, 1) ^ ($c & 0x80000000 ? $poly : 0); 34 | $this->crctable[$x] = $c; 35 | } 36 | 37 | $this->datareflect = $options["refdata"]; 38 | $this->crcreflect = $options["refcrc"]; 39 | $this->firstcrc = $options["start"]; 40 | $this->currcrc = $options["start"]; 41 | $this->finalxor = $options["xor"]; 42 | } 43 | 44 | $this->open = true; 45 | } 46 | 47 | public function AddData($data) 48 | { 49 | if (!$this->open) return false; 50 | 51 | if ($this->hash !== false) hash_update($this->hash, $data); 52 | else 53 | { 54 | $y = strlen($data); 55 | 56 | for ($x = 0; $x < $y; $x++) 57 | { 58 | if ($this->datareflect) $this->currcrc = $this->SHL32($this->currcrc, 8) ^ $this->crctable[$this->SHR32($this->currcrc, 24) ^ self::$revlookup[ord($data[$x])]]; 59 | else $this->currcrc = $this->SHL32($this->currcrc, 8) ^ $this->crctable[$this->SHR32($this->currcrc, 24) ^ ord($data[$x])]; 60 | } 61 | } 62 | 63 | return true; 64 | } 65 | 66 | public function Finalize() 67 | { 68 | if (!$this->open) return false; 69 | 70 | if ($this->hash !== false) 71 | { 72 | $result = hexdec(hash_final($this->hash)); 73 | 74 | $this->hash = hash_init("crc32b"); 75 | } 76 | else 77 | { 78 | if ($this->crcreflect) 79 | { 80 | $tempcrc = $this->currcrc; 81 | $this->currcrc = self::$revlookup[$this->SHR32($tempcrc, 24)] | $this->SHL32(self::$revlookup[$this->SHR32($tempcrc, 16) & 0xFF], 8) | $this->SHL32(self::$revlookup[$this->SHR32($tempcrc, 8) & 0xFF], 16) | $this->SHL32(self::$revlookup[$this->LIM32($tempcrc & 0xFF)], 24); 82 | } 83 | $result = $this->currcrc ^ $this->finalxor; 84 | 85 | $this->currcrc = $this->firstcrc; 86 | } 87 | 88 | return $result; 89 | } 90 | 91 | // These functions are a hacky, but effective way of enforcing unsigned 32-bit integers onto a generic signed int. 92 | // Allow bitwise operations to work across platforms. Minimum integer size must be 32-bit. 93 | private function SHR32($num, $bits) 94 | { 95 | $num = (int)$num; 96 | if ($bits < 0) $bits = 0; 97 | 98 | if ($num < 0 && $bits) 99 | { 100 | $num = ($num >> 1) & 0x7FFFFFFF; 101 | $bits--; 102 | } 103 | 104 | return $this->LIM32($num >> $bits); 105 | } 106 | 107 | private function SHL32($num, $bits) 108 | { 109 | if ($bits < 0) $bits = 0; 110 | 111 | return $this->LIM32((int)$num << $bits); 112 | } 113 | 114 | private function LIM32($num) 115 | { 116 | return (int)((int)$num & 0xFFFFFFFF); 117 | } 118 | } 119 | ?> -------------------------------------------------------------------------------- /support/crc32_stream.php: -------------------------------------------------------------------------------- 1 | 0x04C11DB7, "start" => 0xFFFFFFFF, "xor" => 0xFFFFFFFF, "refdata" => 1, "refcrc" => 1); 14 | 15 | public function __construct() 16 | { 17 | $this->open = false; 18 | } 19 | 20 | public function Init($options = false) 21 | { 22 | if ($options === false && function_exists("hash_init")) $this->hash = hash_init("crc32b"); 23 | else 24 | { 25 | if ($options === false) $options = self::$default; 26 | 27 | $this->hash = false; 28 | $this->crctable = array(); 29 | $poly = $this->LIM32($options["poly"]); 30 | for ($x = 0; $x < 256; $x++) 31 | { 32 | $c = $this->SHL32($x, 24); 33 | for ($y = 0; $y < 8; $y++) $c = $this->SHL32($c, 1) ^ ($c & 0x80000000 ? $poly : 0); 34 | $this->crctable[$x] = $c; 35 | } 36 | 37 | $this->datareflect = $options["refdata"]; 38 | $this->crcreflect = $options["refcrc"]; 39 | $this->firstcrc = $options["start"]; 40 | $this->currcrc = $options["start"]; 41 | $this->finalxor = $options["xor"]; 42 | } 43 | 44 | $this->open = true; 45 | } 46 | 47 | public function AddData($data) 48 | { 49 | if (!$this->open) return false; 50 | 51 | if ($this->hash !== false) hash_update($this->hash, $data); 52 | else 53 | { 54 | $y = strlen($data); 55 | 56 | for ($x = 0; $x < $y; $x++) 57 | { 58 | if ($this->datareflect) $this->currcrc = $this->SHL32($this->currcrc, 8) ^ $this->crctable[$this->SHR32($this->currcrc, 24) ^ self::$revlookup[ord($data[$x])]]; 59 | else $this->currcrc = $this->SHL32($this->currcrc, 8) ^ $this->crctable[$this->SHR32($this->currcrc, 24) ^ ord($data[$x])]; 60 | } 61 | } 62 | 63 | return true; 64 | } 65 | 66 | public function Finalize() 67 | { 68 | if (!$this->open) return false; 69 | 70 | if ($this->hash !== false) 71 | { 72 | $result = hexdec(hash_final($this->hash)); 73 | 74 | $this->hash = hash_init("crc32b"); 75 | } 76 | else 77 | { 78 | if ($this->crcreflect) 79 | { 80 | $tempcrc = $this->currcrc; 81 | $this->currcrc = self::$revlookup[$this->SHR32($tempcrc, 24)] | $this->SHL32(self::$revlookup[$this->SHR32($tempcrc, 16) & 0xFF], 8) | $this->SHL32(self::$revlookup[$this->SHR32($tempcrc, 8) & 0xFF], 16) | $this->SHL32(self::$revlookup[$this->LIM32($tempcrc & 0xFF)], 24); 82 | } 83 | $result = $this->currcrc ^ $this->finalxor; 84 | 85 | $this->currcrc = $this->firstcrc; 86 | } 87 | 88 | return $result; 89 | } 90 | 91 | // These functions are a hacky, but effective way of enforcing unsigned 32-bit integers onto a generic signed int. 92 | // Allow bitwise operations to work across platforms. Minimum integer size must be 32-bit. 93 | private function SHR32($num, $bits) 94 | { 95 | $num = (int)$num; 96 | if ($bits < 0) $bits = 0; 97 | 98 | if ($num < 0 && $bits) 99 | { 100 | $num = ($num >> 1) & 0x7FFFFFFF; 101 | $bits--; 102 | } 103 | 104 | return $this->LIM32($num >> $bits); 105 | } 106 | 107 | private function SHL32($num, $bits) 108 | { 109 | if ($bits < 0) $bits = 0; 110 | 111 | return $this->LIM32((int)$num << $bits); 112 | } 113 | 114 | private function LIM32($num) 115 | { 116 | return (int)((int)$num & 0xFFFFFFFF); 117 | } 118 | } 119 | ?> -------------------------------------------------------------------------------- /support/str_basics.php: -------------------------------------------------------------------------------- 1 | $val) 10 | { 11 | if (is_string($val)) $_REQUEST[$key] = trim($val); 12 | else if (is_array($val)) 13 | { 14 | $_REQUEST[$key] = array(); 15 | foreach ($val as $key2 => $val2) $_REQUEST[$key][$key2] = (is_string($val2) ? trim($val2) : $val2); 16 | } 17 | else $_REQUEST[$key] = $val; 18 | } 19 | } 20 | 21 | // Cleans up all PHP input issues so that $_REQUEST may be used as expected. 22 | public static function ProcessAllInput() 23 | { 24 | self::ProcessSingleInput($_COOKIE); 25 | self::ProcessSingleInput($_GET); 26 | self::ProcessSingleInput($_POST); 27 | } 28 | 29 | public static function ExtractPathname($dirfile) 30 | { 31 | $dirfile = str_replace("\\", "/", $dirfile); 32 | $pos = strrpos($dirfile, "/"); 33 | if ($pos === false) $dirfile = ""; 34 | else $dirfile = substr($dirfile, 0, $pos + 1); 35 | 36 | return $dirfile; 37 | } 38 | 39 | public static function ExtractFilename($dirfile) 40 | { 41 | $dirfile = str_replace("\\", "/", $dirfile); 42 | $pos = strrpos($dirfile, "/"); 43 | if ($pos !== false) $dirfile = substr($dirfile, $pos + 1); 44 | 45 | return $dirfile; 46 | } 47 | 48 | public static function ExtractFileExtension($dirfile) 49 | { 50 | $dirfile = self::ExtractFilename($dirfile); 51 | $pos = strrpos($dirfile, "."); 52 | if ($pos !== false) $dirfile = substr($dirfile, $pos + 1); 53 | else $dirfile = ""; 54 | 55 | return $dirfile; 56 | } 57 | 58 | public static function ExtractFilenameNoExtension($dirfile) 59 | { 60 | $dirfile = self::ExtractFilename($dirfile); 61 | $pos = strrpos($dirfile, "."); 62 | if ($pos !== false) $dirfile = substr($dirfile, 0, $pos); 63 | 64 | return $dirfile; 65 | } 66 | 67 | // Makes an input filename safe for use. 68 | // Allows a very limited number of characters through. 69 | public static function FilenameSafe($filename) 70 | { 71 | return preg_replace('/\s+/', "-", trim(trim(preg_replace('/[^A-Za-z0-9_.\-]/', " ", $filename), "."))); 72 | } 73 | 74 | public static function ReplaceNewlines($replacewith, $data) 75 | { 76 | $data = str_replace("\r\n", "\n", $data); 77 | $data = str_replace("\r", "\n", $data); 78 | $data = str_replace("\n", $replacewith, $data); 79 | 80 | return $data; 81 | } 82 | 83 | public static function LineInput($data, &$pos) 84 | { 85 | $CR = ord("\r"); 86 | $LF = ord("\n"); 87 | 88 | $result = ""; 89 | $y = strlen($data); 90 | if ($pos > $y) $pos = $y; 91 | while ($pos < $y && ord($data[$pos]) != $CR && ord($data[$pos]) != $LF) 92 | { 93 | $result .= $data[$pos]; 94 | $pos++; 95 | } 96 | if ($pos + 1 < $y && ord($data[$pos]) == $CR && ord($data[$pos + 1]) == $LF) $pos++; 97 | if ($pos < $y) $pos++; 98 | 99 | return $result; 100 | } 101 | 102 | // Constant-time string comparison. Ported from CubicleSoft C++ code. 103 | public static function CTstrcmp($secret, $userinput) 104 | { 105 | $sx = 0; 106 | $sy = strlen($secret); 107 | $uy = strlen($userinput); 108 | $result = $sy - $uy; 109 | for ($ux = 0; $ux < $uy; $ux++) 110 | { 111 | $result |= ord($userinput[$ux]) ^ ord($secret[$sx]); 112 | $sx = ($sx + 1) % $sy; 113 | } 114 | 115 | return $result; 116 | } 117 | 118 | public static function ConvertUserStrToBytes($str) 119 | { 120 | $str = trim($str); 121 | $num = (double)$str; 122 | if (strtoupper(substr($str, -1)) == "B") $str = substr($str, 0, -1); 123 | switch (strtoupper(substr($str, -1))) 124 | { 125 | case "P": $num *= 1024; 126 | case "T": $num *= 1024; 127 | case "G": $num *= 1024; 128 | case "M": $num *= 1024; 129 | case "K": $num *= 1024; 130 | } 131 | 132 | return $num; 133 | } 134 | 135 | public static function ConvertBytesToUserStr($num) 136 | { 137 | $num = (double)$num; 138 | 139 | if ($num < 0) return "0 B"; 140 | if ($num < 1024) return number_format($num, 0) . " B"; 141 | if ($num < 1048576) return str_replace(".0 ", "", number_format($num / 1024, 1)) . " KB"; 142 | if ($num < 1073741824) return str_replace(".0 ", "", number_format($num / 1048576, 1)) . " MB"; 143 | if ($num < 1099511627776.0) return str_replace(".0 ", "", number_format($num / 1073741824.0, 1)) . " GB"; 144 | if ($num < 1125899906842624.0) return str_replace(".0 ", "", number_format($num / 1099511627776.0, 1)) . " TB"; 145 | 146 | return str_replace(".0 ", "", number_format($num / 1125899906842624.0, 1)) . " PB"; 147 | } 148 | 149 | public static function JSSafe($data) 150 | { 151 | return str_replace(array("'", "\r", "\n"), array("\\'", "\\r", "\\n"), $data); 152 | } 153 | } 154 | ?> -------------------------------------------------------------------------------- /support/servicemanager.php: -------------------------------------------------------------------------------- 1 | rootpath = str_replace(array("\\", "/"), DIRECTORY_SEPARATOR, $rootpath); 12 | } 13 | 14 | public function Install($servicename, $phpfile, $args, $options = array(), $display = false) 15 | { 16 | $cmd = escapeshellarg($this->GetServiceManagerRealpath()); 17 | if (!isset($options["dir"])) $options["dir"] = $this->rootpath; 18 | foreach ($options as $key => $val) $cmd .= " " . escapeshellarg("-" . $key . "=" . $val); 19 | $cmd .= " install " . escapeshellarg($servicename); 20 | $cmd .= " " . escapeshellarg($phpfile . ".notify"); 21 | $cmd .= " " . escapeshellarg($this->GetPHPBinary()); 22 | $cmd .= " " . escapeshellarg($phpfile); 23 | foreach ($args as $arg) $cmd .= " " . escapeshellarg($arg); 24 | 25 | return $this->RunCommand($cmd, $display); 26 | } 27 | 28 | public function Uninstall($servicename, $display = false) 29 | { 30 | $cmd = escapeshellarg($this->GetServiceManagerRealpath()); 31 | $cmd .= " uninstall " . escapeshellarg($servicename); 32 | 33 | return $this->RunCommand($cmd, $display); 34 | } 35 | 36 | public function Start($servicename, $display = false) 37 | { 38 | $cmd = escapeshellarg($this->GetServiceManagerRealpath()); 39 | $cmd .= " start " . escapeshellarg($servicename); 40 | 41 | return $this->RunCommand($cmd, $display); 42 | } 43 | 44 | public function Stop($servicename, $display = false) 45 | { 46 | $cmd = escapeshellarg($this->GetServiceManagerRealpath()); 47 | $cmd .= " stop " . escapeshellarg($servicename); 48 | 49 | return $this->RunCommand($cmd, $display); 50 | } 51 | 52 | public function Restart($servicename, $display = false) 53 | { 54 | $cmd = escapeshellarg($this->GetServiceManagerRealpath()); 55 | $cmd .= " restart " . escapeshellarg($servicename); 56 | 57 | return $this->RunCommand($cmd, $display); 58 | } 59 | 60 | public function Reload($servicename, $display = false) 61 | { 62 | $cmd = escapeshellarg($this->GetServiceManagerRealpath()); 63 | $cmd .= " reload " . escapeshellarg($servicename); 64 | 65 | return $this->RunCommand($cmd, $display); 66 | } 67 | 68 | public function WaitFor($servicename, $display = false) 69 | { 70 | $cmd = escapeshellarg($this->GetServiceManagerRealpath()); 71 | $cmd .= " waitfor " . escapeshellarg($servicename); 72 | 73 | return $this->RunCommand($cmd, $display); 74 | } 75 | 76 | public function Status($servicename, $display = false) 77 | { 78 | $cmd = escapeshellarg($this->GetServiceManagerRealpath()); 79 | $cmd .= " status " . escapeshellarg($servicename); 80 | 81 | return $this->RunCommand($cmd, $display); 82 | } 83 | 84 | public function GetConfig($servicename) 85 | { 86 | $cmd = escapeshellarg($this->GetServiceManagerRealpath()); 87 | $cmd .= " configfile " . escapeshellarg($servicename); 88 | 89 | $result = $this->RunCommand($cmd); 90 | if (!$result["success"]) return $result; 91 | 92 | $filename = trim($result["output"]); 93 | if (strtolower(substr($filename, 0, 6)) === "error:") return array("success" => false, "error" => self::SMTranslate("Unable to locate the '%s' configuration file.", $servicename), "errorcode" => "missing_config", "info" => $result); 94 | 95 | $fp = fopen($filename, "rb"); 96 | if ($fp === false) return array("success" => false, "error" => self::SMTranslate("Unable to open the configuration file '%s' for reading.", $filename), "errorcode" => "fopen_failed"); 97 | 98 | $result = array( 99 | "success" => true, 100 | "filename" => $filename, 101 | "options" => array() 102 | ); 103 | 104 | while (($line = fgets($fp)) !== false) 105 | { 106 | $line = trim($line); 107 | 108 | $pos = strpos($line, "="); 109 | if ($pos !== false) 110 | { 111 | $key = substr($line, 0, $pos); 112 | $val = (string)substr($line, $pos + 1); 113 | 114 | if (!isset($result["options"][$key])) $result["options"][$key] = $val; 115 | } 116 | 117 | if (feof($fp)) break; 118 | } 119 | 120 | fclose($fp); 121 | 122 | return $result; 123 | } 124 | 125 | public function AddAction($servicename, $actionname, $actiondesc, $phpfile, $args, $display = false) 126 | { 127 | $cmd = escapeshellarg($this->GetServiceManagerRealpath()); 128 | $cmd .= " addaction " . escapeshellarg($servicename); 129 | $cmd .= " " . escapeshellarg($actionname); 130 | $cmd .= " " . escapeshellarg($actiondesc); 131 | $cmd .= " " . escapeshellarg($this->GetPHPBinary()); 132 | $cmd .= " " . escapeshellarg($phpfile); 133 | foreach ($args as $arg) $cmd .= " " . escapeshellarg($arg); 134 | 135 | return $this->RunCommand($cmd, $display); 136 | } 137 | 138 | public function GetServiceManagerRealpath() 139 | { 140 | $os = php_uname("s"); 141 | 142 | if (strtoupper(substr($os, 0, 3)) == "WIN") $result = $this->rootpath . "\\servicemanager.exe"; 143 | else 144 | { 145 | if (file_exists($this->rootpath . "/servicemanager_nix")) $result = $this->rootpath . "/servicemanager_nix"; 146 | else if (file_exists("/usr/local/bin/servicemanager")) $result = "/usr/local/bin/servicemanager"; 147 | else if (strtoupper(substr($os, 0, 6)) == "DARWIN") $result = $this->rootpath . "/servicemanager_mac"; 148 | else if (PHP_INT_SIZE >= 8) $result = $this->rootpath . "/servicemanager_nix_64"; 149 | else $result = $this->rootpath . "/servicemanager_nix_32"; 150 | 151 | @chmod($result, 0755); 152 | } 153 | 154 | return $result; 155 | } 156 | 157 | public function GetPHPBinary() 158 | { 159 | if (file_exists("/usr/bin/php") && realpath("/usr/bin/php") === PHP_BINARY) $result = "/usr/bin/php"; 160 | else $result = PHP_BINARY; 161 | 162 | return $result; 163 | } 164 | 165 | public function RunCommand($cmd, $display = false) 166 | { 167 | if ($display) echo self::SMTranslate("Running command: %s", $cmd) . "\n\n"; 168 | 169 | $fp = popen($cmd, "rb"); 170 | if ($fp === false) return array("success" => false, "error" => self::SMTranslate("The executable failed to start."), "errorcode" => "start_process", "info" => $cmd); 171 | 172 | $data = ""; 173 | while (($data2 = fread($fp, 65536)) !== false) 174 | { 175 | if ($display) echo $data2; 176 | $data .= $data2; 177 | 178 | if (feof($fp)) break; 179 | } 180 | 181 | pclose($fp); 182 | 183 | if ($display) echo "\n"; 184 | 185 | return array("success" => true, "cmd" => $cmd, "output" => $data); 186 | } 187 | 188 | protected static function SMTranslate() 189 | { 190 | $args = func_get_args(); 191 | if (!count($args)) return ""; 192 | 193 | return call_user_func_array((defined("CS_TRANSLATE_FUNC") && function_exists(CS_TRANSLATE_FUNC) ? CS_TRANSLATE_FUNC : "sprintf"), $args); 194 | } 195 | } 196 | ?> -------------------------------------------------------------------------------- /sdks/php/sdk_cloud_storage_server_files.php: -------------------------------------------------------------------------------- 1 | apiprefix = "/files/v1"; 16 | } 17 | 18 | public function GetObjectByPath($path) 19 | { 20 | if (substr($path, 0, 1) !== "/") $path = "/" . $path; 21 | 22 | return $this->RunAPI("GET", "object/bypath" . $path); 23 | } 24 | 25 | public function GetRootFolderID() 26 | { 27 | return $this->RunAPI("GET", "user/root"); 28 | } 29 | 30 | public function GetFolderList($folderid = "0") 31 | { 32 | $result = $this->RunAPI("GET", "folder/list/" . $folderid); 33 | if (!$result["success"]) return $result; 34 | 35 | $folders = array(); 36 | $files = array(); 37 | foreach ($result["body"]["items"] as $item) 38 | { 39 | if ($item["type"] === "file") $files[$item["name"]] = $item; 40 | else $folders[$item["name"]] = $item; 41 | } 42 | 43 | return array("success" => true, "id" => $folderid, "folders" => $folders, "files" => $files); 44 | } 45 | 46 | public function GetObjectIDByName($folderid, $name) 47 | { 48 | return $this->RunAPI("GET", "object/byname/" . $folderid . "/" . $name); 49 | } 50 | 51 | public function CreateFolder($folderid, $name) 52 | { 53 | $options = array( 54 | "folderid" => $folderid, 55 | "name" => $name 56 | ); 57 | 58 | $result = $this->RunAPI("POST", "folder/create", $options); 59 | if (!$result["success"]) return $result; 60 | 61 | $result["id"] = $result["body"]["folder"]["id"]; 62 | 63 | return $result; 64 | } 65 | 66 | public function GetTrashList($folderid = false) 67 | { 68 | return $this->RunAPI("GET", "trash/list" . ($folderid !== false ? "/" . $folderid : "")); 69 | } 70 | 71 | public function GetObjectByID($id) 72 | { 73 | return $this->RunAPI("GET", "object/byid/" . $id); 74 | } 75 | 76 | public function CopyObject($srcid, $destid) 77 | { 78 | $options = array( 79 | "srcid" => $srcid, 80 | "destid" => $destid 81 | ); 82 | 83 | $result = $this->RunAPI("POST", "object/copy", $options); 84 | if (!$result["success"]) return $result; 85 | 86 | $result["id"] = $result["body"]["id"]; 87 | 88 | return $result; 89 | } 90 | 91 | public function MoveObject($srcid, $destfolderid) 92 | { 93 | $options = array( 94 | "srcid" => $srcid, 95 | "destfolderid" => $destfolderid 96 | ); 97 | 98 | return $this->RunAPI("POST", "object/move", $options); 99 | } 100 | 101 | public function RenameObject($id, $newname) 102 | { 103 | $options = array( 104 | "name" => $newname 105 | ); 106 | 107 | return $this->RunAPI("POST", "object/rename/" . $id, $options); 108 | } 109 | 110 | public function TrashObject($id) 111 | { 112 | return $this->RunAPI("POST", "object/trash/" . $id); 113 | } 114 | 115 | public function RestoreObject($id) 116 | { 117 | return $this->RunAPI("POST", "object/restore/" . $id); 118 | } 119 | 120 | public function DeleteObject($id) 121 | { 122 | return $this->RunAPI("DELETE", "object/delete/" . $id); 123 | } 124 | 125 | public function GetUserLimits() 126 | { 127 | return $this->RunAPI("GET", "user/limits"); 128 | } 129 | 130 | public function UploadFile($folderid, $destfilename, $data, $srcfilename, $fileid = false, $callback = false, $callbackopts = false) 131 | { 132 | // Determine if there is a file at the target already. It is more efficient than uploading and discovering afterwards. 133 | if ($fileid === false) 134 | { 135 | $result = $this->GetObjectIDByName($folderid, $destfilename); 136 | if (!$result["success"] && $result["errorcode"] !== "object_not_found") return $result; 137 | 138 | if ($result["success"] && $result["body"]["object"]["type"] !== "file") return array("success" => false, "error" => self::CSS_Translate("Parent folder already contains an object named '%s' that is not a file.", $destfilename), "errorcode" => "object_already_exists"); 139 | } 140 | 141 | $fileinfo = array( 142 | "name" => "data", 143 | "filename" => $destfilename, 144 | "type" => "application/octet-stream" 145 | ); 146 | 147 | if ($srcfilename !== false) $fileinfo["datafile"] = $srcfilename; 148 | else $fileinfo["data"] = $data; 149 | 150 | $options = array( 151 | "debug_callback" => $callback, 152 | "debug_callback_opts" => $callbackopts, 153 | "postvars" => array( 154 | "name" => $destfilename 155 | ), 156 | "files" => array($fileinfo) 157 | ); 158 | 159 | return $this->RunAPI("POST", "file/upload/" . $folderid, $options, 200, false); 160 | } 161 | 162 | public function DownloadFile__Internal($response, $body, &$opts) 163 | { 164 | fwrite($opts["fp"], $body); 165 | 166 | if (is_callable($opts["callback"])) call_user_func_array($opts["callback"], array(&$opts)); 167 | 168 | return true; 169 | } 170 | 171 | // Callback option only used when destination is a file. 172 | public function DownloadFile($destfileorfp, $fileid, $callback = false, $database = false) 173 | { 174 | if ($destfileorfp === false) $options = array(); 175 | else 176 | { 177 | $fp = (is_resource($destfileorfp) ? $destfileorfp : fopen($destfileorfp, "wb")); 178 | if ($fp === false) return array("success" => false, "error" => self::CSS_Translate("Invalid destination filename or handle."), "errorcode" => "invalid_filename_or_handle"); 179 | 180 | $options = array( 181 | "read_body_callback" => array($this, "DownloadFile__Internal"), 182 | "read_body_callback_opts" => array("fp" => $fp, "fileid" => $fileid, "callback" => $callback) 183 | ); 184 | } 185 | 186 | if ($database) $result = $this->RunAPI("GET", "file/downloaddatabase", $options, 200, true, false); 187 | else $result = $this->RunAPI("GET", "file/download/" . $fileid, $options, 200, true, false); 188 | 189 | if ($destfileorfp !== false && !is_resource($destfileorfp)) fclose($fp); 190 | 191 | return $result; 192 | } 193 | 194 | public function CreateGuest($rootid, $read, $write, $delete, $expires) 195 | { 196 | $options = array( 197 | "rootid" => $rootid, 198 | "read" => (int)(bool)$read, 199 | "write" => (int)(bool)$write, 200 | "delete" => (int)(bool)$delete, 201 | "expires" => (int)$expires 202 | ); 203 | 204 | return $this->RunAPI("POST", "guest/create", $options); 205 | } 206 | 207 | public function GetGuestList() 208 | { 209 | return $this->RunAPI("GET", "guest/list"); 210 | } 211 | 212 | public function DeleteGuest($id) 213 | { 214 | return $this->RunAPI("DELETE", "guest/delete/" . $id); 215 | } 216 | } 217 | ?> -------------------------------------------------------------------------------- /support/phpseclib/Crypt/AES.php: -------------------------------------------------------------------------------- 1 | 25 | * setKey('abcdefghijklmnop'); 31 | * 32 | * $size = 10 * 1024; 33 | * $plaintext = ''; 34 | * for ($i = 0; $i < $size; $i++) { 35 | * $plaintext.= 'a'; 36 | * } 37 | * 38 | * echo $aes->decrypt($aes->encrypt($plaintext)); 39 | * ?> 40 | * 41 | * 42 | * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy 43 | * of this software and associated documentation files (the "Software"), to deal 44 | * in the Software without restriction, including without limitation the rights 45 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 46 | * copies of the Software, and to permit persons to whom the Software is 47 | * furnished to do so, subject to the following conditions: 48 | * 49 | * The above copyright notice and this permission notice shall be included in 50 | * all copies or substantial portions of the Software. 51 | * 52 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 53 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 54 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 55 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 56 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 57 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 58 | * THE SOFTWARE. 59 | * 60 | * @category Crypt 61 | * @package Crypt_AES 62 | * @author Jim Wigginton 63 | * @copyright 2008 Jim Wigginton 64 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 65 | * @link http://phpseclib.sourceforge.net 66 | */ 67 | 68 | /** 69 | * Include Crypt_Rijndael 70 | */ 71 | if (!class_exists('Crypt_Rijndael')) { 72 | include_once 'Rijndael.php'; 73 | } 74 | 75 | /**#@+ 76 | * @access public 77 | * @see self::encrypt() 78 | * @see self::decrypt() 79 | */ 80 | /** 81 | * Encrypt / decrypt using the Counter mode. 82 | * 83 | * Set to -1 since that's what Crypt/Random.php uses to index the CTR mode. 84 | * 85 | * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29 86 | */ 87 | define('CRYPT_AES_MODE_CTR', CRYPT_MODE_CTR); 88 | /** 89 | * Encrypt / decrypt using the Electronic Code Book mode. 90 | * 91 | * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29 92 | */ 93 | define('CRYPT_AES_MODE_ECB', CRYPT_MODE_ECB); 94 | /** 95 | * Encrypt / decrypt using the Code Book Chaining mode. 96 | * 97 | * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29 98 | */ 99 | define('CRYPT_AES_MODE_CBC', CRYPT_MODE_CBC); 100 | /** 101 | * Encrypt / decrypt using the Cipher Feedback mode. 102 | * 103 | * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29 104 | */ 105 | define('CRYPT_AES_MODE_CFB', CRYPT_MODE_CFB); 106 | /** 107 | * Encrypt / decrypt using the Cipher Feedback mode. 108 | * 109 | * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29 110 | */ 111 | define('CRYPT_AES_MODE_OFB', CRYPT_MODE_OFB); 112 | /**#@-*/ 113 | 114 | /** 115 | * Pure-PHP implementation of AES. 116 | * 117 | * @package Crypt_AES 118 | * @author Jim Wigginton 119 | * @access public 120 | */ 121 | class Crypt_AES extends Crypt_Rijndael 122 | { 123 | /** 124 | * The namespace used by the cipher for its constants. 125 | * 126 | * @see Crypt_Base::const_namespace 127 | * @var string 128 | * @access private 129 | */ 130 | var $const_namespace = 'AES'; 131 | 132 | /** 133 | * Dummy function 134 | * 135 | * Since Crypt_AES extends Crypt_Rijndael, this function is, technically, available, but it doesn't do anything. 136 | * 137 | * @see Crypt_Rijndael::setBlockLength() 138 | * @access public 139 | * @param int $length 140 | */ 141 | function setBlockLength($length) 142 | { 143 | return; 144 | } 145 | 146 | /** 147 | * Sets the key length 148 | * 149 | * Valid key lengths are 128, 192, and 256. If the length is less than 128, it will be rounded up to 150 | * 128. If the length is greater than 128 and invalid, it will be rounded down to the closest valid amount. 151 | * 152 | * @see Crypt_Rijndael:setKeyLength() 153 | * @access public 154 | * @param int $length 155 | */ 156 | function setKeyLength($length) 157 | { 158 | switch ($length) { 159 | case 160: 160 | $length = 192; 161 | break; 162 | case 224: 163 | $length = 256; 164 | } 165 | parent::setKeyLength($length); 166 | } 167 | 168 | /** 169 | * Sets the key. 170 | * 171 | * Rijndael supports five different key lengths, AES only supports three. 172 | * 173 | * @see Crypt_Rijndael:setKey() 174 | * @see setKeyLength() 175 | * @access public 176 | * @param string $key 177 | */ 178 | function setKey($key) 179 | { 180 | parent::setKey($key); 181 | 182 | if (!$this->explicit_key_length) { 183 | $length = strlen($key); 184 | switch (true) { 185 | case $length <= 16: 186 | $this->key_length = 16; 187 | break; 188 | case $length <= 24: 189 | $this->key_length = 24; 190 | break; 191 | default: 192 | $this->key_length = 32; 193 | } 194 | $this->_setEngine(); 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /support/remotedapi_web_server.php: -------------------------------------------------------------------------------- 1 | rws = false; 17 | $this->rhost = false; 18 | $this->rwr = false; 19 | $this->rwrclients = array(); 20 | $this->rwrnextclientid = 1; 21 | } 22 | 23 | // Helper function to decide which class to use to handle the server. 24 | public static function IsRemoted($host) 25 | { 26 | return (strtolower(substr($host, 0, 6)) === "rws://" || strtolower(substr($host, 0, 7)) === "rwss://"); 27 | } 28 | 29 | // Reconnects to the WebSocket. 30 | // This is a blocking call. In theory, that shouldn't cause too many problems. 31 | private function ReconnectRemotedAPIWebSocket() 32 | { 33 | if (!class_exists("HTTP", false)) require_once str_replace("\\", "/", dirname(__FILE__)) . "/http.php"; 34 | if (!class_exists("WebRoute", false)) require_once str_replace("\\", "/", dirname(__FILE__)) . "/webroute.php"; 35 | 36 | $url = $this->rhost; 37 | if ($url["scheme"] !== "rws" && $url["scheme"] !== "rwss") return array("success" => false, "error" => HTTP::HTTPTranslate("Invalid Remoted API URL scheme."), "errorcode" => "invalid_scheme"); 38 | if ($url["loginusername"] === "") return array("success" => false, "error" => HTTP::HTTPTranslate("Remoted API URL is missing server key."), "errorcode" => "missing_server_key"); 39 | 40 | $options = array("headers" => array()); 41 | $options["headers"]["X-Remoted-APIKey"] = $url["loginusername"]; 42 | 43 | $url["scheme"] = ($url["scheme"] === "rws" ? "ws" : "wss"); 44 | unset($url["loginusername"]); 45 | unset($url["login"]); 46 | 47 | $url2 = "http://" . $url["host"]; 48 | 49 | $url = HTTP::CondenseURL($url); 50 | 51 | return $this->rws->Connect($url, $url2, $options); 52 | } 53 | 54 | // Overrides the default behavior to start a server on a given host and port. 55 | // $host expected URL format: rws://serverapikey@host/webroutepath 56 | // Where 'serverapikey' is the Remoted API Server server API key and 'webroutepath' is the unique path to connect under. 57 | public function Start($host, $port, $sslopts = false) 58 | { 59 | $this->Stop(); 60 | 61 | if (!class_exists("WebSocket", false)) require_once str_replace("\\", "/", dirname(__FILE__)) . "/websocket.php"; 62 | if (!class_exists("WebRoute", false)) require_once str_replace("\\", "/", dirname(__FILE__)) . "/webroute.php"; 63 | 64 | $this->rws = new WebSocket(); 65 | $this->rhost = HTTP::ExtractURL($host); 66 | 67 | $this->rwr = new WebRoute(); 68 | 69 | return $this->ReconnectRemotedAPIWebSocket(); 70 | } 71 | 72 | public function Stop() 73 | { 74 | parent::Stop(); 75 | 76 | if ($this->rws !== false) $this->rws->Disconnect(); 77 | 78 | foreach ($this->rwrclients as $id => $client) 79 | { 80 | if ($client["state"]->webstate !== false && $client["state"]->webstate["httpstate"] !== false) 81 | { 82 | HTTP::ForceClose($client["state"]->webstate["httpstate"]); 83 | } 84 | } 85 | 86 | $this->rwrclients = array(); 87 | } 88 | 89 | // Adds the WebSocket server stream and WebRoute clients. 90 | public function UpdateStreamsAndTimeout($prefix, &$timeout, &$readfps, &$writefps) 91 | { 92 | if ($this->rws !== false) 93 | { 94 | if ($this->rws->GetStream() === false) $this->ReconnectRemotedAPIWebSocket(); 95 | 96 | if ($this->rws->GetStream() === false) $timeout = ($timeout === false || $timeout > 1 ? 1 : 0); 97 | else 98 | { 99 | $readfps[$prefix . "rws_http_s"] = $this->rws->GetStream(); 100 | 101 | if ($this->rws->NeedsWrite()) $writefps[$prefix . "rws_http_s"] = $this->rws->GetStream(); 102 | } 103 | } 104 | 105 | foreach ($this->rwrclients as $id => $client) 106 | { 107 | if ($client["state"]->webstate !== false && $client["state"]->webstate["httpstate"] !== false) 108 | { 109 | if (HTTP::WantRead($client["state"]->webstate["httpstate"])) $readfps[$prefix . "rws_http_c_" . $id] = $client["state"]->webstate["httpstate"]["fp"]; 110 | if (HTTP::WantWrite($client["state"]->webstate["httpstate"])) $writefps[$prefix . "rws_http_c_" . $id] = $client["state"]->webstate["httpstate"]["fp"]; 111 | } 112 | } 113 | 114 | parent::UpdateStreamsAndTimeout($prefix, $timeout, $readfps, $writefps); 115 | } 116 | 117 | protected function HandleNewConnections(&$readfps, &$writefps) 118 | { 119 | $result = $this->rws->ProcessQueuesAndTimeoutState(isset($readfps["rws_http_s"]), isset($writefps["rws_http_s"])); 120 | if ($result["success"]) 121 | { 122 | // Initiate a new async WebRoute for each incoming connection request. 123 | $result = $this->rws->Read(); 124 | while ($result["success"] && $result["data"] !== false) 125 | { 126 | $data = json_decode($result["data"]["payload"], true); 127 | 128 | if (isset($data["ipaddr"]) && isset($data["id"]) && isset($data["timeout"])) 129 | { 130 | $url = $this->rhost; 131 | 132 | $options = array( 133 | "async" => true, 134 | "headers" => array() 135 | ); 136 | 137 | $options["headers"]["X-Remoted-APIKey"] = $url["loginusername"]; 138 | 139 | $url["scheme"] = ($url["scheme"] === "rws" ? "wr" : "wrs"); 140 | unset($url["loginusername"]); 141 | unset($url["login"]); 142 | 143 | $data["url"] = HTTP::CondenseURL($url); 144 | $data["retries"] = 3; 145 | $data["options"] = $options; 146 | 147 | // Due to the async setting, this will only initiate the connection. No data is actually sent/received at this point. 148 | $result = $this->rwr->Connect($data["url"], $data["id"], $data["timeout"], $data["options"]); 149 | if ($result["success"]) 150 | { 151 | $result["data"] = $data; 152 | 153 | $this->rwrclients[$this->rwrnextclientid] = $result; 154 | 155 | $this->rwrnextclientid++; 156 | } 157 | } 158 | 159 | $result = $this->rws->Read(); 160 | } 161 | } 162 | 163 | // WebSocket was disconnected due to a socket error. 164 | if (!$result["success"]) $this->rws->Disconnect(); 165 | 166 | unset($readfps["rws_http_s"]); 167 | unset($writefps["rws_http_s"]); 168 | 169 | // Handle WebRoute clients. 170 | foreach ($this->rwrclients as $id => $client) 171 | { 172 | $result = $this->rwr->ProcessState($client["state"]); 173 | if ($result["success"]) 174 | { 175 | // WebRoute successfully established. Convert it into a WebServer client. 176 | // At this point, the underlying WebServer class takes over. 177 | $client2 = $this->InitNewClient(); 178 | $client2->fp = $result["fp"]; 179 | $client2->ipaddr = $client["data"]["ipaddr"]; 180 | 181 | // Remove the WebRoute client state. 182 | unset($this->rwrclients[$id]); 183 | } 184 | else if ($result["errorcode"] !== "no_data") 185 | { 186 | // Retry a few times in case of a flaky connection. 187 | $data = $client["data"]; 188 | if ($data["retries"] > 0) 189 | { 190 | $data["retries"]--; 191 | 192 | $result = $this->rwr->Connect($data["url"], $data["id"], $data["timeout"], $data["options"]); 193 | if ($result["success"]) 194 | { 195 | $result["data"] = $data; 196 | 197 | $this->rwrclients[$id] = $result; 198 | } 199 | } 200 | else 201 | { 202 | // Client connection failed. 203 | unset($this->rwrclients[$id]); 204 | } 205 | } 206 | } 207 | } 208 | } 209 | ?> -------------------------------------------------------------------------------- /support/multi_async_helper.php: -------------------------------------------------------------------------------- 1 | objs = array(); 12 | $this->queuedobjs = array(); 13 | $this->limit = false; 14 | } 15 | 16 | public function SetConcurrencyLimit($limit) 17 | { 18 | $this->limit = $limit; 19 | } 20 | 21 | public function Set($key, $obj, $callback) 22 | { 23 | if (is_callable($callback)) 24 | { 25 | $this->queuedobjs[$key] = array( 26 | "obj" => $obj, 27 | "callback" => $callback 28 | ); 29 | 30 | unset($this->objs[$key]); 31 | } 32 | } 33 | 34 | public function NumObjects() 35 | { 36 | return count($this->queuedobjs) + count($this->objs); 37 | } 38 | 39 | public function GetObject($key) 40 | { 41 | if (isset($this->queuedobjs[$key])) $result = $this->queuedobjs[$key]["obj"]; 42 | else if (isset($this->objs[$key])) $result = $this->objs[$key]["obj"]; 43 | else $result = false; 44 | 45 | return $result; 46 | } 47 | 48 | // To be able to change a callback on the fly. 49 | public function SetCallback($key, $callback) 50 | { 51 | if (is_callable($callback)) 52 | { 53 | if (isset($this->queuedobjs[$key])) $this->queuedobjs[$key]["callback"] = $callback; 54 | else if (isset($this->objs[$key])) $this->objs[$key]["callback"] = $callback; 55 | } 56 | } 57 | 58 | private function InternalDetach($key, $cleanup) 59 | { 60 | if (isset($this->queuedobjs[$key])) 61 | { 62 | call_user_func_array($this->queuedobjs[$key]["callback"], array("cleanup", &$cleanup, $key, &$this->queuedobjs[$key]["obj"])); 63 | $result = $this->queuedobjs[$key]["obj"]; 64 | unset($this->queuedobjs[$key]); 65 | } 66 | else if (isset($this->objs[$key])) 67 | { 68 | call_user_func_array($this->objs[$key]["callback"], array("cleanup", &$cleanup, $key, &$this->objs[$key]["obj"])); 69 | $result = $this->objs[$key]["obj"]; 70 | unset($this->objs[$key]); 71 | } 72 | else 73 | { 74 | $result = false; 75 | } 76 | 77 | return $result; 78 | } 79 | 80 | public function Detach($key) 81 | { 82 | return $this->InternalDetach($key, false); 83 | } 84 | 85 | public function Remove($key) 86 | { 87 | return $this->InternalDetach($key, true); 88 | } 89 | 90 | // A few default functions for direct file/socket handles. 91 | public static function ReadOnly($mode, &$data, $key, $fp) 92 | { 93 | switch ($mode) 94 | { 95 | case "init": 96 | case "update": 97 | { 98 | // Move to/Keep in the live queue. 99 | if (is_resource($fp)) $data = true; 100 | 101 | break; 102 | } 103 | case "read": 104 | case "write": 105 | case "writefps": 106 | { 107 | break; 108 | } 109 | case "readfps": 110 | { 111 | $data[$key] = $fp; 112 | 113 | break; 114 | } 115 | case "cleanup": 116 | { 117 | if ($data === true) @fclose($fp); 118 | 119 | break; 120 | } 121 | } 122 | } 123 | 124 | public static function WriteOnly($mode, &$data, $key, $fp) 125 | { 126 | switch ($mode) 127 | { 128 | case "init": 129 | case "update": 130 | { 131 | // Move to/Keep in the live queue. 132 | if (is_resource($fp)) $data = true; 133 | 134 | break; 135 | } 136 | case "read": 137 | case "readfps": 138 | case "write": 139 | { 140 | break; 141 | } 142 | case "writefps": 143 | { 144 | $data[$key] = $fp; 145 | 146 | break; 147 | } 148 | case "cleanup": 149 | { 150 | if ($data === true) @fclose($fp); 151 | 152 | break; 153 | } 154 | } 155 | } 156 | 157 | public static function ReadAndWrite($mode, &$data, $key, $fp) 158 | { 159 | switch ($mode) 160 | { 161 | case "init": 162 | case "update": 163 | { 164 | // Move to/Keep in the live queue. 165 | if (is_resource($fp)) $data = true; 166 | 167 | break; 168 | } 169 | case "read": 170 | case "write": 171 | { 172 | break; 173 | } 174 | case "readfps": 175 | case "writefps": 176 | { 177 | $data[$key] = $fp; 178 | 179 | break; 180 | } 181 | case "cleanup": 182 | { 183 | if ($data === true) @fclose($fp); 184 | 185 | break; 186 | } 187 | } 188 | } 189 | 190 | public function Wait($timeout = false) 191 | { 192 | // Move queued objects to live. 193 | $result2 = array("success" => true, "read" => array(), "write" => array(), "removed" => array(), "new" => array()); 194 | while (count($this->queuedobjs) && ($this->limit === false || count($this->objs) < $this->limit)) 195 | { 196 | $info = reset($this->queuedobjs); 197 | $key = key($this->queuedobjs); 198 | unset($this->queuedobjs[$key]); 199 | 200 | $result2["new"][$key] = $key; 201 | 202 | $keep = false; 203 | call_user_func_array($info["callback"], array("init", &$keep, $key, &$info["obj"])); 204 | 205 | $this->objs[$key] = $info; 206 | 207 | if (!$keep) $result2["removed"][$key] = $this->Remove($key); 208 | } 209 | 210 | // Walk the objects looking for read and write handles. 211 | $readfps = array(); 212 | $writefps = array(); 213 | $exceptfps = NULL; 214 | foreach ($this->objs as $key => &$info) 215 | { 216 | $keep = false; 217 | call_user_func_array($info["callback"], array("update", &$keep, $key, &$info["obj"])); 218 | 219 | if (!$keep) $result2["removed"][$key] = $this->Remove($key); 220 | else 221 | { 222 | call_user_func_array($info["callback"], array("readfps", &$readfps, $key, &$info["obj"])); 223 | call_user_func_array($info["callback"], array("writefps", &$writefps, $key, &$info["obj"])); 224 | } 225 | } 226 | if (!count($readfps)) $readfps = NULL; 227 | if (!count($writefps)) $writefps = NULL; 228 | 229 | // Wait for something to happen. 230 | if (isset($readfps) || isset($writefps)) 231 | { 232 | if ($timeout === false) $timeout = NULL; 233 | $readfps2 = $readfps; 234 | $writefps2 = $writefps; 235 | $result = @stream_select($readfps, $writefps, $exceptfps, $timeout); 236 | if ($result === false) return array("success" => false, "error" => self::MAHTranslate("Wait() failed due to stream_select() failure. Most likely cause: Connection failure."), "errorcode" => "stream_select_failed"); 237 | else if ($result > 0) 238 | { 239 | if (isset($readfps)) 240 | { 241 | $readfps3 = array(); 242 | foreach ($readfps as $key => $fp) 243 | { 244 | if (!isset($readfps2[$key]) || $readfps2[$key] !== $fp) 245 | { 246 | foreach ($readfps2 as $key2 => $fp2) 247 | { 248 | if ($fp === $fp2) $key = $key2; 249 | } 250 | } 251 | 252 | if (isset($this->objs[$key])) 253 | { 254 | call_user_func_array($this->objs[$key]["callback"], array("read", &$fp, $key, &$this->objs[$key]["obj"])); 255 | 256 | $readfps3[$key] = $fp; 257 | } 258 | } 259 | 260 | $result2["read"] = $readfps3; 261 | } 262 | 263 | if (isset($writefps)) 264 | { 265 | $writefps3 = array(); 266 | foreach ($writefps as $key => $fp) 267 | { 268 | if (!isset($writefps2[$key]) || $writefps2[$key] !== $fp) 269 | { 270 | foreach ($writefps2 as $key2 => $fp2) 271 | { 272 | if ($fp === $fp2) $key = $key2; 273 | } 274 | } 275 | 276 | if (isset($this->objs[$key])) 277 | { 278 | call_user_func_array($this->objs[$key]["callback"], array("write", &$fp, $key, &$this->objs[$key]["obj"])); 279 | 280 | $readfps3[$key] = $fp; 281 | } 282 | } 283 | 284 | $result2["write"] = $writefps3; 285 | } 286 | } 287 | } 288 | 289 | $result2["numleft"] = count($this->queuedobjs) + count($this->objs); 290 | 291 | return $result2; 292 | } 293 | 294 | public static function MAHTranslate() 295 | { 296 | $args = func_get_args(); 297 | if (!count($args)) return ""; 298 | 299 | return call_user_func_array((defined("CS_TRANSLATE_FUNC") && function_exists(CS_TRANSLATE_FUNC) ? CS_TRANSLATE_FUNC : "sprintf"), $args); 300 | } 301 | } 302 | ?> -------------------------------------------------------------------------------- /support/webroute.php: -------------------------------------------------------------------------------- 1 | csprng = false; 17 | } 18 | 19 | public static function ProcessState($state) 20 | { 21 | while ($state->state !== "done") 22 | { 23 | switch ($state->state) 24 | { 25 | case "initialize": 26 | { 27 | $result = $state->web->Process($state->url, $state->options); 28 | if (!$result["success"]) return $result; 29 | 30 | if (isset($state->options["async"]) && $state->options["async"]) 31 | { 32 | $state->async = true; 33 | $state->webstate = $result["state"]; 34 | 35 | $state->state = "process_async"; 36 | } 37 | else 38 | { 39 | $state->result = $result; 40 | 41 | $state->state = "post_retrieval"; 42 | } 43 | 44 | break; 45 | } 46 | case "process_async": 47 | { 48 | // Run a cycle of the WebBrowser state processor. 49 | $result = $state->web->ProcessState($state->webstate); 50 | if (!$result["success"]) return $result; 51 | 52 | $state->webstate = false; 53 | $state->result = $result; 54 | 55 | $state->state = "post_retrieval"; 56 | 57 | break; 58 | } 59 | case "post_retrieval": 60 | { 61 | if ($state->result["response"]["code"] != 101) return array("success" => false, "error" => self::WRTranslate("WebRoute::Connect() failed to connect to the WebRoute. Server returned: %s %s", $result["response"]["code"], $result["response"]["meaning"]), "errorcode" => "incorrect_server_response"); 62 | if (!isset($state->result["headers"]["Sec-Webroute-Accept"])) return array("success" => false, "error" => self::WRTranslate("Server failed to include a 'Sec-WebRoute-Accept' header in its response to the request."), "errorcode" => "missing_server_webroute_accept_header"); 63 | 64 | // Verify the Sec-WebRoute-Accept response. 65 | if ($state->result["headers"]["Sec-Webroute-Accept"][0] !== base64_encode(sha1($state->options["headers"]["WebRoute-ID"] . self::ID_GUID, true))) return array("success" => false, "error" => self::WRTranslate("The server's 'Sec-WebRoute-Accept' header is invalid."), "errorcode" => "invalid_server_webroute_accept_header"); 66 | 67 | $state->state = "done"; 68 | 69 | break; 70 | } 71 | } 72 | } 73 | 74 | return $state->result; 75 | } 76 | 77 | public function Connect($url, $id = false, $timeout = false, $options = array(), $web = false) 78 | { 79 | // Generate client ID. 80 | if ($id === false) 81 | { 82 | if (!class_exists("CSPRNG", false)) require_once str_replace("\\", "/", dirname(__FILE__)) . "/random.php"; 83 | 84 | if ($this->csprng === false) $this->csprng = new CSPRNG(); 85 | 86 | $id = $this->csprng->GenerateString(64); 87 | } 88 | 89 | if (!class_exists("WebBrowser", false)) require_once str_replace("\\", "/", dirname(__FILE__)) . "/web_browser.php"; 90 | 91 | // Use WebBrowser to initiate the connection. 92 | if ($web === false) $web = new WebBrowser(); 93 | 94 | // Transform URL. 95 | $url2 = HTTP::ExtractURL($url); 96 | if ($url2["scheme"] != "wr" && $url2["scheme"] != "wrs") return array("success" => false, "error" => self::WRTranslate("WebRoute::Connect() only supports the 'wr' and 'wrs' protocols."), "errorcode" => "protocol_check"); 97 | $url2["scheme"] = str_replace("wr", "http", $url2["scheme"]); 98 | $url2 = HTTP::CondenseURL($url2); 99 | 100 | // Generate correct request headers. 101 | if (!isset($options["headers"])) $options["headers"] = array(); 102 | $options["headers"]["Connection"] = "keep-alive, Upgrade"; 103 | $options["headers"]["Pragma"] = "no-cache"; 104 | $options["headers"]["WebRoute-Version"] = "1"; 105 | $options["headers"]["WebRoute-ID"] = $id; 106 | if ($timeout !== false && is_int($timeout)) $options["headers"]["WebRoute-Timeout"] = (string)(int)$timeout; 107 | $options["headers"]["Upgrade"] = "webroute"; 108 | 109 | // Initialize the process state object. 110 | $state = new stdClass(); 111 | $state->async = false; 112 | $state->state = "initialize"; 113 | $state->web = $web; 114 | $state->url = $url2; 115 | $state->options = $options; 116 | $state->webstate = false; 117 | $state->result = false; 118 | 119 | // Run at least one state cycle to finish initializing the state object. 120 | $result = $this->ProcessState($state); 121 | 122 | // Return the state for async calls. Caller must call ProcessState(). 123 | if ($state->async) return array("success" => true, "id" => $id, "state" => $state); 124 | 125 | $result["id"] = $id; 126 | 127 | return $result; 128 | } 129 | 130 | // Implements the correct MultiAsyncHelper responses for WebRoute instances. 131 | public function ConnectAsync__Handler($mode, &$data, $key, $info) 132 | { 133 | switch ($mode) 134 | { 135 | case "init": 136 | { 137 | if ($info->init) $data = $info->keep; 138 | else 139 | { 140 | $info->result = $this->Connect($info->url, $info->id, $info->timeout, $info->options, $info->web); 141 | if (!$info->result["success"]) 142 | { 143 | $info->keep = false; 144 | 145 | if (is_callable($info->callback)) call_user_func_array($info->callback, array($key, $info->url, $info->result)); 146 | } 147 | else 148 | { 149 | $info->id = $info->result["id"]; 150 | $info->state = $info->result["state"]; 151 | 152 | // Move to the live queue. 153 | $data = true; 154 | } 155 | } 156 | 157 | break; 158 | } 159 | case "update": 160 | case "read": 161 | case "write": 162 | { 163 | if ($info->keep) 164 | { 165 | $info->result = $this->ProcessState($info->state); 166 | if ($info->result["success"] || $info->result["errorcode"] !== "no_data") $info->keep = false; 167 | 168 | if (is_callable($info->callback)) call_user_func_array($info->callback, array($key, $info->url, $info->result)); 169 | 170 | if ($mode === "update") $data = $info->keep; 171 | } 172 | 173 | break; 174 | } 175 | case "readfps": 176 | { 177 | if ($info->state->webstate["httpstate"] !== false && HTTP::WantRead($info->state->webstate["httpstate"])) $data[$key] = $info->state->webstate["httpstate"]["fp"]; 178 | 179 | break; 180 | } 181 | case "writefps": 182 | { 183 | if ($info->state->webstate["httpstate"] !== false && HTTP::WantWrite($info->state->webstate["httpstate"])) $data[$key] = $info->state->webstate["httpstate"]["fp"]; 184 | 185 | break; 186 | } 187 | case "cleanup": 188 | { 189 | // When true, caller is removing. Otherwise, detaching from the queue. 190 | if ($data === true) 191 | { 192 | if (isset($info->state)) 193 | { 194 | if ($info->state->webstate["httpstate"] !== false) HTTP::ForceClose($info->state->webstate["httpstate"]); 195 | 196 | unset($info->state); 197 | } 198 | 199 | $info->keep = false; 200 | } 201 | 202 | break; 203 | } 204 | } 205 | } 206 | 207 | public function ConnectAsync($helper, $key, $callback, $url, $id = false, $timeout = false, $options = array(), $web = false) 208 | { 209 | $options["async"] = true; 210 | 211 | $info = new stdClass(); 212 | $info->init = false; 213 | $info->keep = true; 214 | $info->callback = $callback; 215 | $info->url = $url; 216 | $info->id = $id; 217 | $info->timeout = $timeout; 218 | $info->options = $options; 219 | $info->web = $web; 220 | $info->result = false; 221 | 222 | $helper->Set($key, $info, array($this, "ConnectAsync__Handler")); 223 | 224 | return array("success" => true); 225 | } 226 | 227 | public static function WRTranslate() 228 | { 229 | $args = func_get_args(); 230 | if (!count($args)) return ""; 231 | 232 | return call_user_func_array((defined("CS_TRANSLATE_FUNC") && function_exists(CS_TRANSLATE_FUNC) ? CS_TRANSLATE_FUNC : "sprintf"), $args); 233 | } 234 | } 235 | ?> -------------------------------------------------------------------------------- /sdks/php/webroute.php: -------------------------------------------------------------------------------- 1 | csprng = false; 17 | } 18 | 19 | public static function ProcessState($state) 20 | { 21 | while ($state->state !== "done") 22 | { 23 | switch ($state->state) 24 | { 25 | case "initialize": 26 | { 27 | $result = $state->web->Process($state->url, $state->options); 28 | if (!$result["success"]) return $result; 29 | 30 | if (isset($state->options["async"]) && $state->options["async"]) 31 | { 32 | $state->async = true; 33 | $state->webstate = $result["state"]; 34 | 35 | $state->state = "process_async"; 36 | } 37 | else 38 | { 39 | $state->result = $result; 40 | 41 | $state->state = "post_retrieval"; 42 | } 43 | 44 | break; 45 | } 46 | case "process_async": 47 | { 48 | // Run a cycle of the WebBrowser state processor. 49 | $result = $state->web->ProcessState($state->webstate); 50 | if (!$result["success"]) return $result; 51 | 52 | $state->webstate = false; 53 | $state->result = $result; 54 | 55 | $state->state = "post_retrieval"; 56 | 57 | break; 58 | } 59 | case "post_retrieval": 60 | { 61 | if ($state->result["response"]["code"] != 101) return array("success" => false, "error" => self::WRTranslate("WebRoute::Connect() failed to connect to the WebRoute. Server returned: %s %s", $result["response"]["code"], $result["response"]["meaning"]), "errorcode" => "incorrect_server_response"); 62 | if (!isset($state->result["headers"]["Sec-Webroute-Accept"])) return array("success" => false, "error" => self::WRTranslate("Server failed to include a 'Sec-WebRoute-Accept' header in its response to the request."), "errorcode" => "missing_server_webroute_accept_header"); 63 | 64 | // Verify the Sec-WebRoute-Accept response. 65 | if ($state->result["headers"]["Sec-Webroute-Accept"][0] !== base64_encode(sha1($state->options["headers"]["WebRoute-ID"] . self::ID_GUID, true))) return array("success" => false, "error" => self::WRTranslate("The server's 'Sec-WebRoute-Accept' header is invalid."), "errorcode" => "invalid_server_webroute_accept_header"); 66 | 67 | $state->state = "done"; 68 | 69 | break; 70 | } 71 | } 72 | } 73 | 74 | return $state->result; 75 | } 76 | 77 | public function Connect($url, $id = false, $timeout = false, $options = array(), $web = false) 78 | { 79 | // Generate client ID. 80 | if ($id === false) 81 | { 82 | if (!class_exists("CSPRNG", false)) require_once str_replace("\\", "/", dirname(__FILE__)) . "/random.php"; 83 | 84 | if ($this->csprng === false) $this->csprng = new CSPRNG(); 85 | 86 | $id = $this->csprng->GenerateString(64); 87 | } 88 | 89 | if (!class_exists("WebBrowser", false)) require_once str_replace("\\", "/", dirname(__FILE__)) . "/web_browser.php"; 90 | 91 | // Use WebBrowser to initiate the connection. 92 | if ($web === false) $web = new WebBrowser(); 93 | 94 | // Transform URL. 95 | $url2 = HTTP::ExtractURL($url); 96 | if ($url2["scheme"] != "wr" && $url2["scheme"] != "wrs") return array("success" => false, "error" => self::WRTranslate("WebRoute::Connect() only supports the 'wr' and 'wrs' protocols."), "errorcode" => "protocol_check"); 97 | $url2["scheme"] = str_replace("wr", "http", $url2["scheme"]); 98 | $url2 = HTTP::CondenseURL($url2); 99 | 100 | // Generate correct request headers. 101 | if (!isset($options["headers"])) $options["headers"] = array(); 102 | $options["headers"]["Connection"] = "keep-alive, Upgrade"; 103 | $options["headers"]["Pragma"] = "no-cache"; 104 | $options["headers"]["WebRoute-Version"] = "1"; 105 | $options["headers"]["WebRoute-ID"] = $id; 106 | if ($timeout !== false && is_int($timeout)) $options["headers"]["WebRoute-Timeout"] = (string)(int)$timeout; 107 | $options["headers"]["Upgrade"] = "webroute"; 108 | 109 | // Initialize the process state object. 110 | $state = new stdClass(); 111 | $state->async = false; 112 | $state->state = "initialize"; 113 | $state->web = $web; 114 | $state->url = $url2; 115 | $state->options = $options; 116 | $state->webstate = false; 117 | $state->result = false; 118 | 119 | // Run at least one state cycle to finish initializing the state object. 120 | $result = $this->ProcessState($state); 121 | 122 | // Return the state for async calls. Caller must call ProcessState(). 123 | if ($state->async) return array("success" => true, "id" => $id, "state" => $state); 124 | 125 | $result["id"] = $id; 126 | 127 | return $result; 128 | } 129 | 130 | // Implements the correct MultiAsyncHelper responses for WebRoute instances. 131 | public function ConnectAsync__Handler($mode, &$data, $key, $info) 132 | { 133 | switch ($mode) 134 | { 135 | case "init": 136 | { 137 | if ($info->init) $data = $info->keep; 138 | else 139 | { 140 | $info->result = $this->Connect($info->url, $info->id, $info->timeout, $info->options, $info->web); 141 | if (!$info->result["success"]) 142 | { 143 | $info->keep = false; 144 | 145 | if (is_callable($info->callback)) call_user_func_array($info->callback, array($key, $info->url, $info->result)); 146 | } 147 | else 148 | { 149 | $info->id = $info->result["id"]; 150 | $info->state = $info->result["state"]; 151 | 152 | // Move to the live queue. 153 | $data = true; 154 | } 155 | } 156 | 157 | break; 158 | } 159 | case "update": 160 | case "read": 161 | case "write": 162 | { 163 | if ($info->keep) 164 | { 165 | $info->result = $this->ProcessState($info->state); 166 | if ($info->result["success"] || $info->result["errorcode"] !== "no_data") $info->keep = false; 167 | 168 | if (is_callable($info->callback)) call_user_func_array($info->callback, array($key, $info->url, $info->result)); 169 | 170 | if ($mode === "update") $data = $info->keep; 171 | } 172 | 173 | break; 174 | } 175 | case "readfps": 176 | { 177 | if ($info->state->webstate["httpstate"] !== false && HTTP::WantRead($info->state->webstate["httpstate"])) $data[$key] = $info->state->webstate["httpstate"]["fp"]; 178 | 179 | break; 180 | } 181 | case "writefps": 182 | { 183 | if ($info->state->webstate["httpstate"] !== false && HTTP::WantWrite($info->state->webstate["httpstate"])) $data[$key] = $info->state->webstate["httpstate"]["fp"]; 184 | 185 | break; 186 | } 187 | case "cleanup": 188 | { 189 | // When true, caller is removing. Otherwise, detaching from the queue. 190 | if ($data === true) 191 | { 192 | if (isset($info->state)) 193 | { 194 | if ($info->state->webstate["httpstate"] !== false) HTTP::ForceClose($info->state->webstate["httpstate"]); 195 | 196 | unset($info->state); 197 | } 198 | 199 | $info->keep = false; 200 | } 201 | 202 | break; 203 | } 204 | } 205 | } 206 | 207 | public function ConnectAsync($helper, $key, $callback, $url, $id = false, $timeout = false, $options = array(), $web = false) 208 | { 209 | $options["async"] = true; 210 | 211 | $info = new stdClass(); 212 | $info->init = false; 213 | $info->keep = true; 214 | $info->callback = $callback; 215 | $info->url = $url; 216 | $info->id = $id; 217 | $info->timeout = $timeout; 218 | $info->options = $options; 219 | $info->web = $web; 220 | $info->result = false; 221 | 222 | $helper->Set($key, $info, array($this, "ConnectAsync__Handler")); 223 | 224 | return array("success" => true); 225 | } 226 | 227 | public static function WRTranslate() 228 | { 229 | $args = func_get_args(); 230 | if (!count($args)) return ""; 231 | 232 | return call_user_func_array((defined("CS_TRANSLATE_FUNC") && function_exists(CS_TRANSLATE_FUNC) ? CS_TRANSLATE_FUNC : "sprintf"), $args); 233 | } 234 | } 235 | ?> -------------------------------------------------------------------------------- /manage.php: -------------------------------------------------------------------------------- 1 | array( 22 | "s" => "suppressoutput", 23 | "?" => "help" 24 | ), 25 | "rules" => array( 26 | "suppressoutput" => array("arg" => false), 27 | "help" => array("arg" => false) 28 | ) 29 | ); 30 | $args = CLI::ParseCommandLine($options); 31 | 32 | if (isset($args["opts"]["help"])) 33 | { 34 | echo "Cloud Storage Server management command-line tool\n"; 35 | echo "Purpose: Manage users in the users database.\n"; 36 | echo "\n"; 37 | echo "This tool is question/answer enabled. Just running it will provide a guided interface. It can also be run entirely from the command-line if you know all the answers.\n"; 38 | echo "\n"; 39 | echo "Syntax: " . $args["file"] . " [options] [cmd [cmdoptions]]\n"; 40 | echo "Options:\n"; 41 | echo "\t-s Suppress most output. Useful for capturing JSON output.\n"; 42 | echo "\n"; 43 | echo "Examples:\n"; 44 | echo "\tphp " . $args["file"] . "\n"; 45 | echo "\tphp " . $args["file"] . " create username=test\n"; 46 | echo "\tphp " . $args["file"] . " -s list\n"; 47 | echo "\tphp " . $args["file"] . " -s get-info test\n"; 48 | 49 | exit(); 50 | } 51 | 52 | $suppressoutput = (isset($args["opts"]["suppressoutput"]) && $args["opts"]["suppressoutput"]); 53 | 54 | $config = CSS_LoadConfig(); 55 | 56 | if (!isset($config["quota"])) CSS_DisplayError("Configuration is incomplete or missing. Run 'install.php' first."); 57 | 58 | require_once $rootpath . "/support/db.php"; 59 | require_once $rootpath . "/support/db_sqlite.php"; 60 | 61 | $db = new CSDB_sqlite(); 62 | 63 | try 64 | { 65 | $db->Connect("sqlite:" . $rootpath . "/data/main.db"); 66 | } 67 | catch (Exception $e) 68 | { 69 | CSS_DisplayError("Unable to connect to SQLite database. " . $e->getMessage()); 70 | } 71 | 72 | if (!$db->TableExists("users")) CSS_DisplayError("Database table is missing. Run 'install.php' first."); 73 | 74 | require_once $rootpath . "/support/event_manager.php"; 75 | 76 | $em = new EventManager(); 77 | 78 | $userhelper = new CSS_UserHelper(); 79 | $userhelper->Init($config, $db, $em); 80 | 81 | // Load all server extensions. 82 | $serverexts = CSS_LoadServerExtensions(); 83 | 84 | // Let each server extension register event handler callbacks (e.g. "CSS_UserHelper::DeleteUser"). 85 | // For most events, it is best to wait until later to do anything about changes (i.e. lazy updates). 86 | foreach ($serverexts as $serverext) $serverext->RegisterHandlers($em); 87 | 88 | // Get the command. 89 | $cmds = array("list" => "List all users", "create" => "Add a new user", "update" => "Change a user's transfer limit and quota.", "add-ext" => "Add user access to an API extension", "remove-ext" => "Remove user access to an API extension", "get-info" => "Get detailed information about a user", "delete" => "Delete a user"); 90 | 91 | $cmd = CLI::GetLimitedUserInputWithArgs($args, false, "Command", false, "Available commands:", $cmds, true, $suppressoutput); 92 | 93 | function DisplayResult($result) 94 | { 95 | echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"; 96 | 97 | exit(); 98 | } 99 | 100 | function FixUserRow($row) 101 | { 102 | $row->apikey .= "-" . $row->id; 103 | $row->totalbytes = (double)$row->totalbytes; 104 | $row->quota = (double)$row->quota; 105 | $row->transferstart = (int)$row->transferstart; 106 | $row->transferbytes = (double)$row->transferbytes; 107 | $row->transferlimit = (double)$row->transferlimit; 108 | $row->lastupdated = (int)$row->lastupdated; 109 | $row->quotaleft = CSS_GetQuotaLeft($row); 110 | $row->transferleft = CSS_GetTransferLeft($row); 111 | } 112 | 113 | function GetUserList() 114 | { 115 | global $db; 116 | 117 | $result = array("success" => true, "users" => array()); 118 | 119 | $result2 = $db->Query("SELECT", array( 120 | "*", 121 | "FROM" => "?", 122 | ), "users"); 123 | 124 | while ($row = $result2->NextRow()) 125 | { 126 | $row->serverexts = json_decode($row->serverexts, true); 127 | 128 | FixUserRow($row); 129 | 130 | $result["users"][$row->username] = (array)$row; 131 | } 132 | 133 | ksort($result["users"]); 134 | 135 | return $result; 136 | } 137 | 138 | function GetUser() 139 | { 140 | global $suppressoutput, $args, $userhelper; 141 | 142 | if ($suppressoutput || CLI::CanGetUserInputWithArgs($args, "username")) 143 | { 144 | $username = CLI::GetUserInputWithArgs($args, "username", "Username", false, "", $suppressoutput); 145 | 146 | $result = $userhelper->GetUserByUsername($username); 147 | if (!$result["success"]) DisplayResult($result); 148 | 149 | FixUserRow($result["info"]); 150 | 151 | $result["info"] = (array)$result["info"]; 152 | } 153 | else 154 | { 155 | $result = GetUserList(); 156 | if (!$result["success"]) DisplayResult($result); 157 | 158 | $users = array(); 159 | foreach ($result["users"] as $username => $user) 160 | { 161 | $options = array(); 162 | if (!count($user["serverexts"])) $options[] = "No extensions enabled"; 163 | else $options[] = (count($user["serverexts"]) == 1 ? "1 extension" : count($user["serverexts"]) . " extensions") . " enabled (" . implode(", ", array_keys($user["serverexts"])) . ")"; 164 | 165 | if ($user["quotaleft"] > -1) $options[] = Str::ConvertBytesToUserStr($user["quotaleft"]) . " storage left"; 166 | if ($user["transferleft"] > -1) $options[] = Str::ConvertBytesToUserStr($user["transferleft"]) . " transfer left"; 167 | 168 | $users[$username] = implode(", ", $options); 169 | } 170 | if (!count($users)) CLI::DisplayError("No users have been created. Try creating your first user with the command: create"); 171 | $username = CLI::GetLimitedUserInputWithArgs($args, "username", "Username", false, "Available users:", $users, true, $suppressoutput); 172 | $result["info"] = $result["users"][$username]; 173 | unset($result["users"]); 174 | } 175 | 176 | return $result; 177 | } 178 | 179 | if ($cmd === "list") 180 | { 181 | DisplayResult(GetUserList()); 182 | } 183 | else if ($cmd === "create") 184 | { 185 | CLI::ReinitArgs($args, array("username", "basepath", "quota", "transferlimit")); 186 | 187 | // Get the username of the new user. 188 | do 189 | { 190 | $username = CLI::GetUserInputWithArgs($args, "username", "Username", false, "", $suppressoutput); 191 | 192 | $result = $userhelper->GetUserByUsername($username); 193 | if ($result["success"]) CLI::DisplayError("A user with the username '" . $username . "' already exists.", false, false); 194 | else if ($result["errorcode"] !== "no_user") CLI::DisplayError("An error occurred while attempting to check the database.", $result); 195 | else break; 196 | } while (1); 197 | 198 | $options = array(); 199 | $options["basepath"] = CLI::GetUserInputWithArgs($args, "basepath", "Base path", $config["basepath"], "", $suppressoutput); 200 | $options["quota"] = CSS_ConvertUserStrToBytes(CLI::GetUserInputWithArgs($args, "quota", "Quota", $config["quota"], "", $suppressoutput)); 201 | $options["transferlimit"] = CSS_ConvertUserStrToBytes(CLI::GetUserInputWithArgs($args, "transferlimit", "Transfer limit", $config["transferlimit"], "", $suppressoutput)); 202 | 203 | $result = $userhelper->CreateUser($username, $options); 204 | if (!$result["success"]) DisplayResult($result); 205 | 206 | FixUserRow($result["info"]); 207 | 208 | DisplayResult($result); 209 | } 210 | else if ($cmd === "update") 211 | { 212 | CLI::ReinitArgs($args, array("username", "quota", "transferlimit")); 213 | 214 | $result = GetUser(); 215 | $id = $result["info"]["id"]; 216 | 217 | $options = array(); 218 | $options["quota"] = CSS_ConvertUserStrToBytes(CLI::GetUserInputWithArgs($args, "quota", "Quota", Str::ConvertBytesToUserStr($result["info"]["quota"]), "", $suppressoutput)); 219 | $options["transferlimit"] = CSS_ConvertUserStrToBytes(CLI::GetUserInputWithArgs($args, "transferlimit", "Transfer limit", Str::ConvertBytesToUserStr($result["info"]["transferlimit"]), "", $suppressoutput)); 220 | 221 | $result = $userhelper->UpdateUser($id, $options); 222 | if (!$result["success"]) DisplayResult($result); 223 | 224 | $result = $userhelper->GetUserByID($id); 225 | if (!$result["success"]) DisplayResult($result); 226 | 227 | FixUserRow($result["info"]); 228 | 229 | DisplayResult($result); 230 | } 231 | else if ($cmd === "add-ext") 232 | { 233 | CLI::ReinitArgs($args, array("username", "extension")); 234 | 235 | $result = GetUser(); 236 | $id = $result["info"]["id"]; 237 | 238 | $extensions = array(); 239 | foreach ($serverexts as $name => $serverext) $extensions[$name] = "/" . $name; 240 | $extension = CLI::GetLimitedUserInputWithArgs($args, "extension", "Extension", false, "Available extensions:", $extensions, true, $suppressoutput); 241 | 242 | $result2 = $serverexts[$extension]->AddUserExtension((object)$result["info"]); 243 | if (!$result2["success"]) DisplayResult($result2); 244 | 245 | $options = array(); 246 | $options["serverexts"] = $result["info"]["serverexts"]; 247 | $options["serverexts"][$extension] = $result2["info"]; 248 | 249 | $result = $userhelper->UpdateUser($id, $options); 250 | if (!$result["success"]) DisplayResult($result); 251 | 252 | $result = $userhelper->GetUserByID($id); 253 | if (!$result["success"]) DisplayResult($result); 254 | 255 | FixUserRow($result["info"]); 256 | 257 | DisplayResult($result); 258 | } 259 | else if ($cmd === "remove-ext") 260 | { 261 | CLI::ReinitArgs($args, array("username", "extension")); 262 | 263 | $result = GetUser(); 264 | $id = $result["info"]["id"]; 265 | 266 | $extensions = array(); 267 | foreach ($result["info"]["serverexts"] as $name => $info) $extensions[$name] = "/" . $name . " - " . json_encode($info, JSON_UNESCAPED_SLASHES); 268 | if (!count($extensions)) CLI::DisplayError("No extensions have been enabled for the user."); 269 | $extension = CLI::GetLimitedUserInputWithArgs($args, "extension", "Extension", false, "Enabled extensions:", $extensions, true, $suppressoutput); 270 | 271 | $options = array(); 272 | $options["serverexts"] = $result["info"]["serverexts"]; 273 | unset($options["serverexts"][$extension]); 274 | 275 | $result = $userhelper->UpdateUser($id, $options); 276 | if (!$result["success"]) DisplayResult($result); 277 | 278 | $result = $userhelper->GetUserByID($id); 279 | if (!$result["success"]) DisplayResult($result); 280 | 281 | FixUserRow($result["info"]); 282 | 283 | DisplayResult($result); 284 | } 285 | else if ($cmd === "get-info") 286 | { 287 | CLI::ReinitArgs($args, array("username")); 288 | 289 | DisplayResult(GetUser()); 290 | } 291 | else if ($cmd === "delete") 292 | { 293 | CLI::ReinitArgs($args, array("username")); 294 | 295 | $result = GetUser(); 296 | $id = $result["info"]["id"]; 297 | 298 | $result = $userhelper->DeleteUser($id); 299 | 300 | DisplayResult($result); 301 | } 302 | ?> -------------------------------------------------------------------------------- /sdks/php/deflate_stream.php: -------------------------------------------------------------------------------- 1 | open = false; 13 | } 14 | 15 | public function __destruct() 16 | { 17 | $this->Finalize(); 18 | } 19 | 20 | public static function IsSupported() 21 | { 22 | if (!is_bool(self::$supported)) 23 | { 24 | self::$supported = function_exists("stream_filter_append") && function_exists("stream_filter_remove") && function_exists("gzcompress"); 25 | if (self::$supported) 26 | { 27 | $data = self::Compress("test"); 28 | if ($data === false || $data === "") self::$supported = false; 29 | else 30 | { 31 | $data = self::Uncompress($data); 32 | if ($data === false || $data !== "test") self::$supported = false; 33 | } 34 | } 35 | } 36 | 37 | return self::$supported; 38 | } 39 | 40 | public static function Compress($data, $compresslevel = -1, $options = array()) 41 | { 42 | $ds = new DeflateStream; 43 | if (!$ds->Init("wb", $compresslevel, $options)) return false; 44 | if (!$ds->Write($data)) return false; 45 | if (!$ds->Finalize()) return false; 46 | $data = $ds->Read(); 47 | 48 | return $data; 49 | } 50 | 51 | public static function Uncompress($data, $options = array("type" => "auto")) 52 | { 53 | $ds = new DeflateStream; 54 | if (!$ds->Init("rb", -1, $options)) return false; 55 | if (!$ds->Write($data)) return false; 56 | if (!$ds->Finalize()) return false; 57 | $data = $ds->Read(); 58 | 59 | return $data; 60 | } 61 | 62 | public function Init($mode, $compresslevel = -1, $options = array()) 63 | { 64 | if ($mode !== "rb" && $mode !== "wb") return false; 65 | if ($this->open) $this->Finalize(); 66 | 67 | $this->fp = fopen("php://memory", "w+b"); 68 | if ($this->fp === false) return false; 69 | $this->compress = ($mode == "wb"); 70 | if (!isset($options["type"])) $options["type"] = "rfc1951"; 71 | 72 | if ($options["type"] == "rfc1950") $options["type"] = "zlib"; 73 | else if ($options["type"] == "rfc1952") $options["type"] = "gzip"; 74 | 75 | if ($options["type"] != "zlib" && $options["type"] != "gzip" && ($this->compress || $options["type"] != "auto")) $options["type"] = "raw"; 76 | $this->options = $options; 77 | 78 | // Add the deflate filter. 79 | if ($this->compress) $this->filter = stream_filter_append($this->fp, "zlib.deflate", STREAM_FILTER_WRITE, $compresslevel); 80 | else $this->filter = stream_filter_append($this->fp, "zlib.inflate", STREAM_FILTER_READ); 81 | 82 | $this->open = true; 83 | $this->indata = ""; 84 | $this->outdata = ""; 85 | 86 | if ($this->compress) 87 | { 88 | if ($this->options["type"] == "zlib") 89 | { 90 | $this->outdata .= "\x78\x9C"; 91 | $this->options["a"] = 1; 92 | $this->options["b"] = 0; 93 | } 94 | else if ($this->options["type"] == "gzip") 95 | { 96 | if (!class_exists("CRC32Stream", false)) require_once str_replace("\\", "/", dirname(__FILE__)) . "/crc32_stream.php"; 97 | 98 | $this->options["crc32"] = new CRC32Stream(); 99 | $this->options["crc32"]->Init(); 100 | $this->options["bytes"] = 0; 101 | 102 | $this->outdata .= "\x1F\x8B\x08"; 103 | $flags = 0; 104 | if (isset($this->options["filename"])) $flags |= 0x08; 105 | if (isset($this->options["comment"])) $flags |= 0x10; 106 | $this->outdata .= chr($flags); 107 | $this->outdata .= "\x00\x00\x00\x00"; 108 | $this->outdata .= "\x00"; 109 | $this->outdata .= "\x03"; 110 | 111 | if (isset($this->options["filename"])) $this->outdata .= str_replace("\x00", " ", $this->options["filename"]) . "\x00"; 112 | if (isset($this->options["comment"])) $this->outdata .= str_replace("\x00", " ", $this->options["comment"]) . "\x00"; 113 | } 114 | } 115 | else 116 | { 117 | $this->options["header"] = false; 118 | } 119 | 120 | return true; 121 | } 122 | 123 | public function Read() 124 | { 125 | $result = $this->outdata; 126 | $this->outdata = ""; 127 | 128 | return $result; 129 | } 130 | 131 | public function Write($data) 132 | { 133 | if (!$this->open) return false; 134 | 135 | if ($this->compress) 136 | { 137 | if ($this->options["type"] == "zlib") 138 | { 139 | // Adler-32. 140 | $y = strlen($data); 141 | for ($x = 0; $x < $y; $x++) 142 | { 143 | $this->options["a"] = ($this->options["a"] + ord($data[$x])) % 65521; 144 | $this->options["b"] = ($this->options["b"] + $this->options["a"]) % 65521; 145 | } 146 | } 147 | else if ($this->options["type"] == "gzip") 148 | { 149 | $this->options["crc32"]->AddData($data); 150 | $this->options["bytes"] = $this->ADD32($this->options["bytes"], strlen($data)); 151 | } 152 | 153 | $this->indata .= $data; 154 | while (strlen($this->indata) >= 65536) 155 | { 156 | fwrite($this->fp, substr($this->indata, 0, 65536)); 157 | $this->indata = substr($this->indata, 65536); 158 | 159 | $this->ProcessOutput(); 160 | } 161 | } 162 | else 163 | { 164 | $this->indata .= $data; 165 | $this->ProcessInput(); 166 | } 167 | 168 | return true; 169 | } 170 | 171 | // Finalizes the stream. 172 | public function Finalize() 173 | { 174 | if (!$this->open) return false; 175 | 176 | if (!$this->compress) $this->ProcessInput(true); 177 | 178 | if (strlen($this->indata) > 0) 179 | { 180 | fwrite($this->fp, $this->indata); 181 | $this->indata = ""; 182 | } 183 | 184 | // Removing the filter pushes the last buffer into the stream. 185 | stream_filter_remove($this->filter); 186 | $this->filter = false; 187 | 188 | $this->ProcessOutput(); 189 | 190 | fclose($this->fp); 191 | 192 | if ($this->compress) 193 | { 194 | if ($this->options["type"] == "zlib") $this->outdata .= pack("N", $this->SHL32($this->options["b"], 16) | $this->options["a"]); 195 | else if ($this->options["type"] == "gzip") $this->outdata .= pack("V", $this->options["crc32"]->Finalize()) . pack("V", $this->options["bytes"]); 196 | } 197 | 198 | $this->open = false; 199 | 200 | return true; 201 | } 202 | 203 | private function ProcessOutput() 204 | { 205 | rewind($this->fp); 206 | 207 | // Hack! Because ftell() on a stream with a filter is still broken even under the latest PHP a mere 11 years later. 208 | // See: https://bugs.php.net/bug.php?id=49874 209 | ob_start(); 210 | fpassthru($this->fp); 211 | $this->outdata .= ob_get_contents(); 212 | ob_end_clean(); 213 | 214 | rewind($this->fp); 215 | ftruncate($this->fp, 0); 216 | } 217 | 218 | private function ProcessInput($final = false) 219 | { 220 | // Automatically determine the type of data based on the header signature. 221 | if ($this->options["type"] == "auto") 222 | { 223 | if (strlen($this->indata) >= 3) 224 | { 225 | $zlibtest = unpack("n", substr($this->indata, 0, 2)); 226 | 227 | if (substr($this->indata, 0, 3) === "\x1F\x8B\x08") $this->options["type"] = "gzip"; 228 | else if ((ord($this->indata[0]) & 0x0F) == 8 && ((ord($this->indata[0]) & 0xF0) >> 4) < 8 && $zlibtest[1] % 31 == 0) $this->options["type"] = "zlib"; 229 | else $this->options["type"] = "raw"; 230 | } 231 | else if ($final) $this->options["type"] = "raw"; 232 | } 233 | 234 | if ($this->options["type"] == "gzip") 235 | { 236 | if (!$this->options["header"]) 237 | { 238 | if (strlen($this->indata) >= 10) 239 | { 240 | $idcm = substr($this->indata, 0, 3); 241 | $flg = ord($this->indata[3]); 242 | 243 | if ($idcm !== "\x1F\x8B\x08") $this->options["type"] = "ignore"; 244 | else 245 | { 246 | // Calculate the number of bytes to skip. If flags are set, the size can be dynamic. 247 | $size = 10; 248 | $y = strlen($this->indata); 249 | 250 | // FLG.FEXTRA 251 | if ($size && ($flg & 0x04)) 252 | { 253 | if ($size + 2 >= $y) $size = 0; 254 | else 255 | { 256 | $xlen = unpack("v", substr($this->indata, $size, 2)); 257 | $size = ($size + 2 + $xlen <= $y ? $size + 2 + $xlen : 0); 258 | } 259 | } 260 | 261 | // FLG.FNAME 262 | if ($size && ($flg & 0x08)) 263 | { 264 | $pos = strpos($this->indata, "\x00", $size); 265 | $size = ($pos !== false ? $pos + 1 : 0); 266 | } 267 | 268 | // FLG.FCOMMENT 269 | if ($size && ($flg & 0x10)) 270 | { 271 | $pos = strpos($this->indata, "\x00", $size); 272 | $size = ($pos !== false ? $pos + 1 : 0); 273 | } 274 | 275 | // FLG.FHCRC 276 | if ($size && ($flg & 0x02)) $size = ($size + 2 <= $y ? $size + 2 : 0); 277 | 278 | if ($size) 279 | { 280 | $this->indata = substr($this->indata, $size); 281 | $this->options["header"] = true; 282 | } 283 | } 284 | } 285 | } 286 | 287 | if ($this->options["header"] && strlen($this->indata) > 8) 288 | { 289 | fwrite($this->fp, substr($this->indata, 0, -8)); 290 | $this->indata = substr($this->indata, -8); 291 | 292 | $this->ProcessOutput(); 293 | } 294 | 295 | if ($final) $this->indata = ""; 296 | } 297 | else if ($this->options["type"] == "zlib") 298 | { 299 | if (!$this->options["header"]) 300 | { 301 | if (strlen($this->indata) >= 2) 302 | { 303 | $cmf = ord($this->indata[0]); 304 | $flg = ord($this->indata[1]); 305 | $cm = $cmf & 0x0F; 306 | $cinfo = ($cmf & 0xF0) >> 4; 307 | 308 | // Compression method 'deflate' ($cm = 8), window size - 8 ($cinfo < 8), no preset dictionaries ($flg bit 5), checksum validates. 309 | if ($cm != 8 || $cinfo > 7 || ($flg & 0x20) || (($cmf << 8 | $flg) % 31) != 0) $this->options["type"] = "ignore"; 310 | else 311 | { 312 | $this->indata = substr($this->indata, 2); 313 | $this->options["header"] = true; 314 | } 315 | } 316 | } 317 | 318 | if ($this->options["header"] && strlen($this->indata) > 4) 319 | { 320 | fwrite($this->fp, substr($this->indata, 0, -4)); 321 | $this->indata = substr($this->indata, -4); 322 | 323 | $this->ProcessOutput(); 324 | } 325 | 326 | if ($final) $this->indata = ""; 327 | } 328 | 329 | if ($this->options["type"] == "raw") 330 | { 331 | fwrite($this->fp, $this->indata); 332 | $this->indata = ""; 333 | 334 | $this->ProcessOutput(); 335 | } 336 | 337 | // Only set when an unrecoverable header error has occurred for gzip or zlib. 338 | if ($this->options["type"] == "ignore") $this->indata = ""; 339 | } 340 | 341 | private function SHL32($num, $bits) 342 | { 343 | if ($bits < 0) $bits = 0; 344 | 345 | return $this->LIM32((int)$num << $bits); 346 | } 347 | 348 | private function LIM32($num) 349 | { 350 | return (int)((int)$num & 0xFFFFFFFF); 351 | } 352 | 353 | private function ADD32($num, $num2) 354 | { 355 | $num = (int)$num; 356 | $num2 = (int)$num2; 357 | $add = ((($num >> 30) & 0x03) + (($num2 >> 30) & 0x03)); 358 | $num = ((int)($num & 0x3FFFFFFF) + (int)($num2 & 0x3FFFFFFF)); 359 | if ($num & 0x40000000) $add++; 360 | $num = (int)(($num & 0x3FFFFFFF) | (($add & 0x03) << 30)); 361 | 362 | return $num; 363 | } 364 | } 365 | ?> -------------------------------------------------------------------------------- /support/deflate_stream.php: -------------------------------------------------------------------------------- 1 | open = false; 13 | } 14 | 15 | public function __destruct() 16 | { 17 | $this->Finalize(); 18 | } 19 | 20 | public static function IsSupported() 21 | { 22 | if (!is_bool(self::$supported)) 23 | { 24 | self::$supported = function_exists("stream_filter_append") && function_exists("stream_filter_remove") && function_exists("gzcompress"); 25 | if (self::$supported) 26 | { 27 | $data = self::Compress("test"); 28 | if ($data === false || $data === "") self::$supported = false; 29 | else 30 | { 31 | $data = self::Uncompress($data); 32 | if ($data === false || $data !== "test") self::$supported = false; 33 | } 34 | } 35 | } 36 | 37 | return self::$supported; 38 | } 39 | 40 | public static function Compress($data, $compresslevel = -1, $options = array()) 41 | { 42 | $ds = new DeflateStream; 43 | if (!$ds->Init("wb", $compresslevel, $options)) return false; 44 | if (!$ds->Write($data)) return false; 45 | if (!$ds->Finalize()) return false; 46 | $data = $ds->Read(); 47 | 48 | return $data; 49 | } 50 | 51 | public static function Uncompress($data, $options = array("type" => "auto")) 52 | { 53 | $ds = new DeflateStream; 54 | if (!$ds->Init("rb", -1, $options)) return false; 55 | if (!$ds->Write($data)) return false; 56 | if (!$ds->Finalize()) return false; 57 | $data = $ds->Read(); 58 | 59 | return $data; 60 | } 61 | 62 | public function Init($mode, $compresslevel = -1, $options = array()) 63 | { 64 | if ($mode !== "rb" && $mode !== "wb") return false; 65 | if ($this->open) $this->Finalize(); 66 | 67 | $this->fp = fopen("php://memory", "w+b"); 68 | if ($this->fp === false) return false; 69 | $this->compress = ($mode == "wb"); 70 | if (!isset($options["type"])) $options["type"] = "rfc1951"; 71 | 72 | if ($options["type"] == "rfc1950") $options["type"] = "zlib"; 73 | else if ($options["type"] == "rfc1952") $options["type"] = "gzip"; 74 | 75 | if ($options["type"] != "zlib" && $options["type"] != "gzip" && ($this->compress || $options["type"] != "auto")) $options["type"] = "raw"; 76 | $this->options = $options; 77 | 78 | // Add the deflate filter. 79 | if ($this->compress) $this->filter = stream_filter_append($this->fp, "zlib.deflate", STREAM_FILTER_WRITE, $compresslevel); 80 | else $this->filter = stream_filter_append($this->fp, "zlib.inflate", STREAM_FILTER_READ); 81 | 82 | $this->open = true; 83 | $this->indata = ""; 84 | $this->outdata = ""; 85 | 86 | if ($this->compress) 87 | { 88 | if ($this->options["type"] == "zlib") 89 | { 90 | $this->outdata .= "\x78\x9C"; 91 | $this->options["a"] = 1; 92 | $this->options["b"] = 0; 93 | } 94 | else if ($this->options["type"] == "gzip") 95 | { 96 | if (!class_exists("CRC32Stream", false)) require_once str_replace("\\", "/", dirname(__FILE__)) . "/crc32_stream.php"; 97 | 98 | $this->options["crc32"] = new CRC32Stream(); 99 | $this->options["crc32"]->Init(); 100 | $this->options["bytes"] = 0; 101 | 102 | $this->outdata .= "\x1F\x8B\x08"; 103 | $flags = 0; 104 | if (isset($this->options["filename"])) $flags |= 0x08; 105 | if (isset($this->options["comment"])) $flags |= 0x10; 106 | $this->outdata .= chr($flags); 107 | $this->outdata .= "\x00\x00\x00\x00"; 108 | $this->outdata .= "\x00"; 109 | $this->outdata .= "\x03"; 110 | 111 | if (isset($this->options["filename"])) $this->outdata .= str_replace("\x00", " ", $this->options["filename"]) . "\x00"; 112 | if (isset($this->options["comment"])) $this->outdata .= str_replace("\x00", " ", $this->options["comment"]) . "\x00"; 113 | } 114 | } 115 | else 116 | { 117 | $this->options["header"] = false; 118 | } 119 | 120 | return true; 121 | } 122 | 123 | public function Read() 124 | { 125 | $result = $this->outdata; 126 | $this->outdata = ""; 127 | 128 | return $result; 129 | } 130 | 131 | public function Write($data) 132 | { 133 | if (!$this->open) return false; 134 | 135 | if ($this->compress) 136 | { 137 | if ($this->options["type"] == "zlib") 138 | { 139 | // Adler-32. 140 | $y = strlen($data); 141 | for ($x = 0; $x < $y; $x++) 142 | { 143 | $this->options["a"] = ($this->options["a"] + ord($data[$x])) % 65521; 144 | $this->options["b"] = ($this->options["b"] + $this->options["a"]) % 65521; 145 | } 146 | } 147 | else if ($this->options["type"] == "gzip") 148 | { 149 | $this->options["crc32"]->AddData($data); 150 | $this->options["bytes"] = $this->ADD32($this->options["bytes"], strlen($data)); 151 | } 152 | 153 | $this->indata .= $data; 154 | while (strlen($this->indata) >= 65536) 155 | { 156 | fwrite($this->fp, substr($this->indata, 0, 65536)); 157 | $this->indata = substr($this->indata, 65536); 158 | 159 | $this->ProcessOutput(); 160 | } 161 | } 162 | else 163 | { 164 | $this->indata .= $data; 165 | $this->ProcessInput(); 166 | } 167 | 168 | return true; 169 | } 170 | 171 | // Finalizes the stream. 172 | public function Finalize() 173 | { 174 | if (!$this->open) return false; 175 | 176 | if (!$this->compress) $this->ProcessInput(true); 177 | 178 | if (strlen($this->indata) > 0) 179 | { 180 | fwrite($this->fp, $this->indata); 181 | $this->indata = ""; 182 | } 183 | 184 | // Removing the filter pushes the last buffer into the stream. 185 | stream_filter_remove($this->filter); 186 | $this->filter = false; 187 | 188 | $this->ProcessOutput(); 189 | 190 | fclose($this->fp); 191 | 192 | if ($this->compress) 193 | { 194 | if ($this->options["type"] == "zlib") $this->outdata .= pack("N", $this->SHL32($this->options["b"], 16) | $this->options["a"]); 195 | else if ($this->options["type"] == "gzip") $this->outdata .= pack("V", $this->options["crc32"]->Finalize()) . pack("V", $this->options["bytes"]); 196 | } 197 | 198 | $this->open = false; 199 | 200 | return true; 201 | } 202 | 203 | private function ProcessOutput() 204 | { 205 | rewind($this->fp); 206 | 207 | // Hack! Because ftell() on a stream with a filter is still broken even under the latest PHP a mere 11 years later. 208 | // See: https://bugs.php.net/bug.php?id=49874 209 | ob_start(); 210 | fpassthru($this->fp); 211 | $this->outdata .= ob_get_contents(); 212 | ob_end_clean(); 213 | 214 | rewind($this->fp); 215 | ftruncate($this->fp, 0); 216 | } 217 | 218 | private function ProcessInput($final = false) 219 | { 220 | // Automatically determine the type of data based on the header signature. 221 | if ($this->options["type"] == "auto") 222 | { 223 | if (strlen($this->indata) >= 3) 224 | { 225 | $zlibtest = unpack("n", substr($this->indata, 0, 2)); 226 | 227 | if (substr($this->indata, 0, 3) === "\x1F\x8B\x08") $this->options["type"] = "gzip"; 228 | else if ((ord($this->indata[0]) & 0x0F) == 8 && ((ord($this->indata[0]) & 0xF0) >> 4) < 8 && $zlibtest[1] % 31 == 0) $this->options["type"] = "zlib"; 229 | else $this->options["type"] = "raw"; 230 | } 231 | else if ($final) $this->options["type"] = "raw"; 232 | } 233 | 234 | if ($this->options["type"] == "gzip") 235 | { 236 | if (!$this->options["header"]) 237 | { 238 | if (strlen($this->indata) >= 10) 239 | { 240 | $idcm = substr($this->indata, 0, 3); 241 | $flg = ord($this->indata[3]); 242 | 243 | if ($idcm !== "\x1F\x8B\x08") $this->options["type"] = "ignore"; 244 | else 245 | { 246 | // Calculate the number of bytes to skip. If flags are set, the size can be dynamic. 247 | $size = 10; 248 | $y = strlen($this->indata); 249 | 250 | // FLG.FEXTRA 251 | if ($size && ($flg & 0x04)) 252 | { 253 | if ($size + 2 >= $y) $size = 0; 254 | else 255 | { 256 | $xlen = unpack("v", substr($this->indata, $size, 2)); 257 | $size = ($size + 2 + $xlen <= $y ? $size + 2 + $xlen : 0); 258 | } 259 | } 260 | 261 | // FLG.FNAME 262 | if ($size && ($flg & 0x08)) 263 | { 264 | $pos = strpos($this->indata, "\x00", $size); 265 | $size = ($pos !== false ? $pos + 1 : 0); 266 | } 267 | 268 | // FLG.FCOMMENT 269 | if ($size && ($flg & 0x10)) 270 | { 271 | $pos = strpos($this->indata, "\x00", $size); 272 | $size = ($pos !== false ? $pos + 1 : 0); 273 | } 274 | 275 | // FLG.FHCRC 276 | if ($size && ($flg & 0x02)) $size = ($size + 2 <= $y ? $size + 2 : 0); 277 | 278 | if ($size) 279 | { 280 | $this->indata = substr($this->indata, $size); 281 | $this->options["header"] = true; 282 | } 283 | } 284 | } 285 | } 286 | 287 | if ($this->options["header"] && strlen($this->indata) > 8) 288 | { 289 | fwrite($this->fp, substr($this->indata, 0, -8)); 290 | $this->indata = substr($this->indata, -8); 291 | 292 | $this->ProcessOutput(); 293 | } 294 | 295 | if ($final) $this->indata = ""; 296 | } 297 | else if ($this->options["type"] == "zlib") 298 | { 299 | if (!$this->options["header"]) 300 | { 301 | if (strlen($this->indata) >= 2) 302 | { 303 | $cmf = ord($this->indata[0]); 304 | $flg = ord($this->indata[1]); 305 | $cm = $cmf & 0x0F; 306 | $cinfo = ($cmf & 0xF0) >> 4; 307 | 308 | // Compression method 'deflate' ($cm = 8), window size - 8 ($cinfo < 8), no preset dictionaries ($flg bit 5), checksum validates. 309 | if ($cm != 8 || $cinfo > 7 || ($flg & 0x20) || (($cmf << 8 | $flg) % 31) != 0) $this->options["type"] = "ignore"; 310 | else 311 | { 312 | $this->indata = substr($this->indata, 2); 313 | $this->options["header"] = true; 314 | } 315 | } 316 | } 317 | 318 | if ($this->options["header"] && strlen($this->indata) > 4) 319 | { 320 | fwrite($this->fp, substr($this->indata, 0, -4)); 321 | $this->indata = substr($this->indata, -4); 322 | 323 | $this->ProcessOutput(); 324 | } 325 | 326 | if ($final) $this->indata = ""; 327 | } 328 | 329 | if ($this->options["type"] == "raw") 330 | { 331 | fwrite($this->fp, $this->indata); 332 | $this->indata = ""; 333 | 334 | $this->ProcessOutput(); 335 | } 336 | 337 | // Only set when an unrecoverable header error has occurred for gzip or zlib. 338 | if ($this->options["type"] == "ignore") $this->indata = ""; 339 | } 340 | 341 | private function SHL32($num, $bits) 342 | { 343 | if ($bits < 0) $bits = 0; 344 | 345 | return $this->LIM32((int)$num << $bits); 346 | } 347 | 348 | private function LIM32($num) 349 | { 350 | return (int)((int)$num & 0xFFFFFFFF); 351 | } 352 | 353 | private function ADD32($num, $num2) 354 | { 355 | $num = (int)$num; 356 | $num2 = (int)$num2; 357 | $add = ((($num >> 30) & 0x03) + (($num2 >> 30) & 0x03)); 358 | $num = ((int)($num & 0x3FFFFFFF) + (int)($num2 & 0x3FFFFFFF)); 359 | if ($num & 0x40000000) $add++; 360 | $num = (int)(($num & 0x3FFFFFFF) | (($add & 0x03) << 30)); 361 | 362 | return $num; 363 | } 364 | } 365 | ?> -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Cloud Storage Server 2 | ==================== 3 | 4 | An open source, extensible, self-hosted cloud storage API. The base server implements a complete file system similar to Amazon Cloud Drive, B2 Cloud Storage, OpenDrive, and other providers. Just don't expect to build a scalable service with this software. 5 | 6 | Cloud Storage Server pairs quite nicely with [Cloud Backup](https://github.com/cubiclesoft/cloud-backup) and [Cloud Storage Tools](https://github.com/cubiclesoft/cloud-storage-tools). 7 | 8 | [![Donate](https://cubiclesoft.com/res/donate-shield.png)](https://cubiclesoft.com/donate/) [![Discord](https://img.shields.io/discord/777282089980526602?label=chat&logo=discord)](https://cubiclesoft.com/product-support/github/) 9 | 10 | Features 11 | -------- 12 | 13 | * Completely self-contained. No need for a separate web server or enterprise database engine - just install PHP and go. 14 | * Works well with [Service Manager](https://github.com/cubiclesoft/service-manager/). Automatically start Cloud Storage Server when the system boots up. 15 | * Command-line driven user management interface. Quickly set up API keys and access levels. 16 | * The /files API implements everything needed in a file-based cloud storage provider: Folder hierarchy management, file upload/download, copy, move, rename, trash and restore, delete, and guest access. 17 | * User initialization for first time use of a specific server extension. Useful for placing a 'welcome message' and/or initial folder setup in their account. 18 | * Extensible. Only limited by your imagination. 19 | * [Remoted API Server](https://github.com/cubiclesoft/remoted-api-server) capable. 20 | * Standards-based, RESTful interface. Also supports most operations over WebSocket. 21 | * Secure. Automatically generates and signs multi-year root and server SSL certificates. You can use other certs if you want OR proxy requests from a properly configured web server. 22 | * Per-user quota management. 23 | * Per-user daily network transfer limits. 24 | * Also has a liberal open source license. MIT or LGPL, your choice. 25 | * Designed for relatively painless integration into your environment. 26 | * Sits on GitHub for all of that pull request and issue tracker goodness to easily submit changes and ideas respectively. 27 | 28 | Uses 29 | ---- 30 | 31 | * Build your own private cloud storage solution. 32 | * Use [Cloud Backup](http://cubiclesoft.com/cloud-backup) to send encrypted data to a friend's house who lives in the same town. Rapidly recover lost data in the event of catastropic loss (e.g. fire, flood). 33 | * Backup data from deep behind corporate firewalls via [Remoted API Server](https://github.com/cubiclesoft/remoted-api-server). 34 | * Customized, user-oriented, permission-based APIs. 35 | * See the Nifty Extensions section for more ideas. 36 | 37 | Getting Started 38 | --------------- 39 | 40 | Download or clone the latest software release. If you do not have PHP installed, then download and install the command-line (CLI) version for your OS (e.g. 'apt install php-cli' on Debian/Ubuntu). Windows users try [Portable Apache + PHP](https://github.com/cubiclesoft/portable-apache-maria-db-php-for-windows). 41 | 42 | You'll also need to enable the SQLite and OpenSSL PHP modules for your PHP CLI version (e.g. 'apt install php-sqlite php-openssl' on Debian/Ubuntu, edit the 'php.ini' file on Windows). 43 | 44 | From a command-line, run: 45 | 46 | ``` 47 | php install.php 48 | ``` 49 | 50 | The installer will ask a series of questions that will create the baseline server configuration. If extensions will be used that require "root" privileges (e.g. /scripts and /feeds), be sure to enter "root" for the user. When adding extensions or upgrading, re-run the installation command before starting the server to avoid problems. Skip the service installation step until you are ready to have the software run at boot. 51 | 52 | Run the user management interface and add a user with access to the 'files' extension (grants access to the /files API): 53 | 54 | ``` 55 | php manage.php 56 | 57 | Ready. This is a command-line interface. Enter 'help' to get a list of available commands. 58 | 59 | >adduser yourusername 60 | Host: https://localhost:9892 61 | API key: abcdef12......34567890-1 62 | >adduserext yourusername files 63 | [Files Ext] Allow file download access (Y/N): Y 64 | [Files Ext] Allow folder write, file upload, trash access (Y/N): Y 65 | [Files Ext] Allow permanent folder and file delete access (Y/N): Y 66 | [Files Ext] Allow guest creation/deletion (Y/N): Y 67 | >exit 68 | ``` 69 | 70 | Be sure to copy the `Host` and `API key` somewhere. Depending on the configuration and setup, `Host` might not be correct. Adjust it accordingly. 71 | 72 | To make sure the server works correctly, run it directly at first: 73 | 74 | ``` 75 | php server.php 76 | ``` 77 | 78 | Then connect to the server with a valid client SDK using the `Host` and `API key` from earlier. 79 | 80 | The easiest client to get started with is to use [Cloud Storage Tools](https://github.com/cubiclesoft/cloud-storage-tools). 81 | 82 | Example usage using the included PHP SDK with the /files extension: 83 | 84 | ```php 85 | SetAccessInfo($host, $apikey, false, false); 100 | 101 | // Force-loads the server's SSL cert and assume it to be valid. 102 | $result = $css->GetSSLInfo(); 103 | if (!$result["success"]) 104 | { 105 | var_dump($result); 106 | 107 | exit(); 108 | } 109 | 110 | // Get the server's timestamp and root folder ID. 111 | $result = $css->GetRootFolderID(); 112 | if (!$result["success"]) 113 | { 114 | var_dump($result); 115 | 116 | exit(); 117 | } 118 | 119 | var_dump($result); 120 | 121 | // Obtain an object ID via a path. 122 | $result = $css->GetObjectByPath("/"); 123 | if (!$result["success"]) 124 | { 125 | var_dump($result); 126 | 127 | exit(); 128 | } 129 | 130 | $id = $result["body"]["object"]["id"]; 131 | 132 | // Retrieve the folder list associated with an ID. 133 | $result = $css->GetFolderList($id); 134 | if (!$result["success"]) 135 | { 136 | var_dump($result); 137 | 138 | exit(); 139 | } 140 | 141 | var_dump($result); 142 | ``` 143 | 144 | Once everything is good to go, re-run the installer to install the server as a system service: 145 | 146 | ``` 147 | php install.php 148 | ``` 149 | 150 | Nifty Extensions 151 | ---------------- 152 | 153 | Both of the /feeds and /scripts extensions are considered mostly obsolete. If you need them, consider using [xcron](https://github.com/cubiclesoft/xcron) instead, which accomplishes nearly all of the same tasks that /feeds and /scripts does but far more elegantly. 154 | 155 | * [/feeds](https://github.com/cubiclesoft/cloud-storage-server-ext-feeds) - A powerful and flexible API to send and schedule notifications with data payloads attached. The /feeds API allows for scheduling future notifications and has powerful filtering features to only return information that a monitoring application is interested in. 156 | * [/scripts](https://github.com/cubiclesoft/cloud-storage-server-ext-scripts) - A powerful and flexible API to initiate named, long-running scripts as other users on a system (e.g. root/SYSTEM). The /scripts API uses a crontab-like, per-user definition file to define what scripts can be run. Parameter and limited 'stdin' passing support. While scripts are running, track status and/or monitor start and completion of script runs. 157 | 158 | Got an idea for an extension that you would like to see included? Open an issue on the issue tracker. Due to Cloud Storage Server being a server product that is intended to be network-facing, all included extensions must pass rigorous CubicleSoft standards. Extensions must also be dual licensed under MIT/LGPL. 159 | 160 | Extension: /files 161 | ------------------ 162 | 163 | The files extension implements the /files/v1 API. To try to keep this page relatively short, here is the list of available APIs, the input request method, and successful return values (always JSON output with the exceptions of 'download' and 'downloaddatabase'): 164 | 165 | GET /files/v1/folder/list/ID 166 | 167 | * ID - Folder ID 168 | * Returns: success (boolean), items (array) 169 | 170 | POST /files/v1/folder/create 171 | 172 | * name - Folder name 173 | * Returns: success (boolean), folder (array) 174 | 175 | GET /files/v1/trash/list 176 | 177 | * Returns: success (boolean), items (array) 178 | 179 | POST /files/v1/file/upload/ID 180 | 181 | * ID - Parent folder ID 182 | * name - Filename 183 | * data - File data 184 | * Returns: success (boolean), id (string) 185 | 186 | GET /files/v1/file/download/ID[/filename] 187 | 188 | * ID - File ID 189 | * filename - Ignored but used by most web browsers 190 | * Returns: Binary data 191 | 192 | GET /files/v1/file/downloaddatabase[/filename] 193 | 194 | * filename - Ignored but used by most web browsers 195 | * Returns: Binary data (the user's entire SQLite files database) 196 | 197 | GET /files/v1/object/bypath/... 198 | 199 | * ... - A human-readable path in the system from the root of the user's access level (i.e. a guest user's root is usually a subset of the entire system) 200 | * Returns: success (boolean), object (array) 201 | 202 | GET /files/v1/object/byname/ID/NAME 203 | 204 | * ID - Parent folder ID 205 | * NAME - A human-readable name or path in the system from ID 206 | * Returns: success (boolean), object (array) 207 | 208 | GET /files/v1/object/byid/ID 209 | 210 | * ID - Object ID 211 | * Returns: success (boolean), object (array) 212 | 213 | POST /files/v1/object/copy 214 | 215 | * srcid - Source object ID 216 | * destid - Destination object ID 217 | * Returns: success (boolean) 218 | 219 | POST /files/v1/object/move 220 | 221 | * srcid - Source object ID 222 | * destfolderid - Destination folder ID 223 | * Returns: success (boolean) 224 | 225 | POST /files/v1/object/rename/ID 226 | 227 | * ID - Object ID 228 | * name - New object name 229 | * Returns: success (boolean), object (array) 230 | 231 | POST /files/v1/object/trash/ID 232 | 233 | * ID - Object ID 234 | * Returns: success (boolean), object (array) 235 | 236 | POST /files/v1/object/restore/ID 237 | 238 | * ID - Object ID 239 | * Returns: success (boolean), object (array) 240 | 241 | POST /files/v1/object/delete/ID 242 | 243 | * ID - Object ID 244 | * Returns: success (boolean) 245 | 246 | GET /files/v1/user/root 247 | 248 | * Returns: success (boolean), id (string), download (boolean), upload (boolean), delete (boolean), guests (boolean), expires (integer) 249 | * Summary: Returns root ID, permissions, and expiration of the guest's API key. 250 | 251 | GET /files/v1/user/limits 252 | 253 | * Returns: success (boolean), info (array) 254 | * Summary: The 'info' array contains: quota, transferlimit, fileuploadlimit, uploadbytesleft, downloadbytesleft. 255 | 256 | GET /files/v1/guest/list 257 | 258 | * Returns: success (boolean), guests (array) 259 | 260 | POST /files/v1/guest/create 261 | 262 | * rootid - Root object ID 263 | * read - Guest can download files 264 | * write - Guest can upload, trash, and restore 265 | * delete - Guest can permanently delete objects 266 | * expires - Unix timestamp (integer) 267 | * Returns: success (boolean), id (string), info (array) 268 | * Summary: The 'info' array contains: apikey, created, expires, info (rootid, read, write, delete) 269 | 270 | POST /files/v1/guest/delete/ID 271 | 272 | * ID - Guest ID 273 | * Returns: success (boolean) 274 | -------------------------------------------------------------------------------- /support/phpseclib/Crypt/RC4.php: -------------------------------------------------------------------------------- 1 | 20 | * setKey('abcdefgh'); 26 | * 27 | * $size = 10 * 1024; 28 | * $plaintext = ''; 29 | * for ($i = 0; $i < $size; $i++) { 30 | * $plaintext.= 'a'; 31 | * } 32 | * 33 | * echo $rc4->decrypt($rc4->encrypt($plaintext)); 34 | * ?> 35 | * 36 | * 37 | * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy 38 | * of this software and associated documentation files (the "Software"), to deal 39 | * in the Software without restriction, including without limitation the rights 40 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 41 | * copies of the Software, and to permit persons to whom the Software is 42 | * furnished to do so, subject to the following conditions: 43 | * 44 | * The above copyright notice and this permission notice shall be included in 45 | * all copies or substantial portions of the Software. 46 | * 47 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 48 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 49 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 50 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 51 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 52 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 53 | * THE SOFTWARE. 54 | * 55 | * @category Crypt 56 | * @package Crypt_RC4 57 | * @author Jim Wigginton 58 | * @copyright 2007 Jim Wigginton 59 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 60 | * @link http://phpseclib.sourceforge.net 61 | */ 62 | 63 | /** 64 | * Include Crypt_Base 65 | * 66 | * Base cipher class 67 | */ 68 | if (!class_exists('Crypt_Base')) { 69 | include_once 'Base.php'; 70 | } 71 | 72 | /**#@+ 73 | * @access private 74 | * @see self::_crypt() 75 | */ 76 | define('CRYPT_RC4_ENCRYPT', 0); 77 | define('CRYPT_RC4_DECRYPT', 1); 78 | /**#@-*/ 79 | 80 | /** 81 | * Pure-PHP implementation of RC4. 82 | * 83 | * @package Crypt_RC4 84 | * @author Jim Wigginton 85 | * @access public 86 | */ 87 | class Crypt_RC4 extends Crypt_Base 88 | { 89 | /** 90 | * Block Length of the cipher 91 | * 92 | * RC4 is a stream cipher 93 | * so we the block_size to 0 94 | * 95 | * @see Crypt_Base::block_size 96 | * @var int 97 | * @access private 98 | */ 99 | var $block_size = 0; 100 | 101 | /** 102 | * Key Length (in bytes) 103 | * 104 | * @see Crypt_RC4::setKeyLength() 105 | * @var int 106 | * @access private 107 | */ 108 | var $key_length = 128; // = 1024 bits 109 | 110 | /** 111 | * The namespace used by the cipher for its constants. 112 | * 113 | * @see Crypt_Base::const_namespace 114 | * @var string 115 | * @access private 116 | */ 117 | var $const_namespace = 'RC4'; 118 | 119 | /** 120 | * The mcrypt specific name of the cipher 121 | * 122 | * @see Crypt_Base::cipher_name_mcrypt 123 | * @var string 124 | * @access private 125 | */ 126 | var $cipher_name_mcrypt = 'arcfour'; 127 | 128 | /** 129 | * Holds whether performance-optimized $inline_crypt() can/should be used. 130 | * 131 | * @see Crypt_Base::inline_crypt 132 | * @var mixed 133 | * @access private 134 | */ 135 | var $use_inline_crypt = false; // currently not available 136 | 137 | /** 138 | * The Key 139 | * 140 | * @see self::setKey() 141 | * @var string 142 | * @access private 143 | */ 144 | var $key; 145 | 146 | /** 147 | * The Key Stream for decryption and encryption 148 | * 149 | * @see self::setKey() 150 | * @var array 151 | * @access private 152 | */ 153 | var $stream; 154 | 155 | /** 156 | * Default Constructor. 157 | * 158 | * Determines whether or not the mcrypt extension should be used. 159 | * 160 | * @see Crypt_Base::Crypt_Base() 161 | * @return Crypt_RC4 162 | * @access public 163 | */ 164 | function __construct() 165 | { 166 | parent::__construct(CRYPT_MODE_STREAM); 167 | } 168 | 169 | /** 170 | * PHP4 compatible Default Constructor. 171 | * 172 | * @see self::__construct() 173 | * @access public 174 | */ 175 | function Crypt_RC4() 176 | { 177 | $this->__construct(); 178 | } 179 | 180 | /** 181 | * Test for engine validity 182 | * 183 | * This is mainly just a wrapper to set things up for Crypt_Base::isValidEngine() 184 | * 185 | * @see Crypt_Base::Crypt_Base() 186 | * @param int $engine 187 | * @access public 188 | * @return bool 189 | */ 190 | function isValidEngine($engine) 191 | { 192 | if ($engine == CRYPT_ENGINE_OPENSSL) { 193 | if (version_compare(PHP_VERSION, '5.3.7') >= 0) { 194 | $this->cipher_name_openssl = 'rc4-40'; 195 | } else { 196 | switch (strlen($this->key)) { 197 | case 5: 198 | $this->cipher_name_openssl = 'rc4-40'; 199 | break; 200 | case 8: 201 | $this->cipher_name_openssl = 'rc4-64'; 202 | break; 203 | case 16: 204 | $this->cipher_name_openssl = 'rc4'; 205 | break; 206 | default: 207 | return false; 208 | } 209 | } 210 | } 211 | 212 | return parent::isValidEngine($engine); 213 | } 214 | 215 | /** 216 | * Dummy function. 217 | * 218 | * Some protocols, such as WEP, prepend an "initialization vector" to the key, effectively creating a new key [1]. 219 | * If you need to use an initialization vector in this manner, feel free to prepend it to the key, yourself, before 220 | * calling setKey(). 221 | * 222 | * [1] WEP's initialization vectors (IV's) are used in a somewhat insecure way. Since, in that protocol, 223 | * the IV's are relatively easy to predict, an attack described by 224 | * {@link http://www.drizzle.com/~aboba/IEEE/rc4_ksaproc.pdf Scott Fluhrer, Itsik Mantin, and Adi Shamir} 225 | * can be used to quickly guess at the rest of the key. The following links elaborate: 226 | * 227 | * {@link http://www.rsa.com/rsalabs/node.asp?id=2009 http://www.rsa.com/rsalabs/node.asp?id=2009} 228 | * {@link http://en.wikipedia.org/wiki/Related_key_attack http://en.wikipedia.org/wiki/Related_key_attack} 229 | * 230 | * @param string $iv 231 | * @see self::setKey() 232 | * @access public 233 | */ 234 | function setIV($iv) 235 | { 236 | } 237 | 238 | /** 239 | * Sets the key length 240 | * 241 | * Keys can be between 1 and 256 bytes long. 242 | * 243 | * @access public 244 | * @param int $length 245 | */ 246 | function setKeyLength($length) 247 | { 248 | if ($length < 8) { 249 | $this->key_length = 1; 250 | } elseif ($length > 2048) { 251 | $this->key_length = 256; 252 | } else { 253 | $this->key_length = $length >> 3; 254 | } 255 | 256 | parent::setKeyLength($length); 257 | } 258 | 259 | /** 260 | * Encrypts a message. 261 | * 262 | * @see Crypt_Base::decrypt() 263 | * @see self::_crypt() 264 | * @access public 265 | * @param string $plaintext 266 | * @return string $ciphertext 267 | */ 268 | function encrypt($plaintext) 269 | { 270 | if ($this->engine != CRYPT_ENGINE_INTERNAL) { 271 | return parent::encrypt($plaintext); 272 | } 273 | return $this->_crypt($plaintext, CRYPT_RC4_ENCRYPT); 274 | } 275 | 276 | /** 277 | * Decrypts a message. 278 | * 279 | * $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)). 280 | * At least if the continuous buffer is disabled. 281 | * 282 | * @see Crypt_Base::encrypt() 283 | * @see self::_crypt() 284 | * @access public 285 | * @param string $ciphertext 286 | * @return string $plaintext 287 | */ 288 | function decrypt($ciphertext) 289 | { 290 | if ($this->engine != CRYPT_ENGINE_INTERNAL) { 291 | return parent::decrypt($ciphertext); 292 | } 293 | return $this->_crypt($ciphertext, CRYPT_RC4_DECRYPT); 294 | } 295 | 296 | 297 | /** 298 | * Setup the key (expansion) 299 | * 300 | * @see Crypt_Base::_setupKey() 301 | * @access private 302 | */ 303 | function _setupKey() 304 | { 305 | $key = $this->key; 306 | $keyLength = strlen($key); 307 | $keyStream = range(0, 255); 308 | $j = 0; 309 | for ($i = 0; $i < 256; $i++) { 310 | $j = ($j + $keyStream[$i] + ord($key[$i % $keyLength])) & 255; 311 | $temp = $keyStream[$i]; 312 | $keyStream[$i] = $keyStream[$j]; 313 | $keyStream[$j] = $temp; 314 | } 315 | 316 | $this->stream = array(); 317 | $this->stream[CRYPT_RC4_DECRYPT] = $this->stream[CRYPT_RC4_ENCRYPT] = array( 318 | 0, // index $i 319 | 0, // index $j 320 | $keyStream 321 | ); 322 | } 323 | 324 | /** 325 | * Encrypts or decrypts a message. 326 | * 327 | * @see self::encrypt() 328 | * @see self::decrypt() 329 | * @access private 330 | * @param string $text 331 | * @param int $mode 332 | * @return string $text 333 | */ 334 | function _crypt($text, $mode) 335 | { 336 | if ($this->changed) { 337 | $this->_setup(); 338 | $this->changed = false; 339 | } 340 | 341 | $stream = &$this->stream[$mode]; 342 | if ($this->continuousBuffer) { 343 | $i = &$stream[0]; 344 | $j = &$stream[1]; 345 | $keyStream = &$stream[2]; 346 | } else { 347 | $i = $stream[0]; 348 | $j = $stream[1]; 349 | $keyStream = $stream[2]; 350 | } 351 | 352 | $len = strlen($text); 353 | for ($k = 0; $k < $len; ++$k) { 354 | $i = ($i + 1) & 255; 355 | $ksi = $keyStream[$i]; 356 | $j = ($j + $ksi) & 255; 357 | $ksj = $keyStream[$j]; 358 | 359 | $keyStream[$i] = $ksj; 360 | $keyStream[$j] = $ksi; 361 | $text[$k] = $text[$k] ^ chr($keyStream[($ksj + $ksi) & 255]); 362 | } 363 | 364 | return $text; 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /sdks/php/sdk_cloud_storage_server_api_base.php: -------------------------------------------------------------------------------- 1 | web = new WebBrowser(); 19 | $this->fp = false; 20 | $this->host = false; 21 | $this->apikey = false; 22 | $this->cafile = false; 23 | $this->cacert = false; 24 | $this->cert = false; 25 | $this->apiprefix = false; 26 | } 27 | 28 | public function SetAccessInfo($host, $apikey, $cafile, $cert) 29 | { 30 | $this->web = new WebBrowser(); 31 | if (is_resource($this->fp)) @fclose($this->fp); 32 | $this->fp = false; 33 | if (substr($host, -1) === "/") $host = substr($host, 0, -1); 34 | $this->host = $host; 35 | $this->apikey = $apikey; 36 | $this->cafile = $cafile; 37 | $this->cert = $cert; 38 | } 39 | 40 | public function GetSSLInfo() 41 | { 42 | if ($this->host === false) return array("success" => false, "error" => self::CSS_Translate("Missing host."), "errorcode" => "no_access_info"); 43 | 44 | if ($this->cafile !== false && $this->cacert === false) $this->cacert = @file_get_contents($this->cafile); 45 | 46 | if (substr($this->host, 0, 7) === "http://" || RemotedAPI::IsRemoted($this->host)) 47 | { 48 | $this->cacert = ""; 49 | $this->cert = ""; 50 | } 51 | else if ($this->cacert === false || $this->cert === false) 52 | { 53 | $this->cacert = false; 54 | $this->cert = false; 55 | 56 | $this->neterror = ""; 57 | 58 | $options = array( 59 | "peer_cert_callback" => array($this, "Internal_PeerCertificateCheck"), 60 | "peer_cert_callback_opts" => "", 61 | "sslopts" => self::InitSSLOpts(array("verify_peer" => false, "capture_peer_cert_chain" => true)) 62 | ); 63 | 64 | $result = $this->web->Process($this->host . "/", $options); 65 | 66 | if (!$result["success"]) 67 | { 68 | $result["error"] .= " " . $this->neterror; 69 | 70 | return $result; 71 | } 72 | } 73 | 74 | if ($this->cert === false) return array("success" => false, "error" => self::CSS_Translate("Unable to retrieve server certificate."), "errorcode" => "cert_retrieval_failed"); 75 | if ($this->cacert === false) return array("success" => false, "error" => self::CSS_Translate("Unable to retrieve server CA certificate."), "errorcode" => "cacert_retrieval_failed"); 76 | 77 | return array("success" => true, "cacert" => $this->cacert, "cert" => $this->cert); 78 | } 79 | 80 | public function InitSSLCache($host, $cafile, $certfile) 81 | { 82 | if (!file_exists($cafile) || !file_exists($certfile)) 83 | { 84 | $this->SetAccessInfo($host, false, false, false); 85 | 86 | $result = $this->GetSSLInfo(); 87 | if (!$result["success"]) return array("success" => false, "error" => "Unable to get SSL information.", "errorcode" => "get_ssl_info_failed", "info" => $result); 88 | 89 | file_put_contents($cafile, $result["cacert"]); 90 | file_put_contents($certfile, $result["cert"]); 91 | } 92 | 93 | return array("success" => true); 94 | } 95 | 96 | protected static function CSS_Translate() 97 | { 98 | $args = func_get_args(); 99 | if (!count($args)) return ""; 100 | 101 | return call_user_func_array((defined("CS_TRANSLATE_FUNC") && function_exists(CS_TRANSLATE_FUNC) ? CS_TRANSLATE_FUNC : "sprintf"), $args); 102 | } 103 | 104 | // Internal function to retrieve a X509 SSL certificate during the initial connection to confirm that this is the correct target server. 105 | public function Internal_PeerCertificateCheck($type, $cert, $opts) 106 | { 107 | if (is_array($cert)) 108 | { 109 | // The server is incorrectly configured if it doesn't have the self-signed root certificate in the chain. 110 | if (count($cert) < 2) 111 | { 112 | $this->neterror = "Certificate chain is missing the root certificate. Remote host is incorrectly configured."; 113 | 114 | return false; 115 | } 116 | 117 | // The last entry is the root cert. 118 | if (!openssl_x509_export($cert[count($cert) - 1], $str)) 119 | { 120 | $this->neterror = "Certificate chain contains an invalid root certificate. Corrupted on remote host?"; 121 | 122 | return false; 123 | } 124 | 125 | $this->cacert = $str; 126 | } 127 | else 128 | { 129 | if (!openssl_x509_export($cert, $str)) 130 | { 131 | $this->neterror = "Server certificate is invalid. Corrupted on remote host?"; 132 | 133 | return false; 134 | } 135 | 136 | // Initial setup automatically trusts the SSL/TLS certificate of the host. 137 | if ($this->cert === false) $this->cert = $str; 138 | else if ($str !== $this->cert) 139 | { 140 | $this->neterror = "Certificate does not exactly match local certificate. Your client is either under a MITM attack or the remote host changed certificates."; 141 | 142 | return false; 143 | } 144 | } 145 | 146 | return true; 147 | } 148 | 149 | protected static function InitSSLOpts($options) 150 | { 151 | $result = array_merge(array( 152 | "ciphers" => "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS", 153 | "disable_compression" => true, 154 | "allow_self_signed" => false, 155 | "verify_peer_name" => false, 156 | "verify_depth" => 3, 157 | "capture_peer_cert" => true, 158 | ), $options); 159 | 160 | return $result; 161 | } 162 | 163 | protected function InitWebSocket() 164 | { 165 | if ($this->host === false || $this->apikey === false) return array("success" => false, "error" => self::CSS_Translate("Missing host or API key."), "errorcode" => "no_access_info"); 166 | if ($this->cafile === false || $this->cert === false) return array("success" => false, "error" => self::CSS_Translate("Missing SSL Certificate or Certificate Authority filename. Call GetSSLInfo() to initialize for the first time and be sure to save the results."), "errorcode" => "critical_ssl_info_missing"); 167 | 168 | $url = $this->host; 169 | 170 | // Handle Remoted API connections. 171 | if ($this->fp === false && RemotedAPI::IsRemoted($this->host)) 172 | { 173 | $result = RemotedAPI::Connect($this->host); 174 | if (!$result["success"]) return $result; 175 | 176 | $this->fp = $result["fp"]; 177 | $url = $result["url"]; 178 | 179 | $options = array( 180 | "fp" => $this->fp, 181 | "headers" => array( 182 | "X-APIKey" => $this->apikey 183 | ) 184 | ); 185 | } 186 | else 187 | { 188 | $options = array( 189 | "headers" => array( 190 | "X-APIKey" => $this->apikey 191 | ), 192 | "peer_cert_callback" => array($this, "Internal_PeerCertificateCheck"), 193 | "peer_cert_callback_opts" => "", 194 | "sslopts" => self::InitSSLOpts(array("cafile" => $this->cafile, "verify_peer" => true)) 195 | ); 196 | } 197 | 198 | if (!class_exists("WebSocket", false)) require_once str_replace("\\", "/", dirname(__FILE__)) . "/websocket.php"; 199 | 200 | $ws = new WebSocket(); 201 | 202 | $wsurl = str_replace(array("https://", "http://"), array("wss://", "ws://"), $url); 203 | $result = $ws->Connect($wsurl . $this->apiprefix, $url, $options); 204 | if (!$result["success"]) return $result; 205 | 206 | $result["ws"] = $ws; 207 | 208 | return $result; 209 | } 210 | 211 | protected function RunAPI($method, $apipath, $options = array(), $expected = 200, $encodejson = true, $decodebody = true) 212 | { 213 | if ($this->host === false || $this->apikey === false) return array("success" => false, "error" => self::CSS_Translate("Missing host or API key."), "errorcode" => "no_access_info"); 214 | if ($this->cafile === false || $this->cert === false) return array("success" => false, "error" => self::CSS_Translate("Missing SSL Certificate or Certificate Authority filename. Call GetSSLInfo() to initialize for the first time and be sure to save the results."), "errorcode" => "critical_ssl_info_missing"); 215 | 216 | $url = $this->host; 217 | 218 | // Handle Remoted API connections. 219 | if ($this->fp === false && RemotedAPI::IsRemoted($this->host)) 220 | { 221 | $result = RemotedAPI::Connect($this->host); 222 | if (!$result["success"]) return $result; 223 | 224 | $this->fp = $result["fp"]; 225 | $url = $result["url"]; 226 | 227 | $options2 = array( 228 | "fp" => $this->fp, 229 | "method" => $method, 230 | "headers" => array( 231 | "Connection" => "keep-alive", 232 | "X-APIKey" => $this->apikey 233 | ) 234 | ); 235 | } 236 | else if ($this->fp !== false) 237 | { 238 | $url = RemotedAPI::ExtractRealHost($url); 239 | 240 | $options2 = array( 241 | "fp" => $this->fp, 242 | "method" => $method, 243 | "headers" => array( 244 | "Connection" => "keep-alive" 245 | ) 246 | ); 247 | } 248 | else 249 | { 250 | $options2 = array( 251 | "method" => $method, 252 | "headers" => array( 253 | "Connection" => "keep-alive", 254 | "X-APIKey" => $this->apikey 255 | ), 256 | "peer_cert_callback" => array($this, "Internal_PeerCertificateCheck"), 257 | "peer_cert_callback_opts" => "", 258 | "sslopts" => self::InitSSLOpts(array("cafile" => $this->cafile, "verify_peer" => true)) 259 | ); 260 | } 261 | 262 | if ($encodejson && $method !== "GET") 263 | { 264 | $options2["headers"]["Content-Type"] = "application/json"; 265 | $options2["body"] = json_encode($options); 266 | } 267 | else 268 | { 269 | $options2 = array_merge($options2, $options); 270 | } 271 | 272 | $result = $this->web->Process($url . $this->apiprefix . "/" . $apipath, $options2); 273 | 274 | if (!$result["success"] && $this->fp !== false) 275 | { 276 | // If the server terminated the connection, then re-establish the connection and rerun the request. 277 | if (is_resource($this->fp)) @fclose($this->fp); 278 | $this->fp = false; 279 | 280 | return $this->RunAPI($method, $apipath, $options, $expected, $encodejson, $decodebody); 281 | } 282 | 283 | if (!$result["success"]) return $result; 284 | 285 | if (isset($result["fp"]) && is_resource($result["fp"])) $this->fp = $result["fp"]; 286 | else $this->fp = false; 287 | 288 | // Cloud Storage Server always responds with 400 Bad Request for errors. Attempt to decode the error. 289 | if ($result["response"]["code"] == 400) 290 | { 291 | $error = @json_decode($result["body"], true); 292 | if (is_array($error) && isset($error["success"]) && !$error["success"]) return $error; 293 | } 294 | 295 | if ($result["response"]["code"] != $expected) return array("success" => false, "error" => self::CSS_Translate("Expected a %d response from Cloud Storage Server. Received '%s'.", $expected, $result["response"]["line"]), "errorcode" => "unexpected_cloud_storage_server_response", "info" => $result); 296 | 297 | if ($decodebody) $result["body"] = json_decode($result["body"], true); 298 | 299 | return $result; 300 | } 301 | } 302 | ?> -------------------------------------------------------------------------------- /sdks/php/random.php: -------------------------------------------------------------------------------- 1 | mode = false; 15 | $this->fp = false; 16 | $this->cryptosafe = $cryptosafe; 17 | 18 | // Native first (PHP 7 and later). 19 | if (function_exists("random_bytes")) $this->mode = "native"; 20 | 21 | // OpenSSL fallback. 22 | if ($this->mode === false && function_exists("openssl_random_pseudo_bytes")) 23 | { 24 | // PHP 5.4.0 introduced native Windows CryptGenRandom() integration via php_win32_get_random_bytes() for performance. 25 | @openssl_random_pseudo_bytes(4, $strong); 26 | if ($strong) $this->mode = "openssl"; 27 | } 28 | 29 | // Locate a (relatively) suitable source of entropy or raise an exception. 30 | if (strtoupper(substr(PHP_OS, 0, 3)) === "WIN") 31 | { 32 | // PHP 5.3.0 introduced native Windows CryptGenRandom() integration via php_win32_get_random_bytes() for functionality. 33 | if ($this->mode === false && PHP_VERSION_ID > 50300 && function_exists("mcrypt_create_iv")) $this->mode = "mcrypt"; 34 | } 35 | else 36 | { 37 | if (!$cryptosafe && $this->mode === false && file_exists("/dev/arandom")) 38 | { 39 | // OpenBSD. mcrypt doesn't attempt to use this despite claims of higher quality entropy with performance. 40 | $this->fp = @fopen("/dev/arandom", "rb"); 41 | if ($this->fp !== false) $this->mode = "file"; 42 | } 43 | 44 | if ($cryptosafe && $this->mode === false && file_exists("/dev/random")) 45 | { 46 | // Everything else. 47 | $this->fp = @fopen("/dev/random", "rb"); 48 | if ($this->fp !== false) $this->mode = "file"; 49 | } 50 | 51 | if (!$cryptosafe && $this->mode === false && file_exists("/dev/urandom")) 52 | { 53 | // Everything else. 54 | $this->fp = @fopen("/dev/urandom", "rb"); 55 | if ($this->fp !== false) $this->mode = "file"; 56 | } 57 | 58 | if ($this->mode === false && function_exists("mcrypt_create_iv")) 59 | { 60 | // mcrypt_create_iv() is last because it opens and closes a file handle every single call. 61 | $this->mode = "mcrypt"; 62 | } 63 | } 64 | 65 | // Throw an exception if unable to find a suitable entropy source. 66 | if ($this->mode === false) 67 | { 68 | throw new Exception(self::RNG_Translate("Unable to locate a suitable entropy source.")); 69 | exit(); 70 | } 71 | } 72 | 73 | public function __destruct() 74 | { 75 | if ($this->mode === "file") fclose($this->fp); 76 | } 77 | 78 | public function GetBytes($length) 79 | { 80 | if ($this->mode === false) return false; 81 | 82 | $length = (int)$length; 83 | if ($length < 1) return false; 84 | 85 | $result = ""; 86 | do 87 | { 88 | switch ($this->mode) 89 | { 90 | case "native": $data = @random_bytes($length); break; 91 | case "openssl": $data = @openssl_random_pseudo_bytes($length, $strong); if (!$strong) $data = false; break; 92 | case "mcrypt": $data = @mcrypt_create_iv($length, ($this->cryptosafe ? MCRYPT_DEV_RANDOM : MCRYPT_DEV_URANDOM)); break; 93 | case "file": $data = @fread($this->fp, $length); break; 94 | default: $data = false; 95 | } 96 | if ($data === false) return false; 97 | 98 | $result .= $data; 99 | } while (strlen($result) < $length); 100 | 101 | return substr($result, 0, $length); 102 | } 103 | 104 | public function GenerateToken($length = 64) 105 | { 106 | $data = $this->GetBytes($length); 107 | if ($data === false) return false; 108 | 109 | return bin2hex($data); 110 | } 111 | 112 | // Get a random number between $min and $max (inclusive). 113 | public function GetInt($min, $max) 114 | { 115 | $min = (int)$min; 116 | $max = (int)$max; 117 | if ($max < $min) return false; 118 | if ($min == $max) return $min; 119 | 120 | $range = $max - $min + 1; 121 | 122 | $bits = 1; 123 | while ((1 << $bits) <= $range) $bits++; 124 | 125 | $numbytes = (int)(($bits + 7) / 8); 126 | $mask = (1 << $bits) - 1; 127 | 128 | do 129 | { 130 | $data = $this->GetBytes($numbytes); 131 | if ($data === false) return false; 132 | 133 | $result = 0; 134 | for ($x = 0; $x < $numbytes; $x++) 135 | { 136 | $result = ($result * 256) + ord($data[$x]); 137 | } 138 | 139 | $result = $result & $mask; 140 | } while ($result >= $range); 141 | 142 | return $result + $min; 143 | } 144 | 145 | // Convenience method to generate a random alphanumeric string. 146 | public function GenerateString($size = 32) 147 | { 148 | $result = ""; 149 | for ($x = 0; $x < $size; $x++) 150 | { 151 | $data = $this->GetInt(0, 61); 152 | if ($data === false) return false; 153 | 154 | $result .= self::$alphanum[$data]; 155 | } 156 | 157 | return $result; 158 | } 159 | 160 | public function GenerateWordLite(&$freqmap, $len) 161 | { 162 | $totalc = 0; 163 | $totalv = 0; 164 | foreach ($freqmap["consonants"] as $chr => $num) $totalc += $num; 165 | foreach ($freqmap["vowels"] as $chr => $num) $totalv += $num; 166 | 167 | if ($totalc <= 0 || $totalv <= 0) return false; 168 | 169 | $result = ""; 170 | for ($x = 0; $x < $len; $x++) 171 | { 172 | if ($x % 2) 173 | { 174 | $data = $this->GetInt(0, $totalv - 1); 175 | if ($data === false) return false; 176 | 177 | foreach ($freqmap["vowels"] as $chr => $num) 178 | { 179 | if ($num > $data) 180 | { 181 | $result .= $chr; 182 | 183 | break; 184 | } 185 | 186 | $data -= $num; 187 | } 188 | } 189 | else 190 | { 191 | $data = $this->GetInt(0, $totalc - 1); 192 | if ($data === false) return false; 193 | 194 | foreach ($freqmap["consonants"] as $chr => $num) 195 | { 196 | if ($num > $data) 197 | { 198 | $result .= $chr; 199 | 200 | break; 201 | } 202 | 203 | $data -= $num; 204 | } 205 | } 206 | } 207 | 208 | return $result; 209 | } 210 | 211 | public function GenerateWord(&$freqmap, $len, $separator = "-") 212 | { 213 | $result = ""; 214 | $queue = array(); 215 | $threshold = $freqmap["threshold"]; 216 | $state = "start"; 217 | while ($len) 218 | { 219 | //echo $state . " - " . $len . ": " . $result . "\n"; 220 | switch ($state) 221 | { 222 | case "start": 223 | { 224 | // The start of the word (or restart). 225 | $path = &$freqmap["start"]; 226 | while (count($queue) < $threshold && $len) 227 | { 228 | if ($len > 1 || !$path["*"]) 229 | { 230 | // Some part of the word. 231 | $found = false; 232 | if ($path[""]) 233 | { 234 | $pos = $this->GetInt(0, $path[""] - 1); 235 | 236 | foreach ($path as $chr => &$info) 237 | { 238 | if (!is_array($info)) continue; 239 | 240 | if ($info["+"] > $pos) 241 | { 242 | $result .= $chr; 243 | $queue[] = $chr; 244 | $path = &$path[$chr]; 245 | $len--; 246 | 247 | $found = true; 248 | 249 | break; 250 | } 251 | 252 | $pos -= $info["+"]; 253 | } 254 | } 255 | 256 | if (!$found) 257 | { 258 | $state = (count($queue) ? "recovery" : "restart"); 259 | 260 | break; 261 | } 262 | } 263 | else 264 | { 265 | // Last letter of the word. 266 | $found = false; 267 | if ($path["*"]) 268 | { 269 | $pos = $this->GetInt(0, $path["*"] - 1); 270 | 271 | foreach ($path as $chr => &$info) 272 | { 273 | if (!is_array($info)) continue; 274 | 275 | if ($info["-"] > $pos) 276 | { 277 | $result .= $chr; 278 | $queue[] = $chr; 279 | $path = &$path[$chr]; 280 | $len--; 281 | 282 | $found = true; 283 | 284 | break; 285 | } 286 | 287 | $pos -= $info["-"]; 288 | } 289 | } 290 | 291 | if (!$found) 292 | { 293 | $state = (count($queue) ? "end" : "restart"); 294 | 295 | break; 296 | } 297 | } 298 | } 299 | 300 | if (count($queue) >= $threshold) $state = ($len >= $threshold ? "middle" : "end"); 301 | 302 | break; 303 | } 304 | case "middle": 305 | { 306 | // The middle of the word. 307 | $str = implode("", $queue); 308 | 309 | if (!isset($freqmap["middle"][$str])) $state = "recovery"; 310 | else 311 | { 312 | $found = false; 313 | 314 | if ($freqmap["middle"][$str][""]) 315 | { 316 | $pos = $this->GetInt(0, $freqmap["middle"][$str][""] - 1); 317 | 318 | foreach ($freqmap["middle"][$str] as $chr => $num) 319 | { 320 | if ($chr === "") continue; 321 | 322 | if ($num > $pos) 323 | { 324 | $result .= $chr; 325 | $queue[] = $chr; 326 | array_shift($queue); 327 | $len--; 328 | 329 | if ($len < $threshold) $state = "end"; 330 | 331 | $found = true; 332 | 333 | break; 334 | } 335 | 336 | $pos -= $num; 337 | } 338 | } 339 | 340 | if (!$found) $state = "recovery"; 341 | } 342 | 343 | break; 344 | } 345 | case "end": 346 | { 347 | if (!isset($freqmap["end"][$len]) || !count($queue) || !isset($freqmap["end"][$len][$queue[count($queue) - 1]])) $state = "restart"; 348 | else 349 | { 350 | $path = &$freqmap["end"][$len][$queue[count($queue) - 1]]; 351 | 352 | $found = false; 353 | 354 | if ($path[""]) 355 | { 356 | $pos = $this->GetInt(0, $path[""] - 1); 357 | 358 | foreach ($path as $str => $num) 359 | { 360 | if ($str === "") continue; 361 | 362 | if ($num > $pos) 363 | { 364 | $result .= $str; 365 | $len = 0; 366 | 367 | $found = true; 368 | 369 | break; 370 | } 371 | 372 | $pos -= $num; 373 | } 374 | } 375 | 376 | if (!$found) $state = "restart"; 377 | } 378 | 379 | break; 380 | } 381 | case "recovery": 382 | { 383 | if (!count($queue) || !isset($freqmap["recovery"][$queue[count($queue) - 1]])) $state = "restart"; 384 | else 385 | { 386 | $path = &$freqmap["recovery"][$queue[count($queue) - 1]]; 387 | 388 | $found = false; 389 | 390 | if ($path[""]) 391 | { 392 | $pos = $this->GetInt(0, $path[""] - 1); 393 | 394 | foreach ($path as $chr => $num) 395 | { 396 | if ($chr === "") continue; 397 | 398 | if ($num > $pos) 399 | { 400 | $result .= $chr; 401 | $queue[] = $chr; 402 | array_shift($queue); 403 | $len--; 404 | 405 | $state = ($len >= $threshold ? "middle" : "end"); 406 | 407 | $found = true; 408 | 409 | break; 410 | } 411 | 412 | $pos -= $num; 413 | } 414 | } 415 | 416 | if (!$found) $state = "restart"; 417 | } 418 | 419 | break; 420 | } 421 | case "restart": 422 | { 423 | $result .= $separator; 424 | $queue = array(); 425 | $len -= strlen($separator); 426 | 427 | $state = "start"; 428 | 429 | break; 430 | } 431 | } 432 | } 433 | 434 | return $result; 435 | } 436 | 437 | public function GetMode() 438 | { 439 | return $this->mode; 440 | } 441 | 442 | protected static function RNG_Translate() 443 | { 444 | $args = func_get_args(); 445 | if (!count($args)) return ""; 446 | 447 | return call_user_func_array((defined("CS_TRANSLATE_FUNC") && function_exists(CS_TRANSLATE_FUNC) ? CS_TRANSLATE_FUNC : "sprintf"), $args); 448 | } 449 | } 450 | ?> -------------------------------------------------------------------------------- /support/random.php: -------------------------------------------------------------------------------- 1 | mode = false; 15 | $this->fp = false; 16 | $this->cryptosafe = $cryptosafe; 17 | 18 | // Native first (PHP 7 and later). 19 | if (function_exists("random_bytes")) $this->mode = "native"; 20 | 21 | // OpenSSL fallback. 22 | if ($this->mode === false && function_exists("openssl_random_pseudo_bytes")) 23 | { 24 | // PHP 5.4.0 introduced native Windows CryptGenRandom() integration via php_win32_get_random_bytes() for performance. 25 | @openssl_random_pseudo_bytes(4, $strong); 26 | if ($strong) $this->mode = "openssl"; 27 | } 28 | 29 | // Locate a (relatively) suitable source of entropy or raise an exception. 30 | if (strtoupper(substr(PHP_OS, 0, 3)) === "WIN") 31 | { 32 | // PHP 5.3.0 introduced native Windows CryptGenRandom() integration via php_win32_get_random_bytes() for functionality. 33 | if ($this->mode === false && PHP_VERSION_ID > 50300 && function_exists("mcrypt_create_iv")) $this->mode = "mcrypt"; 34 | } 35 | else 36 | { 37 | if (!$cryptosafe && $this->mode === false && file_exists("/dev/arandom")) 38 | { 39 | // OpenBSD. mcrypt doesn't attempt to use this despite claims of higher quality entropy with performance. 40 | $this->fp = @fopen("/dev/arandom", "rb"); 41 | if ($this->fp !== false) $this->mode = "file"; 42 | } 43 | 44 | if ($cryptosafe && $this->mode === false && file_exists("/dev/random")) 45 | { 46 | // Everything else. 47 | $this->fp = @fopen("/dev/random", "rb"); 48 | if ($this->fp !== false) $this->mode = "file"; 49 | } 50 | 51 | if (!$cryptosafe && $this->mode === false && file_exists("/dev/urandom")) 52 | { 53 | // Everything else. 54 | $this->fp = @fopen("/dev/urandom", "rb"); 55 | if ($this->fp !== false) $this->mode = "file"; 56 | } 57 | 58 | if ($this->mode === false && function_exists("mcrypt_create_iv")) 59 | { 60 | // mcrypt_create_iv() is last because it opens and closes a file handle every single call. 61 | $this->mode = "mcrypt"; 62 | } 63 | } 64 | 65 | // Throw an exception if unable to find a suitable entropy source. 66 | if ($this->mode === false) 67 | { 68 | throw new Exception(self::RNG_Translate("Unable to locate a suitable entropy source.")); 69 | exit(); 70 | } 71 | } 72 | 73 | public function __destruct() 74 | { 75 | if ($this->mode === "file") fclose($this->fp); 76 | } 77 | 78 | public function GetBytes($length) 79 | { 80 | if ($this->mode === false) return false; 81 | 82 | $length = (int)$length; 83 | if ($length < 1) return false; 84 | 85 | $result = ""; 86 | do 87 | { 88 | switch ($this->mode) 89 | { 90 | case "native": $data = @random_bytes($length); break; 91 | case "openssl": $data = @openssl_random_pseudo_bytes($length, $strong); if (!$strong) $data = false; break; 92 | case "mcrypt": $data = @mcrypt_create_iv($length, ($this->cryptosafe ? MCRYPT_DEV_RANDOM : MCRYPT_DEV_URANDOM)); break; 93 | case "file": $data = @fread($this->fp, $length); break; 94 | default: $data = false; 95 | } 96 | if ($data === false) return false; 97 | 98 | $result .= $data; 99 | } while (strlen($result) < $length); 100 | 101 | return substr($result, 0, $length); 102 | } 103 | 104 | public function GenerateToken($length = 64) 105 | { 106 | $data = $this->GetBytes($length); 107 | if ($data === false) return false; 108 | 109 | return bin2hex($data); 110 | } 111 | 112 | // Get a random number between $min and $max (inclusive). 113 | public function GetInt($min, $max) 114 | { 115 | $min = (int)$min; 116 | $max = (int)$max; 117 | if ($max < $min) return false; 118 | if ($min == $max) return $min; 119 | 120 | $range = $max - $min + 1; 121 | 122 | $bits = 1; 123 | while ((1 << $bits) <= $range) $bits++; 124 | 125 | $numbytes = (int)(($bits + 7) / 8); 126 | $mask = (1 << $bits) - 1; 127 | 128 | do 129 | { 130 | $data = $this->GetBytes($numbytes); 131 | if ($data === false) return false; 132 | 133 | $result = 0; 134 | for ($x = 0; $x < $numbytes; $x++) 135 | { 136 | $result = ($result * 256) + ord($data[$x]); 137 | } 138 | 139 | $result = $result & $mask; 140 | } while ($result >= $range); 141 | 142 | return $result + $min; 143 | } 144 | 145 | // Convenience method to generate a random alphanumeric string. 146 | public function GenerateString($size = 32) 147 | { 148 | $result = ""; 149 | for ($x = 0; $x < $size; $x++) 150 | { 151 | $data = $this->GetInt(0, 61); 152 | if ($data === false) return false; 153 | 154 | $result .= self::$alphanum[$data]; 155 | } 156 | 157 | return $result; 158 | } 159 | 160 | public function GenerateWordLite(&$freqmap, $len) 161 | { 162 | $totalc = 0; 163 | $totalv = 0; 164 | foreach ($freqmap["consonants"] as $chr => $num) $totalc += $num; 165 | foreach ($freqmap["vowels"] as $chr => $num) $totalv += $num; 166 | 167 | if ($totalc <= 0 || $totalv <= 0) return false; 168 | 169 | $result = ""; 170 | for ($x = 0; $x < $len; $x++) 171 | { 172 | if ($x % 2) 173 | { 174 | $data = $this->GetInt(0, $totalv - 1); 175 | if ($data === false) return false; 176 | 177 | foreach ($freqmap["vowels"] as $chr => $num) 178 | { 179 | if ($num > $data) 180 | { 181 | $result .= $chr; 182 | 183 | break; 184 | } 185 | 186 | $data -= $num; 187 | } 188 | } 189 | else 190 | { 191 | $data = $this->GetInt(0, $totalc - 1); 192 | if ($data === false) return false; 193 | 194 | foreach ($freqmap["consonants"] as $chr => $num) 195 | { 196 | if ($num > $data) 197 | { 198 | $result .= $chr; 199 | 200 | break; 201 | } 202 | 203 | $data -= $num; 204 | } 205 | } 206 | } 207 | 208 | return $result; 209 | } 210 | 211 | public function GenerateWord(&$freqmap, $len, $separator = "-") 212 | { 213 | $result = ""; 214 | $queue = array(); 215 | $threshold = $freqmap["threshold"]; 216 | $state = "start"; 217 | while ($len) 218 | { 219 | //echo $state . " - " . $len . ": " . $result . "\n"; 220 | switch ($state) 221 | { 222 | case "start": 223 | { 224 | // The start of the word (or restart). 225 | $path = &$freqmap["start"]; 226 | while (count($queue) < $threshold && $len) 227 | { 228 | if ($len > 1 || !$path["*"]) 229 | { 230 | // Some part of the word. 231 | $found = false; 232 | if ($path[""]) 233 | { 234 | $pos = $this->GetInt(0, $path[""] - 1); 235 | 236 | foreach ($path as $chr => &$info) 237 | { 238 | if (!is_array($info)) continue; 239 | 240 | if ($info["+"] > $pos) 241 | { 242 | $result .= $chr; 243 | $queue[] = $chr; 244 | $path = &$path[$chr]; 245 | $len--; 246 | 247 | $found = true; 248 | 249 | break; 250 | } 251 | 252 | $pos -= $info["+"]; 253 | } 254 | } 255 | 256 | if (!$found) 257 | { 258 | $state = (count($queue) ? "recovery" : "restart"); 259 | 260 | break; 261 | } 262 | } 263 | else 264 | { 265 | // Last letter of the word. 266 | $found = false; 267 | if ($path["*"]) 268 | { 269 | $pos = $this->GetInt(0, $path["*"] - 1); 270 | 271 | foreach ($path as $chr => &$info) 272 | { 273 | if (!is_array($info)) continue; 274 | 275 | if ($info["-"] > $pos) 276 | { 277 | $result .= $chr; 278 | $queue[] = $chr; 279 | $path = &$path[$chr]; 280 | $len--; 281 | 282 | $found = true; 283 | 284 | break; 285 | } 286 | 287 | $pos -= $info["-"]; 288 | } 289 | } 290 | 291 | if (!$found) 292 | { 293 | $state = (count($queue) ? "end" : "restart"); 294 | 295 | break; 296 | } 297 | } 298 | } 299 | 300 | if (count($queue) >= $threshold) $state = ($len >= $threshold ? "middle" : "end"); 301 | 302 | break; 303 | } 304 | case "middle": 305 | { 306 | // The middle of the word. 307 | $str = implode("", $queue); 308 | 309 | if (!isset($freqmap["middle"][$str])) $state = "recovery"; 310 | else 311 | { 312 | $found = false; 313 | 314 | if ($freqmap["middle"][$str][""]) 315 | { 316 | $pos = $this->GetInt(0, $freqmap["middle"][$str][""] - 1); 317 | 318 | foreach ($freqmap["middle"][$str] as $chr => $num) 319 | { 320 | if ($chr === "") continue; 321 | 322 | if ($num > $pos) 323 | { 324 | $result .= $chr; 325 | $queue[] = $chr; 326 | array_shift($queue); 327 | $len--; 328 | 329 | if ($len < $threshold) $state = "end"; 330 | 331 | $found = true; 332 | 333 | break; 334 | } 335 | 336 | $pos -= $num; 337 | } 338 | } 339 | 340 | if (!$found) $state = "recovery"; 341 | } 342 | 343 | break; 344 | } 345 | case "end": 346 | { 347 | if (!isset($freqmap["end"][$len]) || !count($queue) || !isset($freqmap["end"][$len][$queue[count($queue) - 1]])) $state = "restart"; 348 | else 349 | { 350 | $path = &$freqmap["end"][$len][$queue[count($queue) - 1]]; 351 | 352 | $found = false; 353 | 354 | if ($path[""]) 355 | { 356 | $pos = $this->GetInt(0, $path[""] - 1); 357 | 358 | foreach ($path as $str => $num) 359 | { 360 | if ($str === "") continue; 361 | 362 | if ($num > $pos) 363 | { 364 | $result .= $str; 365 | $len = 0; 366 | 367 | $found = true; 368 | 369 | break; 370 | } 371 | 372 | $pos -= $num; 373 | } 374 | } 375 | 376 | if (!$found) $state = "restart"; 377 | } 378 | 379 | break; 380 | } 381 | case "recovery": 382 | { 383 | if (!count($queue) || !isset($freqmap["recovery"][$queue[count($queue) - 1]])) $state = "restart"; 384 | else 385 | { 386 | $path = &$freqmap["recovery"][$queue[count($queue) - 1]]; 387 | 388 | $found = false; 389 | 390 | if ($path[""]) 391 | { 392 | $pos = $this->GetInt(0, $path[""] - 1); 393 | 394 | foreach ($path as $chr => $num) 395 | { 396 | if ($chr === "") continue; 397 | 398 | if ($num > $pos) 399 | { 400 | $result .= $chr; 401 | $queue[] = $chr; 402 | array_shift($queue); 403 | $len--; 404 | 405 | $state = ($len >= $threshold ? "middle" : "end"); 406 | 407 | $found = true; 408 | 409 | break; 410 | } 411 | 412 | $pos -= $num; 413 | } 414 | } 415 | 416 | if (!$found) $state = "restart"; 417 | } 418 | 419 | break; 420 | } 421 | case "restart": 422 | { 423 | $result .= $separator; 424 | $queue = array(); 425 | $len -= strlen($separator); 426 | 427 | $state = "start"; 428 | 429 | break; 430 | } 431 | } 432 | } 433 | 434 | return $result; 435 | } 436 | 437 | public function GetMode() 438 | { 439 | return $this->mode; 440 | } 441 | 442 | protected static function RNG_Translate() 443 | { 444 | $args = func_get_args(); 445 | if (!count($args)) return ""; 446 | 447 | return call_user_func_array((defined("CS_TRANSLATE_FUNC") && function_exists(CS_TRANSLATE_FUNC) ? CS_TRANSLATE_FUNC : "sprintf"), $args); 448 | } 449 | } 450 | ?> -------------------------------------------------------------------------------- /support/cli.php: -------------------------------------------------------------------------------- 1 | $val) 15 | { 16 | if (!isset($options["rules"][$val])) unset($options["shortmap"][$key]); 17 | } 18 | foreach ($options["rules"] as $key => $val) 19 | { 20 | if (!isset($val["arg"])) $options["rules"][$key]["arg"] = false; 21 | if (!isset($val["multiple"])) $options["rules"][$key]["multiple"] = false; 22 | } 23 | 24 | if ($args === false) $args = $_SERVER["argv"]; 25 | else if (is_string($args)) 26 | { 27 | $args2 = $args; 28 | $args = array(); 29 | $inside = false; 30 | $currarg = ""; 31 | $y = strlen($args2); 32 | for ($x = 0; $x < $y; $x++) 33 | { 34 | $currchr = substr($args2, $x, 1); 35 | 36 | if ($inside === false && $currchr == " " && $currarg != "") 37 | { 38 | $args[] = $currarg; 39 | $currarg = ""; 40 | } 41 | else if ($currchr == "\"" || $currchr == "'") 42 | { 43 | if ($inside === false) $inside = $currchr; 44 | else if ($inside === $currchr) $inside = false; 45 | else $currarg .= $currchr; 46 | } 47 | else if ($currchr == "\\" && $x < $y - 1) 48 | { 49 | $x++; 50 | $currarg .= substr($args2, $x, 1); 51 | } 52 | else if ($inside !== false || $currchr != " ") 53 | { 54 | $currarg .= $currchr; 55 | } 56 | } 57 | 58 | if ($currarg != "") $args[] = $currarg; 59 | } 60 | 61 | $result = array("success" => true, "file" => array_shift($args), "opts" => array(), "params" => array()); 62 | 63 | // Look over shortmap to determine if options exist that are one byte (flags) and don't have arguments. 64 | $chrs = array(); 65 | foreach ($options["shortmap"] as $key => $val) 66 | { 67 | if (isset($options["rules"][$val]) && !$options["rules"][$val]["arg"]) $chrs[$key] = true; 68 | } 69 | 70 | $allowopt = true; 71 | $y = count($args); 72 | for ($x = 0; $x < $y; $x++) 73 | { 74 | $arg = $args[$x]; 75 | 76 | // Attempt to process an option. 77 | $opt = false; 78 | $optval = false; 79 | if ($allowopt && substr($arg, 0, 1) == "-") 80 | { 81 | $pos = strpos($arg, "="); 82 | if ($pos === false) $pos = strlen($arg); 83 | else $optval = substr($arg, $pos + 1); 84 | $arg2 = substr($arg, 1, $pos - 1); 85 | 86 | if (isset($options["rules"][$arg2])) $opt = $arg2; 87 | else if (isset($options["shortmap"][$arg2])) $opt = $options["shortmap"][$arg2]; 88 | else if ($x == 0) 89 | { 90 | // Attempt to process as a set of flags. 91 | $y2 = strlen($arg2); 92 | if ($y2 > 0) 93 | { 94 | for ($x2 = 0; $x2 < $y2; $x2++) 95 | { 96 | $currchr = substr($arg2, $x2, 1); 97 | 98 | if (!isset($chrs[$currchr])) break; 99 | } 100 | 101 | if ($x2 == $y2) 102 | { 103 | for ($x2 = 0; $x2 < $y2; $x2++) 104 | { 105 | $opt = $options["shortmap"][substr($arg2, $x2, 1)]; 106 | 107 | if (!$options["rules"][$opt]["multiple"]) $result["opts"][$opt] = true; 108 | else 109 | { 110 | if (!isset($result["opts"][$opt])) $result["opts"][$opt] = 0; 111 | $result["opts"][$opt]++; 112 | } 113 | } 114 | 115 | continue; 116 | } 117 | } 118 | } 119 | } 120 | 121 | if ($opt === false) 122 | { 123 | // Is a parameter. 124 | if (substr($arg, 0, 1) === "\"" || substr($arg, 0, 1) === "'") $arg = substr($arg, 1); 125 | if (substr($arg, -1) === "\"" || substr($arg, -1) === "'") $arg = substr($arg, 0, -1); 126 | 127 | $result["params"][] = $arg; 128 | 129 | if (!$options["allow_opts_after_param"]) $allowopt = false; 130 | } 131 | else if (!$options["rules"][$opt]["arg"]) 132 | { 133 | // Is a flag by itself. 134 | if (!$options["rules"][$opt]["multiple"]) $result["opts"][$opt] = true; 135 | else 136 | { 137 | if (!isset($result["opts"][$opt])) $result["opts"][$opt] = 0; 138 | $result["opts"][$opt]++; 139 | } 140 | } 141 | else 142 | { 143 | // Is an option. 144 | if ($optval === false) 145 | { 146 | $x++; 147 | if ($x == $y) break; 148 | $optval = $args[$x]; 149 | } 150 | 151 | if (substr($optval, 0, 1) === "\"" || substr($optval, 0, 1) === "'") $optval = substr($optval, 1); 152 | if (substr($optval, -1) === "\"" || substr($optval, -1) === "'") $optval = substr($optval, 0, -1); 153 | 154 | if (!$options["rules"][$opt]["multiple"]) $result["opts"][$opt] = $optval; 155 | else 156 | { 157 | if (!isset($result["opts"][$opt])) $result["opts"][$opt] = array(); 158 | $result["opts"][$opt][] = $optval; 159 | } 160 | } 161 | } 162 | 163 | return $result; 164 | } 165 | 166 | public static function CanGetUserInputWithArgs(&$args, $prefix) 167 | { 168 | return (($prefix !== false && isset($args["opts"][$prefix]) && is_array($args["opts"][$prefix]) && count($args["opts"][$prefix])) || count($args["params"])); 169 | } 170 | 171 | // Gets a line of input from the user. If the user supplies all information via the command-line, this could be entirely automated. 172 | public static function GetUserInputWithArgs(&$args, $prefix, $question, $default, $noparamsoutput = "", $suppressoutput = false, $callback = false, $callbackopts = false) 173 | { 174 | if (!self::CanGetUserInputWithArgs($args, $prefix) && $noparamsoutput != "") 175 | { 176 | echo "\n" . rtrim($noparamsoutput) . "\n\n"; 177 | 178 | $suppressoutput = false; 179 | $noparamsoutput = ""; 180 | } 181 | 182 | do 183 | { 184 | $prompt = ($suppressoutput ? "" : $question . ($default !== false ? " [" . $default . "]" : "") . ": "); 185 | 186 | if ($prefix !== false && isset($args["opts"][$prefix]) && is_array($args["opts"][$prefix]) && count($args["opts"][$prefix])) 187 | { 188 | $line = array_shift($args["opts"][$prefix]); 189 | if ($line === "") $line = $default; 190 | if (!$suppressoutput) echo $prompt . $line . "\n"; 191 | } 192 | else if (count($args["params"])) 193 | { 194 | $line = array_shift($args["params"]); 195 | if ($line === "") $line = $default; 196 | if (!$suppressoutput) echo $prompt . $line . "\n"; 197 | } 198 | else if (strtoupper(substr(php_uname("s"), 0, 3)) != "WIN" && function_exists("readline") && function_exists("readline_add_history")) 199 | { 200 | $line = readline($prompt); 201 | if ($line === false) exit(); 202 | 203 | $line = trim($line); 204 | if ($line === "") $line = $default; 205 | if ($line !== false && $line !== "") readline_add_history($line); 206 | } 207 | else 208 | { 209 | echo $prompt; 210 | fflush(STDOUT); 211 | $line = fgets(STDIN); 212 | if ($line === false || ($line === "" && feof(STDIN))) exit(); 213 | 214 | $line = trim($line); 215 | if ($line === "") $line = $default; 216 | } 217 | 218 | if ($line === false || (is_callable($callback) && !call_user_func_array($callback, array($line, &$callbackopts)))) 219 | { 220 | if ($line !== false) $line = false; 221 | else echo "Please enter a value.\n"; 222 | 223 | if (!self::CanGetUserInputWithArgs($args, $prefix) && $noparamsoutput != "") 224 | { 225 | echo "\n" . $noparamsoutput . "\n"; 226 | 227 | $noparamsoutput = ""; 228 | } 229 | 230 | $suppressoutput = false; 231 | } 232 | } while ($line === false); 233 | 234 | return $line; 235 | } 236 | 237 | // Obtains a valid line of input. 238 | public static function GetLimitedUserInputWithArgs(&$args, $prefix, $question, $default, $allowedoptionsprefix, $allowedoptions, $loop = true, $suppressoutput = false, $multipleuntil = false) 239 | { 240 | $noparamsoutput = $allowedoptionsprefix . "\n\n"; 241 | $size = 0; 242 | foreach ($allowedoptions as $key => $val) 243 | { 244 | if ($size < strlen($key)) $size = strlen($key); 245 | } 246 | 247 | foreach ($allowedoptions as $key => $val) 248 | { 249 | $newtab = str_repeat(" ", 2 + $size + 3); 250 | $noparamsoutput .= " " . $key . ":" . str_repeat(" ", $size - strlen($key)) . " " . str_replace("\n\t", "\n" . $newtab, $val) . "\n"; 251 | } 252 | 253 | $noparamsoutput .= "\n"; 254 | 255 | if ($default === false && count($allowedoptions) == 1) 256 | { 257 | reset($allowedoptions); 258 | $default = key($allowedoptions); 259 | } 260 | 261 | $results = array(); 262 | do 263 | { 264 | $displayed = (!count($args["params"])); 265 | $result = self::GetUserInputWithArgs($args, $prefix, $question, $default, $noparamsoutput, $suppressoutput); 266 | if (is_array($multipleuntil) && $multipleuntil["exit"] === $result) break; 267 | $result2 = false; 268 | if (!count($allowedoptions)) break; 269 | foreach ($allowedoptions as $key => $val) 270 | { 271 | if (!strcasecmp($key, $result) || !strcasecmp($val, $result)) $result2 = $key; 272 | } 273 | if ($loop) 274 | { 275 | if ($result2 === false) 276 | { 277 | echo "Please select an option from the list.\n"; 278 | 279 | $suppressoutput = false; 280 | } 281 | else if (is_array($multipleuntil)) 282 | { 283 | $results[$result2] = $result2; 284 | 285 | $question = $multipleuntil["nextquestion"]; 286 | $default = $multipleuntil["nextdefault"]; 287 | } 288 | } 289 | 290 | if ($displayed) $noparamsoutput = ""; 291 | } while ($loop && ($result2 === false || is_array($multipleuntil))); 292 | 293 | return (is_array($multipleuntil) ? $results : $result2); 294 | } 295 | 296 | // Obtains Yes/No style input. 297 | public static function GetYesNoUserInputWithArgs(&$args, $prefix, $question, $default, $noparamsoutput = "", $suppressoutput = false) 298 | { 299 | $default = (substr(strtoupper(trim($default)), 0, 1) === "Y" ? "Y" : "N"); 300 | 301 | $result = self::GetUserInputWithArgs($args, $prefix, $question, $default, $noparamsoutput, $suppressoutput); 302 | $result = (substr(strtoupper(trim($result)), 0, 1) === "Y"); 303 | 304 | return $result; 305 | } 306 | 307 | public static function GetHexDump($data) 308 | { 309 | $result = ""; 310 | 311 | $x = 0; 312 | $y = strlen($data); 313 | if ($y <= 256) $padwidth = 2; 314 | else if ($y <= 65536) $padwidth = 4; 315 | else if ($y <= 16777216) $padwidth = 6; 316 | else $padwidth = 8; 317 | 318 | $pad = str_repeat(" ", $padwidth); 319 | 320 | $data2 = str_split(strtoupper(bin2hex($data)), 32); 321 | foreach ($data2 as $line) 322 | { 323 | $result .= sprintf("%0" . $padwidth . "X", $x) . " | "; 324 | 325 | $line = str_split($line, 2); 326 | array_splice($line, 8, 0, ""); 327 | $result .= implode(" ", $line) . "\n"; 328 | 329 | $result .= $pad . " |"; 330 | $y2 = $x + 16; 331 | for ($x2 = 0; $x2 < 16 && $x < $y; $x2++) 332 | { 333 | $result .= " "; 334 | if ($x2 === 8) $result .= " "; 335 | 336 | $tempchr = ord($data[$x]); 337 | if ($tempchr === 0x09) $result .= "\\t"; 338 | else if ($tempchr === 0x0D) $result .= "\\r"; 339 | else if ($tempchr === 0x0A) $result .= "\\n"; 340 | else if ($tempchr === 0x00) $result .= "\\0"; 341 | else if ($tempchr < 32 || $tempchr > 126) $result .= " "; 342 | else $result .= " " . $data[$x]; 343 | 344 | $x++; 345 | } 346 | 347 | $result .= "\n"; 348 | } 349 | 350 | return $result; 351 | } 352 | 353 | // Outputs a JSON array (useful for captured output). 354 | public static function DisplayResult($result, $exit = true) 355 | { 356 | if (is_array($result)) echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"; 357 | else echo $result . "\n"; 358 | 359 | if ($exit) exit(); 360 | } 361 | 362 | // Useful for reparsing remaining parameters as new arguments. 363 | public static function ReinitArgs(&$args, $newargs) 364 | { 365 | // Process the parameters. 366 | $options = array( 367 | "shortmap" => array( 368 | "?" => "help" 369 | ), 370 | "rules" => array( 371 | ) 372 | ); 373 | 374 | foreach ($newargs as $arg) $options["rules"][$arg] = array("arg" => true, "multiple" => true); 375 | $options["rules"]["help"] = array("arg" => false); 376 | 377 | $args = self::ParseCommandLine($options, array_merge(array(""), $args["params"])); 378 | 379 | if (isset($args["opts"]["help"])) self::DisplayResult(array("success" => true, "options" => array_keys($options["rules"]))); 380 | } 381 | 382 | // Tracks messages for a command-line interface app. 383 | private static $messages = array(); 384 | 385 | public static function LogMessage($msg, $data = null) 386 | { 387 | if (isset($data)) $msg .= "\n\t" . trim(str_replace("\n", "\n\t", json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES))); 388 | 389 | self::$messages[] = $msg; 390 | 391 | fwrite(STDERR, $msg . "\n"); 392 | } 393 | 394 | public static function DisplayError($msg, $result = false, $exit = true) 395 | { 396 | self::LogMessage(($exit ? "[Error] " : "") . $msg); 397 | 398 | if ($result !== false && is_array($result) && isset($result["error"]) && isset($result["errorcode"])) self::LogMessage("[Error] " . $result["error"] . " (" . $result["errorcode"] . ")", (isset($result["info"]) ? $result["info"] : null)); 399 | 400 | if ($exit) exit(); 401 | } 402 | 403 | public static function GetLogMessages($filters = array()) 404 | { 405 | if (is_string($filters)) $filters = array($filters); 406 | 407 | $result = array(); 408 | foreach (self::$messages as $message) 409 | { 410 | $found = (!count($filters)); 411 | foreach ($filters as $filter) 412 | { 413 | if (preg_match($filter, $message)) $found = true; 414 | } 415 | 416 | if ($found) $result[] = $message; 417 | } 418 | 419 | return $result; 420 | } 421 | 422 | public static function ResetLogMessages() 423 | { 424 | self::$messages = array(); 425 | } 426 | 427 | 428 | private static $timerinfo = array(); 429 | 430 | public static function StartTimer() 431 | { 432 | $ts = microtime(true); 433 | 434 | self::$timerinfo = array( 435 | "start" => $ts, 436 | "diff" => $ts 437 | ); 438 | } 439 | 440 | public static function UpdateTimer() 441 | { 442 | $ts = microtime(true); 443 | $diff = $ts - self::$timerinfo["diff"]; 444 | self::$timerinfo["diff"] = $ts; 445 | 446 | $result = array( 447 | "success" => true, 448 | "diff" => sprintf("%.2f", $diff), 449 | "total" => sprintf("%.2f", $ts - self::$timerinfo["start"]) 450 | ); 451 | 452 | return $result; 453 | } 454 | } 455 | ?> -------------------------------------------------------------------------------- /support/utf_utils.php: -------------------------------------------------------------------------------- 1 | = 0x0300 && $val <= 0x036F) || ($val >= 0x1DC0 && $val <= 0x1DFF) || ($val >= 0x20D0 && $val <= 0x20FF) || ($val >= 0xFE20 && $val <= 0xFE2F)); 21 | } 22 | 23 | public static function Convert($data, $srctype, $desttype) 24 | { 25 | $arr = is_array($data); 26 | if ($arr) $srctype = self::UTF32_ARRAY; 27 | $x = 0; 28 | $y = ($arr ? count($data) : strlen($data)); 29 | $result = ($desttype === self::UTF32_ARRAY ? array() : ""); 30 | if (!$arr && $srctype === self::UTF32_ARRAY) return $result; 31 | 32 | $first = true; 33 | 34 | if ($srctype === self::UTF8_BOM) 35 | { 36 | if (substr($data, 0, 3) === "\xEF\xBB\xBF") $x = 3; 37 | 38 | $srctype = self::UTF8; 39 | } 40 | 41 | if ($srctype === self::UTF16_BOM) 42 | { 43 | if (substr($data, 0, 2) === "\xFE\xFF") 44 | { 45 | $srctype = self::UTF16_BE; 46 | $x = 2; 47 | } 48 | else if (substr($data, 0, 2) === "\xFF\xFE") 49 | { 50 | $srctype = self::UTF16_LE; 51 | $x = 2; 52 | } 53 | else 54 | { 55 | $srctype = self::UTF16_LE; 56 | } 57 | } 58 | 59 | if ($srctype === self::UTF32_BOM) 60 | { 61 | if (substr($data, 0, 4) === "\x00\x00\xFE\xFF") 62 | { 63 | $srctype = self::UTF32_BE; 64 | $x = 4; 65 | } 66 | else if (substr($data, 0, 4) === "\xFF\xFE\x00\x00") 67 | { 68 | $srctype = self::UTF32_LE; 69 | $x = 4; 70 | } 71 | else 72 | { 73 | $srctype = self::UTF32_LE; 74 | } 75 | } 76 | 77 | while ($x < $y) 78 | { 79 | // Read the next valid code point. 80 | $val = false; 81 | 82 | switch ($srctype) 83 | { 84 | case self::UTF8: 85 | { 86 | $tempchr = ord($data[$x]); 87 | if ($tempchr <= 0x7F) 88 | { 89 | $val = $tempchr; 90 | $x++; 91 | } 92 | else if ($tempchr < 0xC2) $x++; 93 | else 94 | { 95 | $left = $y - $x; 96 | if ($left < 2) $x++; 97 | else 98 | { 99 | $tempchr2 = ord($data[$x + 1]); 100 | 101 | if (($tempchr >= 0xC2 && $tempchr <= 0xDF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) 102 | { 103 | $val = (($tempchr & 0x1F) << 6) | ($tempchr2 & 0x3F); 104 | $x += 2; 105 | } 106 | else if ($left < 3) $x++; 107 | else 108 | { 109 | $tempchr3 = ord($data[$x + 2]); 110 | 111 | if ($tempchr3 < 0x80 || $tempchr3 > 0xBF) $x++; 112 | else 113 | { 114 | if (($tempchr == 0xE0 && ($tempchr2 >= 0xA0 && $tempchr2 <= 0xBF)) || ((($tempchr >= 0xE1 && $tempchr <= 0xEC) || $tempchr == 0xEE || $tempchr == 0xEF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) || ($tempchr == 0xED && ($tempchr2 >= 0x80 && $tempchr2 <= 0x9F))) 115 | { 116 | $val = (($tempchr & 0x0F) << 12) | (($tempchr2 & 0x3F) << 6) | ($tempchr3 & 0x3F); 117 | $x += 3; 118 | } 119 | else if ($left < 4) $x++; 120 | else 121 | { 122 | $tempchr4 = ord($data[$x + 3]); 123 | 124 | if ($tempchr4 < 0x80 || $tempchr4 > 0xBF) $x++; 125 | else if (($tempchr == 0xF0 && ($tempchr2 >= 0x90 && $tempchr2 <= 0xBF)) || (($tempchr >= 0xF1 && $tempchr <= 0xF3) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) || ($tempchr == 0xF4 && ($tempchr2 >= 0x80 && $tempchr2 <= 0x8F))) 126 | { 127 | $val = (($tempchr & 0x07) << 18) | (($tempchr2 & 0x3F) << 12) | (($tempchr3 & 0x3F) << 6) | ($tempchr4 & 0x3F); 128 | $x += 4; 129 | } 130 | else 131 | { 132 | $x++; 133 | } 134 | } 135 | } 136 | } 137 | } 138 | } 139 | 140 | break; 141 | } 142 | case self::UTF16_LE: 143 | { 144 | if ($x + 1 >= $y) $x = $y; 145 | else 146 | { 147 | $val = unpack("v", substr($data, $x, 2))[1]; 148 | $x += 2; 149 | 150 | if ($val >= 0xD800 && $val <= 0xDBFF) 151 | { 152 | if ($x + 1 >= $y) 153 | { 154 | $x = $y; 155 | $val = false; 156 | } 157 | else 158 | { 159 | $val2 = unpack("v", substr($data, $x, 2))[1]; 160 | 161 | if ($val2 < 0xDC00 || $val2 > 0xDFFF) $val = false; 162 | else 163 | { 164 | $val = ((($val - 0xD800) << 10) | ($val2 - 0xDC00)) + 0x10000; 165 | $x += 2; 166 | } 167 | } 168 | } 169 | } 170 | 171 | break; 172 | } 173 | case self::UTF16_BE: 174 | { 175 | if ($x + 1 >= $y) $x = $y; 176 | else 177 | { 178 | $val = unpack("n", substr($data, $x, 2))[1]; 179 | $x += 2; 180 | 181 | if ($val >= 0xD800 && $val <= 0xDBFF) 182 | { 183 | if ($x + 1 >= $y) 184 | { 185 | $x = $y; 186 | $val = false; 187 | } 188 | else 189 | { 190 | $val2 = unpack("n", substr($data, $x, 2))[1]; 191 | 192 | if ($val2 < 0xDC00 || $val2 > 0xDFFF) $val = false; 193 | else 194 | { 195 | $val = ((($val - 0xD800) << 10) | ($val2 - 0xDC00)) + 0x10000; 196 | $x += 2; 197 | } 198 | } 199 | } 200 | } 201 | 202 | break; 203 | } 204 | case self::UTF32_LE: 205 | { 206 | if ($x + 3 >= $y) $x = $y; 207 | else 208 | { 209 | $val = unpack("V", substr($data, $x, 4))[1]; 210 | $x += 4; 211 | } 212 | 213 | break; 214 | } 215 | case self::UTF32_BE: 216 | { 217 | if ($x + 3 >= $y) $x = $y; 218 | else 219 | { 220 | $val = unpack("N", substr($data, $x, 4))[1]; 221 | $x += 4; 222 | } 223 | 224 | break; 225 | } 226 | case self::UTF32_ARRAY: 227 | { 228 | $val = (int)$data[$x]; 229 | $x++; 230 | 231 | break; 232 | } 233 | default: $x = $y; break; 234 | } 235 | 236 | // Make sure it is a valid Unicode value. 237 | // 0xD800-0xDFFF are for UTF-16 surrogate pairs. Invalid characters. 238 | // 0xFDD0-0xFDEF are non-characters. 239 | // 0x*FFFE and 0x*FFFF are reserved. 240 | // The largest possible character is 0x10FFFF. 241 | // First character can't be a combining code point. 242 | if ($val !== false && !($val < 0 || ($val >= 0xD800 && $val <= 0xDFFF) || ($val >= 0xFDD0 && $val <= 0xFDEF) || ($val & 0xFFFE) == 0xFFFE || $val > 0x10FFFF || ($first && self::IsCombiningCodePoint($val)))) 243 | { 244 | if ($first) 245 | { 246 | if ($desttype === self::UTF8_BOM) 247 | { 248 | $result .= "\xEF\xBB\xBF"; 249 | 250 | $desttype = self::UTF8; 251 | } 252 | 253 | if ($desttype === self::UTF16_BOM) 254 | { 255 | $result .= "\xFF\xFE"; 256 | 257 | $desttype = self::UTF16_LE; 258 | } 259 | 260 | if ($srctype === self::UTF32_BOM) 261 | { 262 | $result .= "\xFF\xFE\x00\x00"; 263 | 264 | $desttype = self::UTF32_LE; 265 | } 266 | 267 | $first = false; 268 | } 269 | 270 | switch ($desttype) 271 | { 272 | case self::UTF8: 273 | { 274 | if ($val <= 0x7F) $result .= chr($val); 275 | else if ($val <= 0x7FF) $result .= chr(0xC0 | ($val >> 6)) . chr(0x80 | ($val & 0x3F)); 276 | else if ($val <= 0xFFFF) $result .= chr(0xE0 | ($val >> 12)) . chr(0x80 | (($val >> 6) & 0x3F)) . chr(0x80 | ($val & 0x3F)); 277 | else if ($val <= 0x10FFFF) $result .= chr(0xF0 | ($val >> 18)) . chr(0x80 | (($val >> 12) & 0x3F)) . chr(0x80 | (($val >> 6) & 0x3F)) . chr(0x80 | ($val & 0x3F)); 278 | 279 | break; 280 | } 281 | case self::UTF16_LE: 282 | { 283 | if ($val <= 0xFFFF) $result .= pack("v", $val); 284 | else 285 | { 286 | $val -= 0x10000; 287 | $result .= pack("v", ((($val >> 10) & 0x3FF) + 0xD800)); 288 | $result .= pack("v", (($val & 0x3FF) + 0xDC00)); 289 | } 290 | 291 | break; 292 | } 293 | case self::UTF16_BE: 294 | { 295 | if ($val <= 0xFFFF) $result .= pack("n", $val); 296 | else 297 | { 298 | $val -= 0x10000; 299 | $result .= pack("n", ((($val >> 10) & 0x3FF) + 0xD800)); 300 | $result .= pack("n", (($val & 0x3FF) + 0xDC00)); 301 | } 302 | 303 | break; 304 | } 305 | case self::UTF32_LE: 306 | { 307 | $result .= pack("V", $val); 308 | 309 | break; 310 | } 311 | case self::UTF32_BE: 312 | { 313 | $result .= pack("N", $val); 314 | 315 | break; 316 | } 317 | case self::UTF32_ARRAY: 318 | { 319 | $result[] = $val; 320 | 321 | break; 322 | } 323 | default: $x = $y; break; 324 | } 325 | } 326 | } 327 | 328 | return $result; 329 | } 330 | 331 | 332 | protected const PUNYCODE_BASE = 36; 333 | protected const PUNYCODE_TMIN = 1; 334 | protected const PUNYCODE_TMAX = 26; 335 | protected const PUNYCODE_SKEW = 38; 336 | protected const PUNYCODE_DAMP = 700; 337 | protected const PUNYCODE_INITIAL_BIAS = 72; 338 | protected const PUNYCODE_INITIAL_N = 0x80; 339 | protected const PUNYCODE_DIGIT_MAP = "abcdefghijklmnopqrstuvwxyz0123456789"; 340 | 341 | public static function ConvertToPunycode($domain) 342 | { 343 | // Reject invalid domain name lengths. 344 | if (strlen($domain) > 255) return false; 345 | 346 | $parts = explode(".", $domain); 347 | 348 | foreach ($parts as $num => $part) 349 | { 350 | // Reject invalid label lengths. 351 | $y = strlen($part); 352 | if ($y > 63) return false; 353 | 354 | // Skip already encoded portions. 355 | if (substr($part, 0, 4) === "xn--") continue; 356 | 357 | // Convert UTF-8 to UTF-32 code points. 358 | $data = self::Convert($part, self::UTF8, self::UTF32_ARRAY); 359 | 360 | // Handle ASCII code points. 361 | $part2 = ""; 362 | foreach ($data as $cp) 363 | { 364 | if ($cp <= 0x7F) $part2 .= strtolower(chr($cp)); 365 | } 366 | 367 | $numhandled = strlen($part2); 368 | $y = count($data); 369 | 370 | if ($numhandled >= $y) 371 | { 372 | $parts[$num] = $part2; 373 | 374 | continue; 375 | } 376 | 377 | if ($numhandled) $part2 .= "-"; 378 | 379 | $part2 = "xn--" . $part2; 380 | 381 | if (strlen($part2) > 63) return false; 382 | 383 | $bias = self::PUNYCODE_INITIAL_BIAS; 384 | $n = self::PUNYCODE_INITIAL_N; 385 | $delta = 0; 386 | $first = true; 387 | 388 | while ($numhandled < $y) 389 | { 390 | // Find the next largest unhandled code point. 391 | $cp2 = 0x01000000; 392 | foreach ($data as $cp) 393 | { 394 | if ($cp >= $n && $cp2 > $cp) $cp2 = $cp; 395 | } 396 | 397 | // Increase delta but prevent overflow. 398 | $delta += ($cp2 - $n) * ($numhandled + 1); 399 | if ($delta < 0) return false; 400 | $n = $cp2; 401 | 402 | foreach ($data as $cp) 403 | { 404 | if ($cp < $n) 405 | { 406 | $delta++; 407 | 408 | if ($delta < 0) return false; 409 | } 410 | else if ($cp === $n) 411 | { 412 | // Calculate and encode a variable length integer from the delta. 413 | $q = $delta; 414 | $x = 0; 415 | do 416 | { 417 | $x += self::PUNYCODE_BASE; 418 | 419 | if ($x <= $bias) $t = self::PUNYCODE_TMIN; 420 | else if ($x >= $bias + self::PUNYCODE_TMAX) $t = self::PUNYCODE_TMAX; 421 | else $t = $x - $bias; 422 | 423 | if ($q < $t) break; 424 | 425 | $part2 .= self::PUNYCODE_DIGIT_MAP[$t + (($q - $t) % (self::PUNYCODE_BASE - $t))]; 426 | 427 | $q = (int)(($q - $t) / (self::PUNYCODE_BASE - $t)); 428 | 429 | if (strlen($part2) > 63) return false; 430 | } while (1); 431 | 432 | $part2 .= self::PUNYCODE_DIGIT_MAP[$q]; 433 | if (strlen($part2) > 63) return false; 434 | 435 | // Adapt bias. 436 | $numhandled++; 437 | $bias = self::InternalPunycodeAdapt($delta, $numhandled, $first); 438 | $delta = 0; 439 | $first = false; 440 | } 441 | } 442 | 443 | $delta++; 444 | $n++; 445 | } 446 | 447 | $parts[$num] = $part2; 448 | } 449 | 450 | return implode(".", $parts); 451 | } 452 | 453 | public static function ConvertFromPunycode($domain) 454 | { 455 | // Reject invalid domain name lengths. 456 | if (strlen($domain) > 255) return false; 457 | 458 | $parts = explode(".", $domain); 459 | 460 | foreach ($parts as $num => $part) 461 | { 462 | // Reject invalid label lengths. 463 | $y = strlen($part); 464 | if ($y > 63) return false; 465 | 466 | // Skip unencoded portions. 467 | if (substr($part, 0, 4) !== "xn--") continue; 468 | 469 | $part = substr($part, 4); 470 | 471 | // Convert UTF-8 to UTF-32 code points. 472 | $data = self::Convert($part, self::UTF8, self::UTF32_ARRAY); 473 | 474 | // Handle ASCII code points. 475 | $hyphen = ord("-"); 476 | for ($x = count($data); $x && $data[$x - 1] !== $hyphen; $x--); 477 | if (!$x) $data2 = array(); 478 | else 479 | { 480 | $data2 = array_splice($data, 0, $x - 1); 481 | 482 | array_shift($data); 483 | } 484 | 485 | $numhandled = count($data2); 486 | 487 | $bias = self::PUNYCODE_INITIAL_BIAS; 488 | $n = self::PUNYCODE_INITIAL_N; 489 | $delta = 0; 490 | $first = true; 491 | 492 | $pos = 0; 493 | $y = count($data); 494 | while ($pos < $y) 495 | { 496 | // Calculate and decode a delta from the variable length integer. 497 | $olddelta = $delta; 498 | $w = 1; 499 | $x = 0; 500 | do 501 | { 502 | $x += self::PUNYCODE_BASE; 503 | 504 | $cp = $data[$pos]; 505 | $pos++; 506 | 507 | if ($cp >= ord("a") && $cp <= ord("z")) $digit = $cp - ord("a"); 508 | else if ($cp >= ord("A") && $cp <= ord("Z")) $digit = $cp - ord("A"); 509 | else if ($cp >= ord("0") && $cp <= ord("9")) $digit = $cp - ord("0") + 26; 510 | else return false; 511 | 512 | $delta += $digit * $w; 513 | if ($delta < 0) return false; 514 | 515 | if ($x <= $bias) $t = self::PUNYCODE_TMIN; 516 | else if ($x >= $bias + self::PUNYCODE_TMAX) $t = self::PUNYCODE_TMAX; 517 | else $t = $x - $bias; 518 | 519 | if ($digit < $t) break; 520 | 521 | $w *= (self::PUNYCODE_BASE - $t); 522 | if ($w < 0) return false; 523 | } while (1); 524 | 525 | // Adapt bias. 526 | $numhandled++; 527 | $bias = self::InternalPunycodeAdapt($delta - $olddelta, $numhandled, $first); 528 | $first = false; 529 | 530 | // Delta was supposed to wrap around from $numhandled to 0, incrementing $n each time, so fix that now. 531 | $n += (int)($delta / $numhandled); 532 | $delta %= $numhandled; 533 | 534 | // Insert $n (the code point) at the delta position. 535 | array_splice($data2, $delta, 0, array($n)); 536 | $delta++; 537 | } 538 | 539 | $parts[$num] = self::Convert($data2, self::UTF32_ARRAY, self::UTF8); 540 | } 541 | 542 | return implode(".", $parts); 543 | } 544 | 545 | // RFC3492 adapt() function. 546 | protected static function InternalPunycodeAdapt($delta, $numpoints, $first) 547 | { 548 | $delta = ($first ? (int)($delta / self::PUNYCODE_DAMP) : $delta >> 1); 549 | $delta += (int)($delta / $numpoints); 550 | 551 | $y = self::PUNYCODE_BASE - self::PUNYCODE_TMIN; 552 | 553 | $condval = (int)(($y * self::PUNYCODE_TMAX) / 2); 554 | for ($x = 0; $delta > $condval; $x += self::PUNYCODE_BASE) $delta = (int)($delta / $y); 555 | 556 | return (int)($x + ((($y + 1) * $delta) / ($delta + self::PUNYCODE_SKEW))); 557 | } 558 | } 559 | ?> -------------------------------------------------------------------------------- /sdks/php/utf_utils.php: -------------------------------------------------------------------------------- 1 | = 0x0300 && $val <= 0x036F) || ($val >= 0x1DC0 && $val <= 0x1DFF) || ($val >= 0x20D0 && $val <= 0x20FF) || ($val >= 0xFE20 && $val <= 0xFE2F)); 21 | } 22 | 23 | public static function Convert($data, $srctype, $desttype) 24 | { 25 | $arr = is_array($data); 26 | if ($arr) $srctype = self::UTF32_ARRAY; 27 | $x = 0; 28 | $y = ($arr ? count($data) : strlen($data)); 29 | $result = ($desttype === self::UTF32_ARRAY ? array() : ""); 30 | if (!$arr && $srctype === self::UTF32_ARRAY) return $result; 31 | 32 | $first = true; 33 | 34 | if ($srctype === self::UTF8_BOM) 35 | { 36 | if (substr($data, 0, 3) === "\xEF\xBB\xBF") $x = 3; 37 | 38 | $srctype = self::UTF8; 39 | } 40 | 41 | if ($srctype === self::UTF16_BOM) 42 | { 43 | if (substr($data, 0, 2) === "\xFE\xFF") 44 | { 45 | $srctype = self::UTF16_BE; 46 | $x = 2; 47 | } 48 | else if (substr($data, 0, 2) === "\xFF\xFE") 49 | { 50 | $srctype = self::UTF16_LE; 51 | $x = 2; 52 | } 53 | else 54 | { 55 | $srctype = self::UTF16_LE; 56 | } 57 | } 58 | 59 | if ($srctype === self::UTF32_BOM) 60 | { 61 | if (substr($data, 0, 4) === "\x00\x00\xFE\xFF") 62 | { 63 | $srctype = self::UTF32_BE; 64 | $x = 4; 65 | } 66 | else if (substr($data, 0, 4) === "\xFF\xFE\x00\x00") 67 | { 68 | $srctype = self::UTF32_LE; 69 | $x = 4; 70 | } 71 | else 72 | { 73 | $srctype = self::UTF32_LE; 74 | } 75 | } 76 | 77 | while ($x < $y) 78 | { 79 | // Read the next valid code point. 80 | $val = false; 81 | 82 | switch ($srctype) 83 | { 84 | case self::UTF8: 85 | { 86 | $tempchr = ord($data[$x]); 87 | if ($tempchr <= 0x7F) 88 | { 89 | $val = $tempchr; 90 | $x++; 91 | } 92 | else if ($tempchr < 0xC2) $x++; 93 | else 94 | { 95 | $left = $y - $x; 96 | if ($left < 2) $x++; 97 | else 98 | { 99 | $tempchr2 = ord($data[$x + 1]); 100 | 101 | if (($tempchr >= 0xC2 && $tempchr <= 0xDF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) 102 | { 103 | $val = (($tempchr & 0x1F) << 6) | ($tempchr2 & 0x3F); 104 | $x += 2; 105 | } 106 | else if ($left < 3) $x++; 107 | else 108 | { 109 | $tempchr3 = ord($data[$x + 2]); 110 | 111 | if ($tempchr3 < 0x80 || $tempchr3 > 0xBF) $x++; 112 | else 113 | { 114 | if (($tempchr == 0xE0 && ($tempchr2 >= 0xA0 && $tempchr2 <= 0xBF)) || ((($tempchr >= 0xE1 && $tempchr <= 0xEC) || $tempchr == 0xEE || $tempchr == 0xEF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) || ($tempchr == 0xED && ($tempchr2 >= 0x80 && $tempchr2 <= 0x9F))) 115 | { 116 | $val = (($tempchr & 0x0F) << 12) | (($tempchr2 & 0x3F) << 6) | ($tempchr3 & 0x3F); 117 | $x += 3; 118 | } 119 | else if ($left < 4) $x++; 120 | else 121 | { 122 | $tempchr4 = ord($data[$x + 3]); 123 | 124 | if ($tempchr4 < 0x80 || $tempchr4 > 0xBF) $x++; 125 | else if (($tempchr == 0xF0 && ($tempchr2 >= 0x90 && $tempchr2 <= 0xBF)) || (($tempchr >= 0xF1 && $tempchr <= 0xF3) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) || ($tempchr == 0xF4 && ($tempchr2 >= 0x80 && $tempchr2 <= 0x8F))) 126 | { 127 | $val = (($tempchr & 0x07) << 18) | (($tempchr2 & 0x3F) << 12) | (($tempchr3 & 0x3F) << 6) | ($tempchr4 & 0x3F); 128 | $x += 4; 129 | } 130 | else 131 | { 132 | $x++; 133 | } 134 | } 135 | } 136 | } 137 | } 138 | } 139 | 140 | break; 141 | } 142 | case self::UTF16_LE: 143 | { 144 | if ($x + 1 >= $y) $x = $y; 145 | else 146 | { 147 | $val = unpack("v", substr($data, $x, 2))[1]; 148 | $x += 2; 149 | 150 | if ($val >= 0xD800 && $val <= 0xDBFF) 151 | { 152 | if ($x + 1 >= $y) 153 | { 154 | $x = $y; 155 | $val = false; 156 | } 157 | else 158 | { 159 | $val2 = unpack("v", substr($data, $x, 2))[1]; 160 | 161 | if ($val2 < 0xDC00 || $val2 > 0xDFFF) $val = false; 162 | else 163 | { 164 | $val = ((($val - 0xD800) << 10) | ($val2 - 0xDC00)) + 0x10000; 165 | $x += 2; 166 | } 167 | } 168 | } 169 | } 170 | 171 | break; 172 | } 173 | case self::UTF16_BE: 174 | { 175 | if ($x + 1 >= $y) $x = $y; 176 | else 177 | { 178 | $val = unpack("n", substr($data, $x, 2))[1]; 179 | $x += 2; 180 | 181 | if ($val >= 0xD800 && $val <= 0xDBFF) 182 | { 183 | if ($x + 1 >= $y) 184 | { 185 | $x = $y; 186 | $val = false; 187 | } 188 | else 189 | { 190 | $val2 = unpack("n", substr($data, $x, 2))[1]; 191 | 192 | if ($val2 < 0xDC00 || $val2 > 0xDFFF) $val = false; 193 | else 194 | { 195 | $val = ((($val - 0xD800) << 10) | ($val2 - 0xDC00)) + 0x10000; 196 | $x += 2; 197 | } 198 | } 199 | } 200 | } 201 | 202 | break; 203 | } 204 | case self::UTF32_LE: 205 | { 206 | if ($x + 3 >= $y) $x = $y; 207 | else 208 | { 209 | $val = unpack("V", substr($data, $x, 4))[1]; 210 | $x += 4; 211 | } 212 | 213 | break; 214 | } 215 | case self::UTF32_BE: 216 | { 217 | if ($x + 3 >= $y) $x = $y; 218 | else 219 | { 220 | $val = unpack("N", substr($data, $x, 4))[1]; 221 | $x += 4; 222 | } 223 | 224 | break; 225 | } 226 | case self::UTF32_ARRAY: 227 | { 228 | $val = (int)$data[$x]; 229 | $x++; 230 | 231 | break; 232 | } 233 | default: $x = $y; break; 234 | } 235 | 236 | // Make sure it is a valid Unicode value. 237 | // 0xD800-0xDFFF are for UTF-16 surrogate pairs. Invalid characters. 238 | // 0xFDD0-0xFDEF are non-characters. 239 | // 0x*FFFE and 0x*FFFF are reserved. 240 | // The largest possible character is 0x10FFFF. 241 | // First character can't be a combining code point. 242 | if ($val !== false && !($val < 0 || ($val >= 0xD800 && $val <= 0xDFFF) || ($val >= 0xFDD0 && $val <= 0xFDEF) || ($val & 0xFFFE) == 0xFFFE || $val > 0x10FFFF || ($first && self::IsCombiningCodePoint($val)))) 243 | { 244 | if ($first) 245 | { 246 | if ($desttype === self::UTF8_BOM) 247 | { 248 | $result .= "\xEF\xBB\xBF"; 249 | 250 | $desttype = self::UTF8; 251 | } 252 | 253 | if ($desttype === self::UTF16_BOM) 254 | { 255 | $result .= "\xFF\xFE"; 256 | 257 | $desttype = self::UTF16_LE; 258 | } 259 | 260 | if ($srctype === self::UTF32_BOM) 261 | { 262 | $result .= "\xFF\xFE\x00\x00"; 263 | 264 | $desttype = self::UTF32_LE; 265 | } 266 | 267 | $first = false; 268 | } 269 | 270 | switch ($desttype) 271 | { 272 | case self::UTF8: 273 | { 274 | if ($val <= 0x7F) $result .= chr($val); 275 | else if ($val <= 0x7FF) $result .= chr(0xC0 | ($val >> 6)) . chr(0x80 | ($val & 0x3F)); 276 | else if ($val <= 0xFFFF) $result .= chr(0xE0 | ($val >> 12)) . chr(0x80 | (($val >> 6) & 0x3F)) . chr(0x80 | ($val & 0x3F)); 277 | else if ($val <= 0x10FFFF) $result .= chr(0xF0 | ($val >> 18)) . chr(0x80 | (($val >> 12) & 0x3F)) . chr(0x80 | (($val >> 6) & 0x3F)) . chr(0x80 | ($val & 0x3F)); 278 | 279 | break; 280 | } 281 | case self::UTF16_LE: 282 | { 283 | if ($val <= 0xFFFF) $result .= pack("v", $val); 284 | else 285 | { 286 | $val -= 0x10000; 287 | $result .= pack("v", ((($val >> 10) & 0x3FF) + 0xD800)); 288 | $result .= pack("v", (($val & 0x3FF) + 0xDC00)); 289 | } 290 | 291 | break; 292 | } 293 | case self::UTF16_BE: 294 | { 295 | if ($val <= 0xFFFF) $result .= pack("n", $val); 296 | else 297 | { 298 | $val -= 0x10000; 299 | $result .= pack("n", ((($val >> 10) & 0x3FF) + 0xD800)); 300 | $result .= pack("n", (($val & 0x3FF) + 0xDC00)); 301 | } 302 | 303 | break; 304 | } 305 | case self::UTF32_LE: 306 | { 307 | $result .= pack("V", $val); 308 | 309 | break; 310 | } 311 | case self::UTF32_BE: 312 | { 313 | $result .= pack("N", $val); 314 | 315 | break; 316 | } 317 | case self::UTF32_ARRAY: 318 | { 319 | $result[] = $val; 320 | 321 | break; 322 | } 323 | default: $x = $y; break; 324 | } 325 | } 326 | } 327 | 328 | return $result; 329 | } 330 | 331 | 332 | protected const PUNYCODE_BASE = 36; 333 | protected const PUNYCODE_TMIN = 1; 334 | protected const PUNYCODE_TMAX = 26; 335 | protected const PUNYCODE_SKEW = 38; 336 | protected const PUNYCODE_DAMP = 700; 337 | protected const PUNYCODE_INITIAL_BIAS = 72; 338 | protected const PUNYCODE_INITIAL_N = 0x80; 339 | protected const PUNYCODE_DIGIT_MAP = "abcdefghijklmnopqrstuvwxyz0123456789"; 340 | 341 | public static function ConvertToPunycode($domain) 342 | { 343 | // Reject invalid domain name lengths. 344 | if (strlen($domain) > 255) return false; 345 | 346 | $parts = explode(".", $domain); 347 | 348 | foreach ($parts as $num => $part) 349 | { 350 | // Reject invalid label lengths. 351 | $y = strlen($part); 352 | if ($y > 63) return false; 353 | 354 | // Skip already encoded portions. 355 | if (substr($part, 0, 4) === "xn--") continue; 356 | 357 | // Convert UTF-8 to UTF-32 code points. 358 | $data = self::Convert($part, self::UTF8, self::UTF32_ARRAY); 359 | 360 | // Handle ASCII code points. 361 | $part2 = ""; 362 | foreach ($data as $cp) 363 | { 364 | if ($cp <= 0x7F) $part2 .= strtolower(chr($cp)); 365 | } 366 | 367 | $numhandled = strlen($part2); 368 | $y = count($data); 369 | 370 | if ($numhandled >= $y) 371 | { 372 | $parts[$num] = $part2; 373 | 374 | continue; 375 | } 376 | 377 | if ($numhandled) $part2 .= "-"; 378 | 379 | $part2 = "xn--" . $part2; 380 | 381 | if (strlen($part2) > 63) return false; 382 | 383 | $bias = self::PUNYCODE_INITIAL_BIAS; 384 | $n = self::PUNYCODE_INITIAL_N; 385 | $delta = 0; 386 | $first = true; 387 | 388 | while ($numhandled < $y) 389 | { 390 | // Find the next largest unhandled code point. 391 | $cp2 = 0x01000000; 392 | foreach ($data as $cp) 393 | { 394 | if ($cp >= $n && $cp2 > $cp) $cp2 = $cp; 395 | } 396 | 397 | // Increase delta but prevent overflow. 398 | $delta += ($cp2 - $n) * ($numhandled + 1); 399 | if ($delta < 0) return false; 400 | $n = $cp2; 401 | 402 | foreach ($data as $cp) 403 | { 404 | if ($cp < $n) 405 | { 406 | $delta++; 407 | 408 | if ($delta < 0) return false; 409 | } 410 | else if ($cp === $n) 411 | { 412 | // Calculate and encode a variable length integer from the delta. 413 | $q = $delta; 414 | $x = 0; 415 | do 416 | { 417 | $x += self::PUNYCODE_BASE; 418 | 419 | if ($x <= $bias) $t = self::PUNYCODE_TMIN; 420 | else if ($x >= $bias + self::PUNYCODE_TMAX) $t = self::PUNYCODE_TMAX; 421 | else $t = $x - $bias; 422 | 423 | if ($q < $t) break; 424 | 425 | $part2 .= self::PUNYCODE_DIGIT_MAP[$t + (($q - $t) % (self::PUNYCODE_BASE - $t))]; 426 | 427 | $q = (int)(($q - $t) / (self::PUNYCODE_BASE - $t)); 428 | 429 | if (strlen($part2) > 63) return false; 430 | } while (1); 431 | 432 | $part2 .= self::PUNYCODE_DIGIT_MAP[$q]; 433 | if (strlen($part2) > 63) return false; 434 | 435 | // Adapt bias. 436 | $numhandled++; 437 | $bias = self::InternalPunycodeAdapt($delta, $numhandled, $first); 438 | $delta = 0; 439 | $first = false; 440 | } 441 | } 442 | 443 | $delta++; 444 | $n++; 445 | } 446 | 447 | $parts[$num] = $part2; 448 | } 449 | 450 | return implode(".", $parts); 451 | } 452 | 453 | public static function ConvertFromPunycode($domain) 454 | { 455 | // Reject invalid domain name lengths. 456 | if (strlen($domain) > 255) return false; 457 | 458 | $parts = explode(".", $domain); 459 | 460 | foreach ($parts as $num => $part) 461 | { 462 | // Reject invalid label lengths. 463 | $y = strlen($part); 464 | if ($y > 63) return false; 465 | 466 | // Skip unencoded portions. 467 | if (substr($part, 0, 4) !== "xn--") continue; 468 | 469 | $part = substr($part, 4); 470 | 471 | // Convert UTF-8 to UTF-32 code points. 472 | $data = self::Convert($part, self::UTF8, self::UTF32_ARRAY); 473 | 474 | // Handle ASCII code points. 475 | $hyphen = ord("-"); 476 | for ($x = count($data); $x && $data[$x - 1] !== $hyphen; $x--); 477 | if (!$x) $data2 = array(); 478 | else 479 | { 480 | $data2 = array_splice($data, 0, $x - 1); 481 | 482 | array_shift($data); 483 | } 484 | 485 | $numhandled = count($data2); 486 | 487 | $bias = self::PUNYCODE_INITIAL_BIAS; 488 | $n = self::PUNYCODE_INITIAL_N; 489 | $delta = 0; 490 | $first = true; 491 | 492 | $pos = 0; 493 | $y = count($data); 494 | while ($pos < $y) 495 | { 496 | // Calculate and decode a delta from the variable length integer. 497 | $olddelta = $delta; 498 | $w = 1; 499 | $x = 0; 500 | do 501 | { 502 | $x += self::PUNYCODE_BASE; 503 | 504 | $cp = $data[$pos]; 505 | $pos++; 506 | 507 | if ($cp >= ord("a") && $cp <= ord("z")) $digit = $cp - ord("a"); 508 | else if ($cp >= ord("A") && $cp <= ord("Z")) $digit = $cp - ord("A"); 509 | else if ($cp >= ord("0") && $cp <= ord("9")) $digit = $cp - ord("0") + 26; 510 | else return false; 511 | 512 | $delta += $digit * $w; 513 | if ($delta < 0) return false; 514 | 515 | if ($x <= $bias) $t = self::PUNYCODE_TMIN; 516 | else if ($x >= $bias + self::PUNYCODE_TMAX) $t = self::PUNYCODE_TMAX; 517 | else $t = $x - $bias; 518 | 519 | if ($digit < $t) break; 520 | 521 | $w *= (self::PUNYCODE_BASE - $t); 522 | if ($w < 0) return false; 523 | } while (1); 524 | 525 | // Adapt bias. 526 | $numhandled++; 527 | $bias = self::InternalPunycodeAdapt($delta - $olddelta, $numhandled, $first); 528 | $first = false; 529 | 530 | // Delta was supposed to wrap around from $numhandled to 0, incrementing $n each time, so fix that now. 531 | $n += (int)($delta / $numhandled); 532 | $delta %= $numhandled; 533 | 534 | // Insert $n (the code point) at the delta position. 535 | array_splice($data2, $delta, 0, array($n)); 536 | $delta++; 537 | } 538 | 539 | $parts[$num] = self::Convert($data2, self::UTF32_ARRAY, self::UTF8); 540 | } 541 | 542 | return implode(".", $parts); 543 | } 544 | 545 | // RFC3492 adapt() function. 546 | protected static function InternalPunycodeAdapt($delta, $numpoints, $first) 547 | { 548 | $delta = ($first ? (int)($delta / self::PUNYCODE_DAMP) : $delta >> 1); 549 | $delta += (int)($delta / $numpoints); 550 | 551 | $y = self::PUNYCODE_BASE - self::PUNYCODE_TMIN; 552 | 553 | $condval = (int)(($y * self::PUNYCODE_TMAX) / 2); 554 | for ($x = 0; $delta > $condval; $x += self::PUNYCODE_BASE) $delta = (int)($delta / $y); 555 | 556 | return (int)($x + ((($y + 1) * $delta) / ($delta + self::PUNYCODE_SKEW))); 557 | } 558 | } 559 | ?> --------------------------------------------------------------------------------