├── .gitignore ├── README.md ├── client ├── router.php └── support │ ├── cacert.pem │ ├── cli.php │ ├── crc32_stream.php │ ├── deflate_stream.php │ ├── http.php │ ├── sdk_drc_client.php │ ├── utf_utils.php │ ├── web_browser.php │ └── websocket.php └── server ├── index.php └── support ├── admin.css ├── admin.js ├── admin_bulkedit.css ├── admin_bulkedit.js ├── admin_print.css ├── admin_view.css ├── admin_view_print.css ├── cacert.pem ├── crc32_stream.php ├── deflate_stream.php ├── en_us_lite.json ├── flex_forms.php ├── flex_forms_error.png ├── http.php ├── jquery-3.5.0.min.js ├── page_basics.php ├── random.php ├── sdk_drc_client.php ├── str_basics.php ├── utf_utils.php ├── view_print_layout.php ├── web_browser.php └── websocket.php /.gitignore: -------------------------------------------------------------------------------- 1 | /server/config.php 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Open Broadcaster (OBS) Remote Control Manager 2 | ============================================= 3 | 4 | This project allows [Open Broadcaster (OBS)](https://obsproject.com/) scene selection to be remotely controlled via a standard web browser. Now you can control the active scene in OBS from a phone, tablet, or other device anywhere in the world from any web browser without having to open any ports on a firewall. 5 | 6 | ![Open Broadcaster (OBS) Remote Control Manager](https://user-images.githubusercontent.com/1432111/216733991-2ffda342-4a30-4196-b19d-95516b9519b3.png) 7 | 8 | [Open Broadcaster](https://obsproject.com/) is a very popular piece of software for recording and live streaming. It has a powerful scene system which allows for multiple scenes to be setup and can be switched between with a single click. But what if the computer running OBS to switch scenes is not on the same network, on a restrictive network such as a corporate environment or some public WiFi networks, or you want someone else to control the active scene remotely? That's where this Remote Control Manager software comes in to enable remotely switching scenes in OBS. 9 | 10 | [![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/) 11 | 12 | Fetaures 13 | -------- 14 | 15 | * Easy to use web browser interface for switching scenes. 16 | * Responsive client software. Scene switching is very fast with minimal lag. 17 | * Client software works with the built-in OBS WebSocket plugin. 18 | * Self-hosted server software for total control and peace of mind. 19 | * Great for switching between multiple cameras during a livestream event. 20 | * Has a liberal open source license. MIT or LGPL, your choice. 21 | * Designed for relatively painless integration into your environment. 22 | * Sits on GitHub for all of that pull request and issue tracker goodness to easily submit changes and ideas respectively. 23 | 24 | Getting Started 25 | --------------- 26 | 27 | Either 'git clone' this repository or use the green "Code" button and "Download ZIP" above to obtain the latest release of this software. 28 | 29 | This software is written in PHP and therefore depends on PHP being installed on the system. Installing PHP can be a little tricky on Windows. Here's a [portable version of PHP](https://github.com/cubiclesoft/portable-apache-maria-db-php-for-windows). Other OSes have their own quirky method of installing PHP. Consult your favorite search engine. 30 | 31 | The ideal setup involves installing the self-hosted server on your own web host (e.g. a VPS). However, there may be a number of public installs of the server software that you can freely use with the client software. Public servers may not work as well as a privately hosted server. 32 | 33 | Start OBS. Go to Tools -> WebSocket Server Settings. Enable the WebSocket server on the default port 4455, set a password, and then "Apply" the changes. 34 | 35 | Start a Command Prompt or terminal, `cd` into the Remote Control Manager "client" directory and run: 36 | 37 | ``` 38 | php router.php -p YourOBSWebSocketPasswordHere https://grilledapps.com/obsmanager/ "Live Presentation" 39 | ``` 40 | 41 | If successful, OBS will now be connected to a public facing installation of the server software and will output a URL that looks like this: 42 | 43 | ``` 44 | Joined DRC channel 1 as client ID 2. 45 | Channel name: katun-tireh-letih 46 | Users may join via: https://grilledapps.com/obsmanager/?channel=katun-tireh-letih 47 | ``` 48 | 49 | Visit the URL on the device and web browser of your choice. The web browser will list the scenes in OBS. Simply select a scene to switch to it in OBS. 50 | 51 | While anyone with the URL can switch scenes, the server software randomly generates a new, unique channel name every time the client software starts. 52 | 53 | To stop the client software, press Ctrl + C in the Command Prompt or terminal. Alternatively, just close OBS. 54 | 55 | Server Setup 56 | ------------ 57 | 58 | The Getting Started section above uses a public Remote Control Manager server. To setup your own self-hosted server, you will first need a server running Nginx, PHP FPM, PHP CLI, [PHP Data Relay Center (DRC)](https://github.com/cubiclesoft/php-drc), and the server software from this repository. 59 | 60 | If that sounds like a lot, fortunately there's a faster way to get everything installed via [Server Instant Start](https://github.com/cubiclesoft/server-instant-start). Server Instant Start can spin up a fully ready system on most VPS providers in a matter of minutes with everything installed and configured except this server software. 61 | 62 | Note: Purchasing a domain, setting up DNS, and enabling SSL/TLS certificates is left as an exercise. 63 | 64 | Upload the contents of the "server" directory to a directory on your server. Create a file called "config.php" in the same directory as "index.php" and apply corrected paths to PHP DRC on your system: 65 | 66 | ```php 67 | array( 20 | "h" => "host", 21 | "p" => "password", 22 | "?" => "help" 23 | ), 24 | "rules" => array( 25 | "host" => array("arg" => true), 26 | "password" => array("arg" => true), 27 | "help" => array("arg" => false) 28 | ), 29 | "allow_opts_after_param" => false 30 | ); 31 | $args = CLI::ParseCommandLine($options); 32 | 33 | if (isset($args["opts"]["help"]) || count($args["params"]) < 2) 34 | { 35 | echo "The OBS Manager client\n"; 36 | echo "Purpose: Connect to a remote OBS Manager instance and Palakis obs-websocket from the command-line.\n"; 37 | echo "\n"; 38 | echo "Syntax: " . $args["file"] . " [options] OBSManagerURL LocationName\n"; 39 | echo "Options:\n"; 40 | echo "\t-h Host and port to connect to (Default is \"ws://127.0.0.1:4455/\").\n"; 41 | echo "\t-p Password for obs-websocket.\n"; 42 | echo "\n"; 43 | echo "Examples:\n"; 44 | echo "\tphp " . $args["file"] . "\n"; 45 | echo "\tphp " . $args["file"] . " -p supersecret https://yourdomain.com/obsmanager/ Home\n"; 46 | 47 | exit(); 48 | } 49 | 50 | $host = (isset($args["opts"]["host"]) ? $args["opts"]["host"] : "ws://127.0.0.1:4455/"); 51 | 52 | require_once $rootpath . "/support/web_browser.php"; 53 | require_once $rootpath . "/support/websocket.php"; 54 | require_once $rootpath . "/support/sdk_drc_client.php"; 55 | 56 | // Connect to OBS. 57 | // OBS Websocket Protocol specification: https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md 58 | echo "Initializing...\n"; 59 | $ws = new WebSocket(); 60 | $result = $ws->Connect($host, "http://127.0.0.1"); 61 | if (!$result["success"]) CLI::DisplayError("Unable to connect to '" . $host . "'.", $result); 62 | 63 | $nextobsmsg = 1; 64 | $obsmessages = array(); 65 | $obsstate = "connect"; 66 | 67 | function SendOBSRequest($type, $data = array(), $drcclient = false) 68 | { 69 | global $nextobsmsg, $ws, $obsmessages; 70 | 71 | $reqid = "router-" . $nextobsmsg; 72 | $nextobsmsg++; 73 | 74 | // Request (Opcode 6). 75 | $payload = array( 76 | "op" => 6, 77 | "d" => array( 78 | "requestType" => $type, 79 | "requestId" => $reqid, 80 | "requestData" => $data 81 | ) 82 | ); 83 | 84 | $result = $ws->Write(json_encode($payload, JSON_UNESCAPED_SLASHES), WebSocket::FRAMETYPE_TEXT); 85 | if (!$result["success"]) return $result; 86 | 87 | $obsmessages[$reqid] = array("type" => $type, "reqid" => $reqid, "origmsg" => $data, "drcclient" => $drcclient); 88 | 89 | return array("success" => true); 90 | } 91 | 92 | // Get the DRC connection and token information. 93 | $web = new WebBrowser(); 94 | 95 | $url = $args["params"][0]; 96 | if (substr($url, -1) != "/") $url .= "/"; 97 | $url .= "?api=init&location=" . urlencode($args["params"][1]); 98 | $result = $web->Process($url); 99 | if (!$result["success"]) CLI::DisplayError("Unable to retrieve '" . $url . "'.", $result); 100 | 101 | $drcinfo = json_decode($result["body"], true); 102 | if (!is_array($drcinfo)) CLI::DisplayError("Expected a JSON response from the OBS Manager.", $result); 103 | if (!$drcinfo["success"]) CLI::DisplayError("A failure occurred while attempting to get access to the remote OBS Manager.", $drcinfo); 104 | if ($drcinfo["data"]["protocol"] !== "obs-websocket-remote-api") CLI::DisplayError("The server returned an unknown/unsupported DRC protocol.", $result); 105 | 106 | $drc = false; 107 | $drcstate = "connect"; 108 | 109 | echo "Ready.\n"; 110 | 111 | do 112 | { 113 | if ($drc === false) 114 | { 115 | $drc = new DRCClient(); 116 | 117 | // Connect to the DRC server. 118 | $result = $drc->Connect($drcinfo["drc_url"], $drcinfo["drc_origin"]); 119 | if (!$result["success"]) 120 | { 121 | CLI::DisplayError("Unable to connect to the DRC server.", $result, false); 122 | 123 | $drc = false; 124 | } 125 | else 126 | { 127 | // Join the channel. 128 | $result = $drc->JoinChannel($drcinfo["data"]["channelname"], $drcinfo["data"]["protocol"], $drcinfo["data"]["token"], true); 129 | if (!$result["success"]) CLI::DisplayError("Unable to join the DRC channel '" . $drcinfo["data"]["channelname"] . "'.", $result); 130 | 131 | $drcchannelinfo = $result["data"]; 132 | 133 | echo "Joined DRC channel " . $drcchannelinfo["channel"] . " as client ID " . $drcchannelinfo["id"] . ".\n"; 134 | echo "Channel name: " . $drcinfo["user_channel"] . "\n"; 135 | echo "Users may join via: " . $drcinfo["user_url"] . "\n"; 136 | } 137 | } 138 | 139 | // Implement stream_select() directly since multiple clients are involved. 140 | $timeout = 30; 141 | $readfps = array(); 142 | $writefps = array(); 143 | $exceptfps = NULL; 144 | 145 | $fp = $ws->GetStream(); 146 | $readfps[] = $fp; 147 | if ($ws->NeedsWrite()) $writefps[] = $fp; 148 | 149 | if ($drc === false) $timeout = 3; 150 | else 151 | { 152 | $fp = $drc->GetStream(); 153 | $readfps[] = $fp; 154 | if ($drc->NeedsWrite()) $writefps[] = $fp; 155 | } 156 | 157 | $result = @stream_select($readfps, $writefps, $exceptfps, $timeout); 158 | if ($result === false) break; 159 | 160 | // Process WebSocket data. 161 | $result = $ws->Wait(0); 162 | if (!$result["success"]) CLI::DisplayError("WebSocket connection failure.", $result); 163 | 164 | do 165 | { 166 | $result = $ws->Read(); 167 | if (!$result["success"]) CLI::DisplayError("WebSocket connection failure.", $result); 168 | 169 | if ($result["data"] !== false) 170 | { 171 | // echo "Raw message from server:\n"; 172 | // var_dump($result["data"]); 173 | // echo "\n"; 174 | 175 | $data = json_decode($result["data"]["payload"], true); 176 | if ($obsstate === "connect") 177 | { 178 | if ($data["op"] == 0) 179 | { 180 | // Identify (Opcode 1). 181 | $payload = array( 182 | "op" => 1, 183 | "d" => array( 184 | "rpcVersion" => 1 185 | ) 186 | ); 187 | 188 | if (isset($data["d"]["authentication"])) 189 | { 190 | if (!isset($args["opts"]["password"])) CLI::DisplayError("The OBS websocket host '" . $host . "' requires a password."); 191 | 192 | echo "Authenticating with OBS...\n"; 193 | $hash = base64_encode(hash("sha256", base64_encode(hash("sha256", $args["opts"]["password"] . $data["d"]["authentication"]["salt"], true)) . $data["d"]["authentication"]["challenge"], true)); 194 | 195 | $payload["d"]["authentication"] = $hash; 196 | } 197 | 198 | $result2 = $ws->Write(json_encode($payload, JSON_UNESCAPED_SLASHES), WebSocket::FRAMETYPE_TEXT); 199 | if (!$result2["success"]) return $result2; 200 | 201 | $obsstate = "identify"; 202 | } 203 | else 204 | { 205 | echo "Expected Hello (Opcode 0). Received unknown message:\n"; 206 | var_dump($data); 207 | echo "\n"; 208 | } 209 | } 210 | else if ($obsstate === "identify") 211 | { 212 | if ($data["op"] == 2) 213 | { 214 | $obsstate = "main"; 215 | } 216 | else 217 | { 218 | echo "Expected Identified (Opcode 2). Received unknown message:\n"; 219 | var_dump($data); 220 | echo "\n"; 221 | } 222 | } 223 | else if ($obsstate === "main") 224 | { 225 | // RequestResponse (Opcode 7). 226 | if ($data["op"] == 7) 227 | { 228 | if (!isset($data["d"]["requestId"]) || !isset($obsmessages[$data["d"]["requestId"]])) 229 | { 230 | echo "Unknown message:\n"; 231 | var_dump($data); 232 | echo "\n"; 233 | } 234 | else 235 | { 236 | $origreq = $obsmessages[$data["d"]["requestId"]]; 237 | unset($obsmessages[$data["d"]["requestId"]]); 238 | 239 | // Send the results back to the client. 240 | unset($data["d"]["requestId"]); 241 | if ($drc !== false) 242 | { 243 | $result2 = $drc->SendCommand($origreq["drcclient"]["channel"], $origreq["drcclient"]["cmd"], $origreq["drcclient"]["from"], array("result" => $data["d"])); 244 | if (!$result2["success"]) CLI::DisplayError("Unable to send response to the request.", $result2, false); 245 | } 246 | } 247 | } 248 | } 249 | 250 | 251 | /* 252 | if (!isset($data["message-id"]) || !isset($obsmessages[$data["message-id"]])) 253 | { 254 | // Skip all events. 255 | if (!isset($data["update-type"])) 256 | { 257 | echo "Unknown message:\n"; 258 | var_dump($data); 259 | echo "\n"; 260 | } 261 | } 262 | else 263 | { 264 | $origreq = $obsmessages[$data["message-id"]]; 265 | unset($obsmessages[$data["message-id"]]); 266 | 267 | if ($origreq["type"] === "GetAuthRequired") 268 | { 269 | // Send password if required. 270 | if ($data["authRequired"]) 271 | { 272 | if (!isset($args["opts"]["password"])) CLI::DisplayError("The OBS websocket host '" . $host . "' requires a password."); 273 | 274 | echo "Authenticating with OBS...\n"; 275 | $hash = base64_encode(hash("sha256", base64_encode(hash("sha256", $args["opts"]["password"] . $data["salt"], true)) . $data["challenge"], true)); 276 | 277 | SendOBSMessage("Authenticate", array("auth" => $hash)); 278 | } 279 | } 280 | else if ($origreq["type"] === "Authenticate") 281 | { 282 | if ($data["status"] !== "ok") CLI::DisplayError("Unable to authenticate with OBS websocket. Incorrect password? " . $data["error"]); 283 | else echo "Authentication successful.\n"; 284 | } 285 | else if (isset($origreq["drcclient"])) 286 | { 287 | // Send the results back to the client. 288 | unset($data["message-id"]); 289 | if ($drc !== false) 290 | { 291 | $result2 = $drc->SendCommand($origreq["drcclient"]["channel"], $origreq["drcclient"]["cmd"], $origreq["drcclient"]["from"], array("result" => $data)); 292 | if (!$result2["success"]) CLI::DisplayError("Unable to send response to the request.", $result2, false); 293 | } 294 | } 295 | else 296 | { 297 | echo "Other!\n"; 298 | var_dump($origreq); 299 | var_dump($data); 300 | } 301 | } 302 | */ 303 | } 304 | } while ($result["data"] !== false); 305 | 306 | 307 | // Process DRC channel data. 308 | if ($drc !== false) 309 | { 310 | $result = $drc->Wait(0); 311 | if (!$result["success"]) 312 | { 313 | CLI::DisplayError("DRC connection failure.", $result, false); 314 | 315 | $drc = false; 316 | } 317 | else 318 | { 319 | do 320 | { 321 | $result = $drc->Read(); 322 | if (!$result["success"]) 323 | { 324 | CLI::DisplayError("DRC connection failure.", $result, false); 325 | 326 | $drc = false; 327 | 328 | break; 329 | } 330 | 331 | if ($result["data"] !== false) 332 | { 333 | $data = $result["data"]; 334 | 335 | if (strncmp($data["cmd"], "OBS-", 4) == 0 && $data["cmd"] !== "OBS-GetAuthRequired" && $data["cmd"] !== "OBS-Authenticate") 336 | { 337 | $data2 = $data; 338 | unset($data2["channel"]); 339 | unset($data2["success"]); 340 | unset($data2["from"]); 341 | unset($data2["cmd"]); 342 | 343 | echo "Client " . $data["from"] . " requested " . substr($data["cmd"], 4) . "." . (count($data2) ? "\n " . json_encode($data2, JSON_UNESCAPED_SLASHES) : "") . "\n"; 344 | 345 | SendOBSRequest(substr($data["cmd"], 4), $data2, $data); 346 | } 347 | } 348 | } while ($result["data"] !== false); 349 | } 350 | } 351 | 352 | } while (1); 353 | ?> -------------------------------------------------------------------------------- /client/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 | ?> -------------------------------------------------------------------------------- /client/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 | ?> -------------------------------------------------------------------------------- /client/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 | ?> -------------------------------------------------------------------------------- /client/support/sdk_drc_client.php: -------------------------------------------------------------------------------- 1 | drc_channels = array(); 20 | $this->drc_client_id = false; 21 | } 22 | 23 | public function Read($finished = true, $wait = false) 24 | { 25 | $result = parent::Read(); 26 | if (!$result["success"] || $result["data"] === false) return $result; 27 | 28 | $result["data"] = @json_decode($result["data"]["payload"], true); 29 | 30 | if (!is_array($result["data"])) $result["data"] = array("success" => false, "error" => "Invalid packet.", "errorcode" => "invalid_packet"); 31 | else if (!$result["data"]["success"]) return $result; 32 | else if (isset($result["data"]["cmd"]) && $result["data"]["cmd"] === "JOINED") 33 | { 34 | if (isset($result["data"]["channelname"]) && isset($result["data"]["protocol"]) && isset($result["data"]["clients"])) 35 | { 36 | $this->drc_channels[$result["data"]["channel"]] = $result["data"]; 37 | $this->drc_client_id = $result["data"]["id"]; 38 | } 39 | else if (isset($this->drc_channels[$result["data"]["channel"]])) 40 | { 41 | $this->drc_channels[$result["data"]["channel"]]["clients"][$result["data"]["id"]] = $result["data"]["info"]; 42 | } 43 | } 44 | else if (isset($result["data"]["cmd"]) && $result["data"]["cmd"] === "SET_EXTRA" && isset($this->drc_channels[$result["data"]["channel"]]) && isset($this->drc_channels[$result["data"]["channel"]]["clients"][$result["data"]["id"]])) 45 | { 46 | $this->drc_channels[$result["data"]["channel"]]["clients"][$result["data"]["id"]]["extra"] = $result["data"]["extra"]; 47 | } 48 | else if (isset($result["data"]["cmd"]) && $result["data"]["cmd"] === "LEFT" && isset($this->drc_channels[$result["data"]["channel"]])) 49 | { 50 | if ($result["data"]["id"] === $this->drc_client_id) unset($this->drc_channels[$result["data"]["channel"]]); 51 | else unset($this->drc_channels[$result["data"]["channel"]]["clients"][$result["data"]["id"]]); 52 | } 53 | 54 | return $result; 55 | } 56 | 57 | public function CreateToken($authtoken, $channelname, $protocol, $clientmode, $extra = array(), $wait = false) 58 | { 59 | $data = array( 60 | "cmd" => "GRANT", 61 | "channel" => $channelname, 62 | "protocol" => $protocol, 63 | "clientmode" => $clientmode, 64 | "extra" => $extra 65 | ); 66 | 67 | if ($authtoken !== false) $data["token"] = $authtoken; 68 | 69 | $result = $this->Write(json_encode($data, JSON_UNESCAPED_SLASHES), WebSocket::FRAMETYPE_TEXT); 70 | if (!$result["success"]) return $result; 71 | 72 | if ($wait) 73 | { 74 | $result = $this->Wait(); 75 | while ($result["success"]) 76 | { 77 | do 78 | { 79 | $result = $this->Read(); 80 | if (!$result["success"]) return $result; 81 | 82 | if ($result["data"] !== false && isset($result["data"]["cmd"]) && $result["data"]["cmd"] === "GRANTED" && isset($result["data"]["channelname"]) && $result["data"]["channelname"] === $channelname && isset($result["data"]["protocol"]) && $result["data"]["protocol"] === $protocol && isset($result["data"]["token"])) 83 | { 84 | return $result; 85 | } 86 | } while ($result["data"] !== false); 87 | 88 | $result = $this->Wait(); 89 | } 90 | } 91 | 92 | return $result; 93 | } 94 | 95 | public function JoinChannel($channelname, $protocol, $token, $wait = false, $allowipauth = true) 96 | { 97 | $data = array( 98 | "cmd" => "JOIN", 99 | "channel" => $channelname, 100 | "protocol" => $protocol 101 | ); 102 | 103 | if ($token !== false) $data["token"] = $token; 104 | if ($allowipauth === false) $data["ipauth"] = false; 105 | 106 | $result = $this->Write(json_encode($data, JSON_UNESCAPED_SLASHES), WebSocket::FRAMETYPE_TEXT); 107 | if (!$result["success"]) return $result; 108 | 109 | if ($wait) 110 | { 111 | $result = $this->Wait(); 112 | while ($result["success"]) 113 | { 114 | do 115 | { 116 | $result = $this->Read(); 117 | if (!$result["success"]) return $result; 118 | 119 | if ($result["data"] !== false && isset($result["data"]["cmd"]) && $result["data"]["cmd"] === "JOINED" && isset($result["data"]["channelname"]) && $result["data"]["channelname"] === $channelname && isset($result["data"]["protocol"]) && $result["data"]["protocol"] === $protocol && isset($result["data"]["clients"])) 120 | { 121 | return $result; 122 | } 123 | } while ($result["data"] !== false); 124 | 125 | $result = $this->Wait(); 126 | } 127 | } 128 | 129 | return $result; 130 | } 131 | 132 | public function GetChannels() 133 | { 134 | return $this->drc_channels; 135 | } 136 | 137 | public function GetChannel($channel) 138 | { 139 | return (isset($this->drc_channels[$channel]) ? $this->drc_channels[$channel] : false); 140 | } 141 | 142 | public function GetClientID() 143 | { 144 | return $this->drc_client_id; 145 | } 146 | 147 | public function SetExtra($channel, $id, $extra = array(), $wait = false) 148 | { 149 | $data = array( 150 | "channel" => $channel, 151 | "cmd" => "SET_EXTRA", 152 | "id" => $id, 153 | "extra" => $extra 154 | ); 155 | 156 | $result = $this->Write(json_encode($data, JSON_UNESCAPED_SLASHES), WebSocket::FRAMETYPE_TEXT); 157 | if (!$result["success"]) return $result; 158 | 159 | if ($wait) 160 | { 161 | $result = $this->Wait(); 162 | while ($result["success"]) 163 | { 164 | do 165 | { 166 | $result = $this->Read(); 167 | if (!$result["success"]) return $result; 168 | 169 | if ($result["data"] !== false && isset($result["data"]["cmd"]) && $result["data"]["cmd"] === "SET_EXTRA" && $result["data"]["id"] === $id) return $result; 170 | } while ($result["data"] !== false); 171 | 172 | $result = $this->Wait(); 173 | } 174 | } 175 | 176 | return $result; 177 | } 178 | 179 | public function SendCommand($channel, $cmd, $to, $options = array(), $wait = false) 180 | { 181 | $data = array( 182 | "channel" => $channel, 183 | "cmd" => $cmd, 184 | "to" => $to 185 | ); 186 | 187 | $data = $data + $options; 188 | 189 | $result = $this->Write(json_encode($data, JSON_UNESCAPED_SLASHES), WebSocket::FRAMETYPE_TEXT); 190 | if (!$result["success"]) return $result; 191 | 192 | if (is_string($wait)) 193 | { 194 | $result = $this->Wait(); 195 | while ($result["success"]) 196 | { 197 | do 198 | { 199 | $result = $this->Read(); 200 | if (!$result["success"]) return $result; 201 | 202 | if ($result["data"] !== false && isset($result["data"]["cmd"]) && $result["data"]["cmd"] === $wait && ($to < 0 || $result["data"]["from"] === $to)) return $result; 203 | } while ($result["data"] !== false); 204 | 205 | $result = $this->Wait(); 206 | } 207 | } 208 | else if ($wait) 209 | { 210 | do 211 | { 212 | $result = $this->Wait(); 213 | } while ($result["success"] && $this->NeedsWrite()); 214 | } 215 | 216 | return $result; 217 | } 218 | 219 | public function GetRandomAuthClientID($channel) 220 | { 221 | if (!isset($this->drc_channels[$channel])) return false; 222 | 223 | $idmap = array(); 224 | foreach ($this->drc_channels[$channel]["clients"] as $id => $info) 225 | { 226 | if ($info["auth"]) $idmap[] = $id; 227 | } 228 | 229 | $y = count($idmap); 230 | if (!$y) return false; 231 | 232 | return $idmap[random_int(0, $y - 1)]; 233 | } 234 | 235 | public function SendCommandToAuthClients($channel, $cmd, $options = array(), $wait = false) 236 | { 237 | if (!isset($this->drc_channels[$channel])) return array("success" => false, "error" => "Invalid channel.", "errorcode" => "invalid_channel"); 238 | 239 | foreach ($this->drc_channels[$channel]["clients"] as $id => $info) 240 | { 241 | if ($info["auth"]) 242 | { 243 | $result = $this->SendCommand($channel, $cmd, $id, $options, $wait); 244 | if (!$result["success"]) return $result; 245 | } 246 | } 247 | 248 | return array("success" => true); 249 | } 250 | 251 | public function LeaveChannel($channel, $wait = false) 252 | { 253 | unset($this->drc_channels[$channel]); 254 | 255 | $data = array( 256 | "cmd" => "LEAVE", 257 | "channel" => $channel 258 | ); 259 | 260 | $result = $this->Write(json_encode($data, JSON_UNESCAPED_SLASHES), WebSocket::FRAMETYPE_TEXT); 261 | if (!$result["success"]) return $result; 262 | 263 | if ($wait) 264 | { 265 | $result = $this->Wait(); 266 | while ($result["success"]) 267 | { 268 | do 269 | { 270 | $result = $this->Read(); 271 | if (!$result["success"]) return $result; 272 | 273 | if ($result["data"] !== false && isset($result["data"]["cmd"]) && $result["data"]["cmd"] === "LEFT" && !isset($this->drc_channels[$channel])) return $result; 274 | } while ($result["data"] !== false); 275 | 276 | $result = $this->Wait(); 277 | } 278 | } 279 | 280 | return $result; 281 | } 282 | } 283 | ?> -------------------------------------------------------------------------------- /client/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 | ?> -------------------------------------------------------------------------------- /server/index.php: -------------------------------------------------------------------------------- 1 | false, 45 | "error" => $msg, 46 | "errorcode" => $msgcode 47 | ); 48 | 49 | if ($info !== false) $result["info"] = $info; 50 | 51 | OutputAPIResult($result); 52 | } 53 | 54 | // Handle API calls. 55 | if (isset($_REQUEST["api"])) 56 | { 57 | if ($_REQUEST["api"] == "init") 58 | { 59 | if (!isset($_REQUEST["location"]) || !is_string($_REQUEST["location"])) OutputAPIError("Missing or invalid 'location'.", "missing_location"); 60 | 61 | $rng = new CSPRNG(); 62 | 63 | $freqmap = json_decode(file_get_contents("support/en_us_lite.json"), true); 64 | 65 | $words = array(); 66 | for ($x = 0; $x < 3; $x++) $words[] = $rng->GenerateWordLite($freqmap, $rng->GetInt(4, 5)); 67 | $words = implode("-", $words); 68 | $channelname = "OBSManager-" . $words; 69 | 70 | $drc = new DRCClient(); 71 | 72 | // Connect to the server. 73 | $result = $drc->Connect($drc_private_url, $drc_private_origin); 74 | if (!$result["success"]) OutputAPIError("Unable to connect to DRC.", "drc_connect_failed", $result); 75 | 76 | // Create a grant token. 77 | $result = $drc->CreateToken(false, $channelname, "obs-websocket-remote-api", DRCClient::CM_SEND_TO_ANY, array("location" => $_REQUEST["location"]), 10, true); 78 | if (!$result["success"]) OutputAPIError("Unable to create a DRC channel token.", "drc_create_token_failed", $result); 79 | 80 | $result["user_channel"] = $words; 81 | $result["user_url"] = BB_GetFullRequestURLBase() . "?channel=" . urlencode($words); 82 | $result["drc_url"] = $drc_public_url; 83 | $result["drc_origin"] = $drc_public_origin; 84 | 85 | OutputAPIResult($result); 86 | } 87 | else 88 | { 89 | OutputAPIError("Unknown API '" . $_REQUEST["api"] . "'.", "invalid_api"); 90 | } 91 | } 92 | 93 | // Establish a regular session. 94 | session_start(); 95 | 96 | if (!isset($_SESSION["ga_usertoken"])) 97 | { 98 | $rng = new CSPRNG(); 99 | 100 | $_SESSION["ga_usertoken"] = $rng->GenerateToken(64); 101 | } 102 | 103 | $bb_usertoken = $_SESSION["ga_usertoken"]; 104 | 105 | 106 | BB_ProcessPageToken("action"); 107 | 108 | // Menu/Navigation options. 109 | $menuopts = array( 110 | ); 111 | 112 | // Optional function to customize styles. 113 | function BB_InjectLayoutHead() 114 | { 115 | // Menu title underline: Colors with 60% saturation and 75% brightness generally look good. 116 | ?> 117 | 120 | 126 | 134 | Connect($drc_private_url, $drc_private_origin); 160 | if (!$result["success"]) OutputAPIError("Unable to connect to DRC.", "drc_connect_failed", $result); 161 | 162 | // Create a grant token. 163 | $result = $drc->CreateToken(false, $channelname, "obs-websocket-remote-api", DRCClient::CM_SEND_TO_AUTHS, array(), 10); 164 | if (!$result["success"]) OutputAPIError("Unable to create a DRC channel token.", "drc_create_token_failed", $result); 165 | 166 | $token = $result["data"]["token"]; 167 | 168 | // Join the channel. 169 | $result = $drc->JoinChannel($channelname, "obs-websocket-remote-api", $token, 10, false); 170 | if (!$result["success"]) OutputAPIError("Unable to join DRC channel.", "drc_join_channel_failed", $result); 171 | 172 | $channelinfo = $result["data"]; 173 | $channelid = $channelinfo["channel"]; 174 | 175 | // Verify that an authority client is available. 176 | $authclientid = $drc->GetRandomAuthClientID($channelid); 177 | if ($authclientid === false) OutputAPIError("No authorities are available. Is OBS running?", "no_authorities"); 178 | 179 | // Activate the scene. 180 | $result = $drc->SendCommand($channelid, "OBS-SetCurrentProgramScene", $authclientid, array("sceneName" => $_REQUEST["scene"]), 10, "OBS-SetCurrentProgramScene"); 181 | if (!$result["success"]) OutputAPIError("Failed to activate the scene.", "obs_setcurrentscene_failed", $result); 182 | 183 | if (!$result["data"]["result"]["requestStatus"]["result"]) OutputAPIError("Failed to activate the scene.", "obs_setcurrentprogramscene_failed", $result); 184 | 185 | $result = array( 186 | "success" => true 187 | ); 188 | 189 | OutputAPIResult($result); 190 | } 191 | 192 | if (isset($_REQUEST["action"]) && $_REQUEST["action"] == "managechannel" && isset($_REQUEST["channel"]) && is_string($_REQUEST["channel"]) && $_REQUEST["channel"] !== "") 193 | { 194 | $location = ""; 195 | $scenes = array(); 196 | $channelname = "OBSManager-" . $_REQUEST["channel"]; 197 | 198 | $drc = new DRCClient(); 199 | 200 | // Connect to the server. 201 | $result = $drc->Connect($drc_private_url, $drc_private_origin); 202 | if (!$result["success"]) BB_SetPageMessage("error", "Unable to connect to DRC. " . $result["error"] . " (" . $result["errorcode"] . ")"); 203 | else 204 | { 205 | // Create a grant token. 206 | $result = $drc->CreateToken(false, $channelname, "obs-websocket-remote-api", DRCClient::CM_SEND_TO_AUTHS, array(), 10); 207 | if (!$result["success"]) BB_SetPageMessage("error", "Unable to create a DRC channel token. " . $result["error"] . " (" . $result["errorcode"] . ")"); 208 | else 209 | { 210 | $token = $result["data"]["token"]; 211 | 212 | // Join the channel. 213 | $result = $drc->JoinChannel($channelname, "obs-websocket-remote-api", $token, 10, false); 214 | if (!$result["success"]) BB_SetPageMessage("error", "Unable to join DRC channel. " . $result["error"] . " (" . $result["errorcode"] . ")"); 215 | else 216 | { 217 | $channelinfo = $result["data"]; 218 | $channelid = $channelinfo["channel"]; 219 | 220 | // Verify that an authority client is available. 221 | $authclientid = $drc->GetRandomAuthClientID($channelid); 222 | if ($authclientid === false) BB_SetPageMessage("error", "No authorities are available. Is OBS running?"); 223 | else 224 | { 225 | // Get the scene list. 226 | if (isset($channelinfo["clients"][$authclientid]["extra"]["location"])) $location = $channelinfo["clients"][$authclientid]["extra"]["location"]; 227 | 228 | $result = $drc->SendCommand($channelid, "OBS-GetSceneList", $authclientid, array(), 10, "OBS-GetSceneList"); 229 | if (!$result["success"]) BB_SetPageMessage("error", "Unable to request scene list. " . $result["error"] . " (" . $result["errorcode"] . ")"); 230 | else if (!$result["data"]["result"]["requestStatus"]["result"]) BB_SetPageMessage("error", "Request for the scene list resulted in an error. " . $result["data"]["result"]["requestStatus"]["comment"]); 231 | else 232 | { 233 | $scenes2 = array_reverse($result["data"]["result"]["responseData"]["scenes"]); 234 | 235 | foreach ($scenes2 as $scene) 236 | { 237 | $scenes[$scene["sceneName"]] = "" . htmlspecialchars($scene["sceneName"]) . ""; 238 | } 239 | } 240 | } 241 | } 242 | } 243 | } 244 | 245 | $desc = "
"; 246 | ob_start(); 247 | ?> 248 | 253 | 254 | 271 | \n" . implode("\n", $scenes) . "\n"; 275 | 276 | $contentopts = array( 277 | "desc" => "Select the current OBS scene" . ($location !== "" ? " for " . $location : "") . ".", 278 | "htmldesc" => $desc 279 | ); 280 | 281 | BB_GeneratePage("Manage OBS Channel", array(), $contentopts); 282 | } 283 | 284 | if (isset($_REQUEST["channel"])) 285 | { 286 | if ($_REQUEST["channel"] == "") BB_SetPageMessage("error", "Please fill in 'Channel Name'.", "channel"); 287 | 288 | if (BB_GetPageMessageType() != "error") 289 | { 290 | // Redirect to the channel. 291 | BB_RedirectPage("", "", array("action=managechannel&channel=" . urlencode($_REQUEST["channel"]) . "&sec_t=" . BB_CreateSecurityToken("managechannel"))); 292 | } 293 | } 294 | 295 | $contentopts = array( 296 | "desc" => "Fill in the field below to remotely manage OBS.", 297 | "fields" => array( 298 | array( 299 | "title" => "Channel Name", 300 | "type" => "text", 301 | "name" => "channel", 302 | "default" => "" 303 | ), 304 | ), 305 | "submit" => "Go" 306 | ); 307 | 308 | BB_GeneratePage("Select Channel", array(), $contentopts); 309 | ?> -------------------------------------------------------------------------------- /server/support/admin.css: -------------------------------------------------------------------------------- 1 | html, body { height: 100%; margin: 0px; padding: 0px; overflow: hidden; background-color: #FFFFFF; } 2 | body { font-family: Verdana, Arial, Helvetica, sans-serif; position: relative; color: #222222; } 3 | 4 | #contentwrap { height: 100%; overflow: auto; outline: none; } 5 | #contentwrap .proptitlewrap { background-color: #222222; color: #FAFAFA; padding: 0.85em; box-shadow: 0px 3px 3px 0px rgba(0,0,0,0.15); } 6 | #contentwrap .proptitle { max-width: 800px; margin-left: auto; margin-right: auto; font-weight: bold; font-size: 1.3em; } 7 | #contentwrap #navbutton { vertical-align: middle; display: none; padding: 0.23em 0; border: 1px solid #CCCCCC; border-radius: 5px; cursor: pointer; } 8 | #contentwrap #navbutton .navbuttonline { display: block; margin: 0.23em 0.4em; width: 1.0em; height: 2px; background-color: #FAFAFA; border-radius: 2px; } 9 | #contentwrap #navbutton:hover { color: #FFFFFF; border: 1px solid #E0E0E0; background-color: #2A2A2A; } 10 | #contentwrap #navbutton:hover .navbuttonline { background-color: #FFFFFF; } 11 | 12 | #contentwrap .propmessagewrap { border-top: 1px solid transparent; border-bottom: 1px solid transparent; padding: 0.5em 1.0em; box-shadow: 0px 3px 3px 0px rgba(0,0,0,0.15); } 13 | #contentwrap .propmessage { max-width: 800px; margin-left: auto; margin-right: auto; } 14 | #contentwrap .propmessagesuccess { border-color: #B2DBA1; background-color: #DFF0D8; background-repeat: repeat-x; color: #3C763D; } 15 | #contentwrap .propmessageerror { border-color: #DCA7A7; background-color: #F2DEDE; background-repeat: repeat-x; color: #A94442; } 16 | #contentwrap .propmessageinfo { border-color: #9ACFEA; background-color: #D9EDF7; background-repeat: repeat-x; color: #31708F; } 17 | 18 | #contentwrap .propdescwrap { padding: 1.0em 1.0em 0; } 19 | #contentwrap .propdesc { max-width: 800px; margin-left: auto; margin-right: auto; } 20 | #contentwrap .propmainwrap { padding: 1.0em; } 21 | #contentwrap .propmain { max-width: 800px; margin-left: auto; margin-right: auto; } 22 | 23 | #menuwrap { float: left; width: 250px; min-width: 200px; max-width: 15%; height: 100%; overflow: auto; border-right: 1px solid #CCCCCC; font-size: 0.9em; background-color: #222222; color: #FAFAFA; display: flex; flex-direction: column; } 24 | #menuwrap .logo { text-align: center; user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; cursor: default; } 25 | #menuwrap .logo:last-child { flex-grow: 1; display: flex; } 26 | #menuwrap .logo > * { display: inline-block; margin: auto auto 0 auto; padding-top: 1.0em; border-right: 1px solid transparent; } 27 | #menuwrap .logo img { max-width: 100%; display: inline-block; } 28 | #menuwrap .logo + .menu { margin-top: 0; } 29 | #menuwrap .menu { margin-top: 1.0em; border-top: 1px solid #444444; padding-top: 1.2em; } 30 | #menuwrap .menu:first-child { margin-top: 0; border-top: 0 none; padding-top: 1.0em; } 31 | #menuwrap .menu .titlewrap { margin: 0 1.0em 0.85em; } 32 | #menuwrap .menu .title { font-size: 1.2em; font-weight: bold; padding-bottom: 0.2em; border-bottom: 2px solid #888888; } 33 | #menuwrap .menu a { display: block; padding: 0.5em 1.0em; color: #FAFAFA; text-decoration: none; } 34 | #menuwrap .menu a:hover { background-color: #444444; color :#FFFFFF; } 35 | 36 | #contentwrap.showmenu { white-space: nowrap; } 37 | #contentwrap.showmenu #navbutton { display: table-cell; } 38 | #contentwrap.showmenu .proptitletext { padding-left: 1.0em; } 39 | #contentwrap #navoverflowwrap { display: none; } 40 | #contentwrap.showmenu { overflow: hidden; } 41 | #contentwrap.showmenu #navoverflowwrap { display: block; position: absolute; width: 100%; height: 100%; z-index: 10000; cursor: pointer; background-color: #000000; opacity: 0.25; } 42 | 43 | #contentwrap:not(.nomenu) .proptitle { max-width: none; margin-left: 0; margin-right: 0; display: table-row; } 44 | #contentwrap:not(.nomenu) .proptitletext { display: table-cell; vertical-align: middle; } 45 | #contentwrap:not(.nomenu) .propmessage, #contentwrap:not(.nomenu) .propdesc, #contentwrap:not(.nomenu) .propmain { max-width: none; margin-left: 0; margin-right: 0; } 46 | 47 | @media (max-width: 1000px) { 48 | #menuwrap { display: none; } 49 | #menuwrap.showmenu { display: flex; } 50 | #contentwrap:not(.nomenu) #navbutton { display: table-cell; } 51 | #contentwrap:not(.nomenu) .proptitletext { padding-left: 1.0em; } 52 | } 53 | 54 | a { color: #4E88C2; text-decoration: none; } 55 | a:hover { color: #297ACC; text-decoration: underline; } 56 | 57 | .formitem { margin-top: 1.0em; } 58 | .formitem:first-child { margin-top: 0; } 59 | 60 | .formitemtitle { margin-bottom: 0.2em; font-weight: bold; } 61 | 62 | .formitemdata { position: relative; } 63 | 64 | .formitemdata table.formitemtable { border-collapse: collapse; } 65 | .formitemdata table.formitemtable > thead > tr > th, .formitemdata table.formitemtable > tbody > tr > td { padding: 0.3em 0.5em; border-left: 1px solid #CCCCCC; border-right: 1px solid #CCCCCC; vertical-align: top; } 66 | .formitemdata table.formitemtable > thead > tr > th { background-color: #222222; color: #FAFAFA; font-weight: normal; } 67 | .formitemdata table.formitemtable > thead > tr > th:first-child { border-left: 1px solid #222222; } 68 | .formitemdata table.formitemtable > thead > tr > th:last-child { border-right: 1px solid #222222; } 69 | .formitemdata table.formitemtable > tbody > tr:first-child > td { border-top: none; } 70 | .formitemdata table.formitemtable > tbody > tr:nth-child(even) { background-color: #F6F6F6; } 71 | .formitemdata table.formitemtable > tbody > tr:last-child { border-bottom: 1px solid #CCCCCC; } 72 | .formitemdata table.formitemtable > tbody > tr > td.nowrap { white-space: nowrap; } 73 | 74 | .formitemdata .textitemwrap > input { outline: none; } 75 | .formitemdata .textitemwrap > input.text { box-sizing: border-box; width: 100%; font-size: 0.9em; padding: 0.3em; border: 1px solid #BBBBBB; } 76 | .formitemdata .textitemwrap > input.text:focus, .formitemdata .textitemwrap > input.text:hover { border: 1px solid #888888; } 77 | 78 | .formitemdata .textareawrap > textarea { box-sizing: border-box; width: 100%; font-size: 0.9em; padding: 0.3em; border: 1px solid #BBBBBB; outline: none; } 79 | .formitemdata .textareawrap > textarea:focus, .formitemdata .textareawrap > textarea:hover { border: 1px solid #888888; } 80 | 81 | .formitemdata .selectitemwrap > select { box-sizing: border-box; width: 100%; font-size: 0.9em; padding: 0.3em; border: 1px solid #BBBBBB; outline: none; } 82 | .formitemdata .selectitemwrap > select:focus, .formitemdata .selectitemwrap > select:hover { border: 1px solid #888888; } 83 | 84 | .formitemdata .radioitemwrap, .formitemdata .checkboxitemwrap { margin-left: 1.7em; text-indent: -1.7em; } 85 | 86 | .formitemdata .staticwrap { font-size: 0.9em; } 87 | 88 | .fieldtablewrap { margin-top: 1.0em; } 89 | .fieldtablewrap > table.rowwrap { border-collapse: collapse; width: 100%; } 90 | .fieldtablewrap > table.rowwrap > tbody > tr > td { padding: 1.0em 1.0em 0 0; vertical-align: top; } 91 | .fieldtablewrap > table.rowwrap > tbody > tr:first-child > td { padding-top: 0; } 92 | .formfieldsresponsive .fieldtablewrap > table.rowwrap > tbody > tr > td:nth-last-child(2) { padding-right: 0; } 93 | .fieldtablewrap > table.rowwrap > tbody > tr > td:last-child { padding-right: 0; } 94 | .fieldtablewrap .formitemtitle { white-space: nowrap; } 95 | 96 | hr { margin: 1.0em 0 0 0; border: none; border-top: 1px solid #CCCCCC; } 97 | 98 | .formitemdesc { color: #333333; margin-top: 0.1em; margin-left: 0.5em; font-size: 0.9em; } 99 | 100 | .formitemresult { margin-left: 0.5em; font-size: 0.9em; } 101 | .formitemresult .formitemerror { background: url('flex_forms_error.png') 0 0.1em no-repeat; padding-left: 25px; color: #A94442; font-weight: bold; } 102 | 103 | form.ff_form .formsubmit { margin-top: 1.2em; margin-right: 1.0em; } 104 | form.ff_form .formsubmit input { margin-right: 1.0em; padding: 0.3em 1.0em; font-weight: bold; font-size: 1.0em; background-color: #222222; color: #FAFAFA; border: 1px solid #222222; } 105 | form.ff_form .formsubmit input:hover { background-color: #444444; color :#FFFFFF; } 106 | 107 | @media (max-width: 420px) { 108 | .fieldtablewrap { margin-top: 0; } 109 | .fieldtablewrap > table.rowwrap { display: block; } 110 | .fieldtablewrap > table.rowwrap > tbody { display: block; } 111 | .fieldtablewrap > table.rowwrap > tbody > tr { display: block; } 112 | .fieldtablewrap > table.rowwrap > tbody > tr > td { display: block; padding: 0; margin-top: 1.0em; max-width: 100%; } 113 | .formfieldsresponsive .fieldtablewrap > table.rowwrap > tbody > tr > td:last-child { display: none; } 114 | } 115 | 116 | 117 | /* FlexForms Extras styles for Admin Pack */ 118 | 119 | .formaccordionwrap.ui-accordion h3.ui-accordion-header { margin-top: 1.0em; border: 1px solid #CCCCCC; background: #F6F6F6; font-weight: bold; color: #222222; } 120 | .formaccordionwrap.ui-accordion:first-child h3.ui-accordion-header:first-child { margin-top: 0; } 121 | .formaccordionwrap.ui-accordion h3.ui-accordion-header.ui-state-hover { border: 1px solid #C5C5C5; background: #F0F0F0; font-weight: bold; color: #222222; } 122 | .formaccordionwrap.ui-accordion h3.ui-accordion-header .ui-icon { background-image: url("jquery_ui_themes/adminpack/images/ui-icons_333333_256x240.png"); } 123 | .formaccordionwrap.ui-accordion h3.ui-accordion-header.ui-state-active { border: 1px solid #444444; background: #222222; font-weight: bold; color: #ffffff; } 124 | .formaccordionwrap.ui-accordion h3.ui-accordion-header.ui-state-active .ui-icon { background-image: url("jquery_ui_themes/adminpack/images/ui-icons_fafafa_256x240.png"); } 125 | .formaccordionwrap.ui-accordion h3.ui-accordion-header.ui-state-active.ui-state-hover { border: 1px solid #444444; background: #333333; font-weight: bold; color: #ffffff; } 126 | 127 | .formaccordionwrap.ui-accordion .ui-helper-reset { line-height: normal; } 128 | .formaccordionwrap.ui-accordion .formaccordionitems.ui-widget-content a { color: #4E88C2; text-decoration: none; } 129 | .formaccordionwrap.ui-accordion .formaccordionitems.ui-widget-content a:hover { color: #297ACC; text-decoration: underline; } 130 | .formaccordionwrap.ui-accordion .formaccordionitems.ui-accordion-content { padding: 0 1.5em 1.0em; } 131 | .formaccordionwrap.ui-accordion .formaccordionitems.ui-accordion-content > .formitem:first-child { margin-top: 1.0em; } 132 | 133 | .formitemdata input.date { box-sizing: border-box; width: 100%; font-size: 0.9em; padding: 0.3em; border: 1px solid #BBBBBB; } 134 | .formitemdata input.date:focus, .formitemdata input.date:hover { border: 1px solid #888888; } 135 | 136 | .ui-state-default.ui-state-highlight, .ui-widget-content .ui-state-default.ui-state-highlight { border: 1px solid #c5c5c5; background: #f6f6f6; color: #454545; } 137 | .ui-state-active.ui-state-highlight, .ui-widget-content .ui-state-active.ui-state-highlight { border: 1px solid #444444; background: #222222; color: #ffffff; } 138 | .ui-state-hover.ui-state-highlight, .ui-widget-content .ui-state-hover.ui-state-highlight { border: 1px solid #444444; background: #333333; color: #ffffff; } 139 | 140 | .formitemdata .uix-multiselect .ui-widget-header div.header-text { white-space: normal; } 141 | .formitemdata .uix-multiselect .ui-widget-header { padding-top: 0.3em; padding-bottom: 0.3em; } 142 | .formitemdata .uix-multiselect .ui-widget-header .uix-control-right { padding-top: 0.8em; } 143 | 144 | .formitemdata .ui-multiselect { padding: 0.3em; } 145 | .formitemdata .ui-multiselect.ui-widget.ui-state-default { border: 1px solid #444444; background: #333333; color: #ffffff; } 146 | .formitemdata .ui-multiselect.ui-widget.ui-state-default span.ui-icon { background-image: url("jquery_ui_themes/adminpack/images/ui-icons_ffffff_256x240.png"); } 147 | .formitemdata .ui-multiselect.ui-widget.ui-state-active { border: 1px solid #444444; background: #222222; color: #ffffff; } 148 | .formitemdata .ui-multiselect.ui-widget.ui-state-hover { border: 1px solid #444444; background: #222222; color: #ffffff; } 149 | .formitemdata .ui-multiselect span.ui-icon { margin-top: 0.2em; } 150 | 151 | .formitemdata .select2-container { display: block; } 152 | .formitemdata .select2-container-multi .select2-choices .select2-search-choice { line-height: 17px; } 153 | .formitemdata .select2-search-choice-close { top: 5px; } 154 | .select2-results .select2-highlighted { background-color: #444444 !important; } 155 | 156 | .formitemdata .draghandle { cursor: move; } 157 | .formitemdata .dragactive { background-color: #E3E3E3; } 158 | 159 | .formitemdata table.tablecards > thead > th:nth-last-child(2) { border-right: 1px solid #222222; } 160 | .formitemdata table.tablecards.tablecard-show > thead > th:nth-last-child(2) { border-right: 1px solid #CCCCCC; } 161 | .formitemdata table.tablecard-show { width: 100%; } 162 | .formitemdata table.tablecard-show-nohead > tbody > tr:first-child > td { border-top: 1px solid #CCCCCC; } 163 | 164 | .formitemdata .tablebodyscroll-scroller3 > table.tablecard-show-nohead > thead > tr { display: none; } 165 | 166 | .formitemdata .tablebodyscroll-shadow-top .tablebodyscroll-scroller-shadow-top, .formitemdata .tablebodyscroll-shadow-both .tablebodyscroll-scroller-shadow-top { border-top: 1px solid #CCCCCC; } 167 | 168 | 169 | /* FlexForms Modules styles for Admin Pack */ 170 | 171 | .formitemdata table.tablecard-show .calendar_day_of_week { margin-top: 0.5em; border-top: 1px solid #E0E0E0; padding-top: 0.5em; font-weight: bold; } 172 | .formitemdata table.tablecard-show .calendar_day_of_week:first-child { margin-top: 0; border-top: none; padding-top: 0; } 173 | 174 | .formitemdata .tablefiltersearchwrap { margin-bottom: 0.5em; } 175 | .formitemdata table.ff_tablefilter > tbody > tr.visible { background-color: #FFFFFF; } 176 | .formitemdata table.ff_tablefilter > tbody > tr.visible.altrow { background-color: #F6F6F6; } 177 | .formitemdata table.ff_tablefilter > tbody > tr.visible.lastrow { border-bottom: 1px solid #CCCCCC; } 178 | -------------------------------------------------------------------------------- /server/support/admin.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | // For handling menu scroll. 3 | function GetScrollLineHeight() { 4 | var iframe = document.createElement('iframe'); 5 | iframe.src = '#'; 6 | document.body.appendChild(iframe); 7 | var iwin = iframe.contentWindow; 8 | var idoc = iwin.document; 9 | idoc.open(); 10 | idoc.write('a'); 11 | idoc.close(); 12 | var span = idoc.body.firstElementChild; 13 | var r = span.offsetHeight; 14 | document.body.removeChild(iframe); 15 | 16 | return r; 17 | } 18 | 19 | var scrolllineheight = GetScrollLineHeight(); 20 | var showingmenu = false; 21 | 22 | $('.proptitlewrap').after($('').click(function() { 23 | $('#menuwrap').toggleClass('showmenu'); 24 | $('#contentwrap').toggleClass('showmenu'); 25 | 26 | showingmenu = !showingmenu; 27 | })); 28 | 29 | $('#navbutton').click(function() { 30 | $('#menuwrap').toggleClass('showmenu'); 31 | $('#contentwrap').toggleClass('showmenu'); 32 | 33 | showingmenu = !showingmenu; 34 | }); 35 | 36 | var scrolltarget = $('#contentwrap').get(0); 37 | 38 | $('#menuwrap').on('wheel', function(e) { 39 | var $this = $(this); 40 | 41 | if (!showingmenu && $this.get(0).scrollHeight <= $this.innerHeight()) 42 | { 43 | var o = e.originalEvent; 44 | var mult = (o.deltaMode == 1 ? scrolllineheight * 2 : (o.deltaMode == 2 ? $(window).height() - (2 * scrolllineheight) : 1)); 45 | 46 | scrolltarget.scrollTop += o.deltaY * mult; 47 | scrolltarget.scrollLeft += o.deltaX * mult; 48 | } 49 | }); 50 | 51 | $('#contentwrap').focus(); 52 | }); -------------------------------------------------------------------------------- /server/support/admin_bulkedit.css: -------------------------------------------------------------------------------- 1 | html, body { height: 100%; margin: 0px; padding: 0px; overflow: hidden; background-color: #FFFFFF; } 2 | body { font-family: Verdana, Arial, Helvetica, sans-serif; position: relative; } 3 | 4 | #sidebarwrap { float: left; width: 350px; min-width: 200px; max-width: 25%; height: 100%; overflow: auto; border-right: 1px solid #CCCCCC; font-size: 0.9em; } 5 | #sidebarwrap a { display: block; padding: 0.3em 0.5em; color: #035488; text-decoration: none; background-color: #FAFAFA; } 6 | #sidebarwrap a.altrow { background-color: #F5F5F5; } 7 | #sidebarwrap a:hover { background-color: #EEEEEE; } 8 | #sidebarwrap a.selected { color: #FEFEFE; background-color: #035488; } 9 | #sidebarwrap a.selected:hover { background-color: #035488; } 10 | 11 | #topbarwrap { border-bottom: 1px solid #CCCCCC; font-size: 0.9em; padding: 0.7em; overflow: hidden; } 12 | #maincontentwrap { overflow: auto; } 13 | #bottombarwrap { border-top: 1px solid #CCCCCC; font-size: 0.9em; padding: 0.7em; overflow: hidden; } 14 | 15 | #ajaxhidden { display: none; } 16 | 17 | @media (max-width: 600px) { 18 | html, body { overflow: auto; } 19 | #sidebarwrap { float: none; width: auto; min-width: 0; max-width: none; height: 60%; border-right: none; border-bottom: 1px solid #CCCCCC; } 20 | } 21 | -------------------------------------------------------------------------------- /server/support/admin_bulkedit.js: -------------------------------------------------------------------------------- 1 | function BB_UpdateLayout() { 2 | var barheight = 0; 3 | var topbar = $('#topbarwrap'); 4 | if (!topbar.children().length) topbar.hide(); 5 | else 6 | { 7 | topbar.show(); 8 | barheight += topbar.outerHeight(); 9 | } 10 | 11 | var bottombar = $('#bottombarwrap'); 12 | if (!bottombar.children().length) $('#bottombarwrap').hide(); 13 | else 14 | { 15 | bottombar.show(); 16 | barheight += bottombar.outerHeight(); 17 | } 18 | 19 | $('#maincontentwrap').outerHeight($(window).height() - barheight); 20 | } 21 | 22 | function BB_StripeSidebar() { 23 | $('#sidebarwrap a').removeClass('altrow').filter(':visible:odd').addClass('altrow'); 24 | } 25 | 26 | function BB_SelectSidebarItem(obj) { 27 | $('#sidebarwrap a').removeClass('selected'); 28 | $(obj).addClass('selected'); 29 | 30 | return false; 31 | } 32 | 33 | $(function() { 34 | BB_StripeSidebar(); 35 | BB_UpdateLayout(); 36 | 37 | $(window).on('resize', BB_UpdateLayout); 38 | }); -------------------------------------------------------------------------------- /server/support/admin_print.css: -------------------------------------------------------------------------------- 1 | html, body { height: auto; overflow: visible; } 2 | 3 | #menuwrap { display: none; } 4 | #menuwrap.showmenu { display: none; } 5 | 6 | #contentwrap { height: auto; overflow: hidden; } 7 | #contentwrap .proptitlewrap { background-color: #222222; color: #FAFAFA; padding: 0.85em; box-shadow: none; border-bottom: 1px solid #CCCCCC; } 8 | #contentwrap.showmenu #navoverflowwrap { display: none; } 9 | #contentwrap #navbutton { display: none; } 10 | #contentwrap .proptitletext { padding-left: 0em; } 11 | 12 | .formitemdata table.formitemtable > thead > tr > th, .formitemdata table.formitemtable > tbody > tr > td { border-top: 1px solid #CCCCCC; } 13 | .formitemdata table.formitemtable > thead > tr > th:first-child { border-left: 1px solid #CCCCCC; } 14 | .formitemdata table.formitemtable > thead > tr > th:last-child { border-right: 1px solid #CCCCCC; } 15 | .formitemdata table.formitemtable > tbody > tr:first-child > td { border-top: 1px solid #CCCCCC; } 16 | 17 | form.ff_form .formsubmit input { background-color: #FAFAFA; color: #222222; border: 1px solid #222222; } 18 | 19 | .ui-multiselect { width: 100% !important; } 20 | .formitemdata .ui-multiselect.ui-widget { border: 1px solid #c5c5c5 !important; background: #ffffff !important; color: #222222 !important; } 21 | 22 | .multiselect-available-list { display: none; } 23 | .multiselect-selected-list { left: 0 !important; width: 100% !important; } 24 | 25 | .no-print { display: none; } 26 | -------------------------------------------------------------------------------- /server/support/admin_view.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | border: 0; 5 | font-family: Verdana, Arial, Helvetica, sans-serif; 6 | max-width: 800px; 7 | margin: 0 auto; 8 | } 9 | 10 | .pagewrap { 11 | margin: 0 5px 5px 5px; 12 | } 13 | 14 | .pagewrap .headerwrap { 15 | text-align: center; 16 | font-weight: bold; 17 | font-size: 1.7em; 18 | border-left: 1px solid #BBBBBB; 19 | border-right: 1px solid #BBBBBB; 20 | border-bottom: 1px solid #BBBBBB; 21 | background-color: #F5F5F5; 22 | padding: 0.1em; 23 | border-bottom-right-radius: 3px; 24 | border-bottom-left-radius: 3px; 25 | -moz-border-radius-bottomright: 3px; 26 | -moz-border-radius-bottomleft: 3px; 27 | -webkitborder-bottom-right-radius: 3px; 28 | -webkitborder-bottom-left-radius: 3px; 29 | } 30 | 31 | .pagewrap .contentwrap { 32 | margin-top: 10px; 33 | } 34 | 35 | .pagewrap .menuwrap { 36 | margin-top: 7px; 37 | border-top: 1px solid #CCCCCC; 38 | padding: 25px 0 10px; 39 | } 40 | 41 | /* Basic styles for elements. */ 42 | img { 43 | border: 0px none; 44 | margin: 0px; 45 | } 46 | 47 | form { 48 | border: 0px none; 49 | margin: 0px; 50 | } 51 | 52 | a { 53 | color: #035488; 54 | text-decoration: none; 55 | } 56 | 57 | a:hover { 58 | color: #444444; 59 | text-decoration: underline; 60 | } 61 | 62 | /* Optional message styles. */ 63 | .message { 64 | margin: 10px 7px; 65 | font-weight: bold; 66 | padding: 7px 15px; 67 | border-radius: 3px; 68 | -moz-border-radius: 3px; 69 | -webkit-border-radius: 3px; 70 | } 71 | 72 | .message .success { 73 | background-color: #E8FCDC; 74 | border: 1px solid #008800; 75 | color: #008800; 76 | } 77 | 78 | .message .error { 79 | background-color: #FCDCDC; 80 | border: 1px solid #880000; 81 | color: #880000; 82 | } 83 | 84 | .message .info { 85 | background-color: #DCDCDC; 86 | border: 1px solid #333333; 87 | color: #333333; 88 | } 89 | 90 | .maincontent { 91 | } 92 | 93 | .maincontent .proptitle { 94 | font-weight: bold; 95 | font-size: 1.3em; 96 | color: #1F1F1F; 97 | } 98 | 99 | .maincontent .propdesc { 100 | margin-top: 5px; 101 | padding: 10px 7px; 102 | border-top: 1px solid #CCCCCC; 103 | color: #333333; 104 | font-size: 1.0em; 105 | } 106 | 107 | .maincontent .propinfo { 108 | } 109 | 110 | .maincontent .propmain { 111 | margin: 0px 7px; 112 | } 113 | 114 | .maincontent .propmain .formfields { 115 | font-size: 0.9em; 116 | } 117 | 118 | .maincontent .propmain .formfields .formaccordionwrap.ui-accordion { 119 | margin-bottom: 7px; 120 | } 121 | 122 | .maincontent .propmain .formfields .formaccordionwrap.ui-accordion h3.ui-accordion-header { 123 | margin-top: 7px; 124 | } 125 | 126 | .maincontent .propmain .formfields .formaccordionwrap.ui-accordion h3.ui-state-active { 127 | background: url("jquery_ui_themes/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png") repeat-x scroll 50% 50% #E6E6E6; 128 | } 129 | 130 | .maincontent .propmain .formfields .formaccordionwrap.ui-accordion .formaccordionitems.ui-accordion-content { 131 | padding: 0 14px; 132 | background: none; 133 | background-color: #FEFEFE; 134 | } 135 | 136 | .maincontent .propmain .formfields .formaccordionwrap.ui-accordion .formaccordionitems.ui-widget-content a { 137 | color: #035488; 138 | } 139 | 140 | .maincontent .propmain .formfields .formaccordionwrap.ui-accordion .formaccordionitems.ui-accordion-content .formitem { 141 | margin-top: 6px; 142 | border-top: 1px dotted #CCCCCC; 143 | padding-top: 6px; 144 | } 145 | 146 | .maincontent .propmain .formfields .formaccordionwrap.ui-accordion .formaccordionitems.ui-accordion-content .formitem.firstitem { 147 | border-top: 0; 148 | } 149 | 150 | .maincontent .propmain .formfields .formitem { 151 | margin: 0.9em 0 0.5em; 152 | } 153 | 154 | .maincontent .propmain .formfields .formitem .formitemtitle { 155 | font-weight: bold; 156 | margin-bottom: 2px; 157 | } 158 | 159 | .maincontent .propmain .formfields .formitem .staticwrap, .maincontent .propmain .formfields .formitem .static { 160 | margin-left: 7px; 161 | width: 95%; 162 | font-size: 0.95em; 163 | color: #333333; 164 | } 165 | 166 | .maincontent .propmain .formfields .formitem input.text { 167 | padding: 0.5em; 168 | width: 95%; 169 | border: 1px solid #BBBBBB; 170 | } 171 | 172 | .maincontent .propmain .formfields .formitem input.text:hover { 173 | border: 1px solid #888888; 174 | } 175 | 176 | .maincontent .propmain .formfields .formitem input.text:focus { 177 | border: 1px solid #888888; 178 | } 179 | 180 | .maincontent .propmain .formfields .formitem input.date { 181 | padding: 0.5em; 182 | max-width: 20em; 183 | width: 95%; 184 | border: 1px solid #BBBBBB; 185 | } 186 | 187 | .maincontent .propmain .formfields .formitem input.date:hover { 188 | border: 1px solid #888888; 189 | } 190 | 191 | .maincontent .propmain .formfields .formitem input.date:focus { 192 | border: 1px solid #888888; 193 | } 194 | 195 | .maincontent .propmain .formfields .formitem .textareawrap { 196 | width: 95%; 197 | } 198 | 199 | .maincontent .propmain .formfields .formitem textarea.text { 200 | padding: 0.3em; 201 | width: 100%; 202 | border: 1px solid #BBBBBB; 203 | } 204 | 205 | .maincontent .propmain .formfields .formitem textarea.text:hover { 206 | border: 1px solid #888888; 207 | } 208 | 209 | .maincontent .propmain .formfields .formitem textarea.text:focus { 210 | border: 1px solid #888888; 211 | } 212 | 213 | .maincontent .propmain .formfields .formitem input.checkbox { 214 | border: 1px solid #BBBBBB; 215 | } 216 | 217 | .maincontent .propmain .formfields .formitem select { 218 | padding: 0.3em; 219 | width: 95%; 220 | max-width: 750px; 221 | border: 1px solid #BBBBBB; 222 | } 223 | 224 | .maincontent .propmain .formfields .formitem select:hover { 225 | border: 1px solid #888888; 226 | } 227 | 228 | .maincontent .propmain .formfields .formitem select:focus { 229 | border: 1px solid #888888; 230 | } 231 | 232 | .maincontent .propmain .formfields .formitem table.viewtable td { 233 | vertical-align: top; 234 | } 235 | 236 | .maincontent .propmain .formfields .formitem table.viewtable td.datakey { 237 | padding-right: 2.0em; 238 | font-weight: bold; 239 | white-space: nowrap; 240 | } 241 | 242 | .maincontent .propmain .formfields .formitem .formitemdesc { 243 | margin-left: 7px; 244 | font-size: 0.9em; 245 | color: #333333; 246 | } 247 | 248 | .maincontent .propmain .formsubmit { 249 | margin: 15px auto; 250 | text-align: center; 251 | } 252 | 253 | .maincontent .propmain .formsubmit input.submit { 254 | color: #1F1F1F; 255 | font-size: 0.9em; 256 | font-weight: bold; 257 | padding: 0.2em 0.5em; 258 | } 259 | 260 | .maincontent .propmain hr { 261 | margin-top: 0.7em; 262 | border: none 0; 263 | border-top: 1px dashed #AAAAAA; 264 | height: 1px; 265 | } 266 | 267 | .maincontent .propmain .nontablewrap { 268 | border-left: 1px solid #CCCCCC; 269 | border-right: 1px solid #CCCCCC; 270 | border-bottom: 1px solid #CCCCCC; 271 | color: #333333; 272 | } 273 | 274 | .maincontent .propmain .nontable_row { 275 | background-color: #F5F5F5; 276 | border-top: 1px dashed #CCCCCC; 277 | padding: 0.5em; 278 | } 279 | 280 | .maincontent .propmain .nontable_row.firstrow { 281 | border-top: 1px solid #CCCCCC; 282 | } 283 | 284 | .maincontent .propmain .nontable_row.altrow { 285 | background-color: #FEFEFE; 286 | } 287 | 288 | .maincontent .propmain .nontable_th { 289 | margin-top: 0.4em; 290 | font-weight: bold; 291 | } 292 | 293 | .maincontent .propmain .nontable_th.firstcol { 294 | margin-top: 0; 295 | } 296 | 297 | .maincontent .propmain .nontable_td { 298 | margin-left: 7px; 299 | color: #444444; 300 | } 301 | 302 | .maincontent .propmain .mediawrap { 303 | float: right; 304 | margin-left: 2.0em; 305 | max-width: 45%; 306 | margin-bottom: 0.5em; 307 | } 308 | 309 | @media (max-width: 600px) { 310 | .maincontent .propmain .mediawrap { 311 | float: none; 312 | margin-left: 0; 313 | max-width: none; 314 | } 315 | } 316 | 317 | .maincontent .propmain .mediawrap .mediaitemtitle { 318 | font-weight: bold; 319 | margin-bottom: 0.3em; 320 | } 321 | 322 | .maincontent .propmain .mediawrap .mediaitem { 323 | max-width: 100%; 324 | } 325 | 326 | .maincontent .propmain .mediawrap .mediaitemdesc { 327 | font-size: 0.9em; 328 | color: #333333; 329 | } 330 | 331 | /* Optional menu navigation styles. */ 332 | .menuwrap { 333 | } 334 | 335 | .menuwrap .menu { 336 | font-size: 0.7em; 337 | border: 1px solid #BBBBBB; 338 | background-color: #F5F5F5; 339 | margin-top: 7px; 340 | padding: 5px; 341 | } 342 | 343 | .menuwrap .menu .titlewrap { 344 | font-weight: bold; 345 | padding-bottom: 6px; 346 | } 347 | 348 | .menuwrap .menu a { 349 | display: block; 350 | border-top: 1px solid #CCCCCC; 351 | padding: 12px 15px; 352 | } 353 | 354 | .menuwrap .menu a:hover { 355 | background-color: #EAEAEA; 356 | text-decoration: none; 357 | } 358 | 359 | /* Floating navigation menu styles. */ 360 | .maincontent .proptitle #navbutton { 361 | display: none; 362 | } 363 | 364 | .maincontent .proptitle #navdropdown { 365 | display: none; 366 | } 367 | -------------------------------------------------------------------------------- /server/support/admin_view_print.css: -------------------------------------------------------------------------------- 1 | .pagewrap .menuwrap { 2 | display: none; 3 | } 4 | -------------------------------------------------------------------------------- /server/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 | ?> -------------------------------------------------------------------------------- /server/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 | ?> -------------------------------------------------------------------------------- /server/support/en_us_lite.json: -------------------------------------------------------------------------------- 1 | { 2 | "consonants": { "b": 50602, "c": 115771, "d": 90394, "f": 31570, "g": 74641, "h": 72887, "j": 4649, "k": 23626, "l": 146940, "m": 83734, "n": 190792, "p": 86539, "q": 4530, "r": 195116, "s": 261974, "t": 185092, "v": 25672, "w": 19402, "x": 8053, "z": 13142 }, 3 | "vowels": { "a": 222822, "e": 311897, "i": 255488, "o": 193425, "u": 92494, "y": 47630 } 4 | } -------------------------------------------------------------------------------- /server/support/flex_forms_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cubiclesoft/php-obsmanager/e6c3459444a6fb8153b53fec70586b754ba7a6a1/server/support/flex_forms_error.png -------------------------------------------------------------------------------- /server/support/page_basics.php: -------------------------------------------------------------------------------- 1 | SetSecretKey($bb_randpage . ":" . $bb_usertoken); 16 | 17 | return parent::CreateSecurityToken($action, $extra); 18 | } 19 | } 20 | 21 | // Can be used to override default functionality with an extended class. However, form handlers are generally a better solution. 22 | $bb_flexforms = new BB_FlexForms(); 23 | 24 | // Code swiped from Barebones CMS support functions. 25 | function BB_JSSafe($data) 26 | { 27 | return FlexForms::JSSafe($data); 28 | } 29 | 30 | function BB_IsSSLRequest() 31 | { 32 | return FlexForms::IsSSLRequest(); 33 | } 34 | 35 | // Returns 'http[s]://www.something.com[:port]' based on the current page request. 36 | function BB_GetRequestHost($protocol = "") 37 | { 38 | return FlexForms::GetRequestHost($protocol); 39 | } 40 | 41 | function BB_GetRequestURLBase() 42 | { 43 | return FlexForms::GetRequestURLBase(); 44 | } 45 | 46 | function BB_GetFullRequestURLBase($protocol = "") 47 | { 48 | return FlexForms::GetFullRequestURLBase($protocol); 49 | } 50 | 51 | // Multilingual admin functions. 52 | function BB_Translate() 53 | { 54 | global $bb_admin_lang, $bb_admin_def_lang, $bb_langmap; 55 | 56 | $args = func_get_args(); 57 | if (!count($args) || $args[0] == "") return ""; 58 | if (isset($bb_admin_lang) && isset($bb_admin_def_lang) && isset($bb_langmap)) 59 | { 60 | $arg = $args[0]; 61 | if (isset($bb_langmap[$bb_admin_lang]) && isset($bb_langmap[$bb_admin_lang][$arg])) $args[0] = $bb_langmap[$bb_admin_lang][$arg]; 62 | else if (isset($bb_langmap[$bb_admin_def_lang]) && isset($bb_langmap[$bb_admin_def_lang][$arg])) $args[0] = $bb_langmap[$bb_admin_def_lang][$arg]; 63 | else if (isset($bb_langmap[""][$arg])) $args[0] = $bb_langmap[""][$arg]; 64 | else if (function_exists("BB_Untranslated")) BB_Untranslated($args); 65 | } 66 | if (count($args) == 1 && strpos($args[0], "%") !== false) $args[0] = str_replace("%", "%%", $args[0]); 67 | 68 | return call_user_func_array("sprintf", $args); 69 | } 70 | 71 | function BB_PostTranslate($str) 72 | { 73 | global $bb_admin_lang, $bb_admin_def_lang, $bb_langmap; 74 | 75 | if (isset($bb_admin_lang) && isset($bb_admin_def_lang) && isset($bb_langmap)) 76 | { 77 | if (isset($bb_langmap[$bb_admin_lang]) && isset($bb_langmap[$bb_admin_lang][""]) && is_array($bb_langmap[$bb_admin_lang][""])) $str = str_replace($bb_langmap[$bb_admin_lang][""][0], $bb_langmap[$bb_admin_lang][""][1], $str); 78 | else if (isset($bb_langmap[$bb_admin_def_lang]) && isset($bb_langmap[$bb_admin_def_lang][""]) && is_array($bb_langmap[$bb_admin_def_lang][""])) $str = str_replace($bb_langmap[$bb_admin_def_lang][""][0], $bb_langmap[$bb_admin_def_lang][""][1], $str); 79 | else if (isset($bb_langmap[""][""]) && is_array($bb_langmap[""][""])) $str = str_replace($bb_langmap[""][""][0], $bb_langmap[""][""][1], $str); 80 | } 81 | 82 | return $str; 83 | } 84 | 85 | function BB_FormatTimestamp($format, $ts) 86 | { 87 | return BB_PostTranslate(date(BB_Translate($format), $ts)); 88 | } 89 | 90 | function BB_SetLanguage($path, $lang) 91 | { 92 | global $bb_langmap, $bb_admin_lang; 93 | 94 | $lang = preg_replace('/\s+/', "_", trim(preg_replace('/[^a-z]/', " ", strtolower($lang)))); 95 | if ($lang == "") 96 | { 97 | $path .= "default/"; 98 | } 99 | else 100 | { 101 | if ($lang == "default") return array("success" => false, "error" => "Invalid language."); 102 | $path .= $lang . "/"; 103 | } 104 | 105 | if (isset($bb_langmap[$lang])) 106 | { 107 | if ($lang != "") $bb_admin_lang = $lang; 108 | 109 | return array("success" => true); 110 | } 111 | $bb_langmap[$lang] = array(); 112 | 113 | $dir = @opendir($path); 114 | if ($dir === false) return array("success" => false, "error" => "Directory does not exist.", "info" => $path); 115 | 116 | while (($file = readdir($dir)) !== false) 117 | { 118 | if (strtolower(substr($file, -4)) == ".php") require_once $path . $file; 119 | } 120 | 121 | closedir($dir); 122 | 123 | if (isset($bb_langmap[$lang][""]) && is_array($bb_langmap[$lang][""])) $bb_langmap[$lang][""] = array(array_keys($bb_langmap[$lang][""]), array_values($bb_langmap[$lang][""])); 124 | 125 | $bb_admin_lang = $lang; 126 | 127 | return array("success" => true); 128 | } 129 | 130 | function BB_InitLangmap($path, $default = "") 131 | { 132 | global $bb_admin_lang, $bb_admin_def_lang, $bb_langmap; 133 | 134 | $bb_langmap = array(); 135 | BB_SetLanguage($path, ""); 136 | if ($default != "") BB_SetLanguage($path, $default); 137 | if (isset($bb_admin_lang)) $bb_admin_def_lang = $bb_admin_lang; 138 | if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) 139 | { 140 | $langs = explode(",", $_SERVER["HTTP_ACCEPT_LANGUAGE"]); 141 | foreach ($langs as $lang) 142 | { 143 | $lang = trim($lang); 144 | $pos = strpos($lang, ";"); 145 | if ($pos !== false) $lang = substr($lang, 0, $pos); 146 | if ($lang != "") 147 | { 148 | $result = BB_SetLanguage($path, $lang); 149 | if ($result["success"]) 150 | { 151 | if (!isset($bb_admin_def_lang) && isset($bb_admin_lang)) $bb_admin_def_lang = $bb_admin_lang; 152 | 153 | break; 154 | } 155 | } 156 | } 157 | } 158 | } 159 | 160 | // Route all translation requests through BB_Translate(). 161 | if (!defined("CS_TRANSLATE_FUNC")) define("CS_TRANSLATE_FUNC", "BB_Translate"); 162 | 163 | 164 | // Code swiped from CubicleSoft browser cookie support functions. 165 | function SetCookieFixDomain($name, $value = "", $expires = 0, $path = "", $domain = "", $secure = false, $httponly = false) 166 | { 167 | if (!empty($domain)) 168 | { 169 | // Remove port information. 170 | $pos = strpos($domain, "]"); 171 | if (substr($domain, 0, 1) == "[" && $pos !== false) $domain = substr($domain, 0, $pos + 1); 172 | else 173 | { 174 | $port = strpos($domain, ":"); 175 | if ($port !== false) $domain = substr($domain, 0, $port); 176 | 177 | // Fix the domain to accept domains with and without 'www.'. 178 | if (strtolower(substr($domain, 0, 4)) == "www.") $domain = substr($domain, 4); 179 | if (strpos($domain, ".") === false) $domain = ""; 180 | else if (substr($domain, 0, 1) != ".") $domain = "." . $domain; 181 | } 182 | } 183 | 184 | header("Set-Cookie: " . rawurlencode($name) . "=" . rawurlencode($value) 185 | . (empty($expires) ? "" : "; expires=" . gmdate("D, d-M-Y H:i:s", $expires) . " GMT") 186 | . (empty($path) ? "" : "; path=" . $path) 187 | . (empty($domain) ? "" : "; domain=" . $domain) 188 | . (!$secure ? "" : "; secure") 189 | . (!$httponly ? "" : "; HttpOnly"), false); 190 | } 191 | 192 | 193 | $bb_errors = array(); 194 | 195 | function BB_RegisterPropertyFormHandler($mode, $callback) 196 | { 197 | FlexForms::RegisterFormHandler($mode, $callback); 198 | } 199 | 200 | // Originally from Barebones CMS editing routines. Most functionality has now transitioned to FlexForms. 201 | function BB_PropertyForm($options) 202 | { 203 | global $bb_message_layout, $bb_formtables, $bb_formwidths, $bb_flexforms, $bb_errors; 204 | 205 | if (!isset($bb_formtables) || !is_bool($bb_formtables)) $bb_formtables = true; 206 | if (!isset($bb_formwidths) || !is_bool($bb_formwidths)) $bb_formwidths = true; 207 | 208 | // Certain types of fields require the Admin Pack extras package. 209 | if (defined("BB_ROOT_URL")) $rooturl = BB_ROOT_URL; 210 | else if (defined("ROOT_URL")) $rooturl = ROOT_URL; 211 | else 212 | { 213 | $rooturl = BB_GetRequestURLBase(); 214 | if (substr($rooturl, -1) != "/") $rooturl = str_replace("\\", "/", dirname($rooturl)); 215 | if (substr($rooturl, -1) == "/") $rooturl = substr($rooturl, 0, -1); 216 | } 217 | 218 | if (defined("BB_SUPPORT_PATH")) $supportpath = BB_SUPPORT_PATH; 219 | else if (defined("SUPPORT_PATH")) $supportpath = SUPPORT_PATH; 220 | else $supportpath = "support"; 221 | 222 | // Initialize the FlexForms class instance. Override a few defaults for AdminPack template integration. 223 | $bb_flexforms->SetState(array("supporturl" => $rooturl . "/" . $supportpath, "formtables" => $bb_formtables, "formwidths" => $bb_formwidths, "jqueryuitheme" => "adminpack")); 224 | $bb_flexforms->SetJSOutput("jquery"); 225 | $bb_flexforms->SetCSSOutput("formcss"); 226 | 227 | // AdminPack provides its own security token logic and doesn't need stringent randomized field support. 228 | unset($options["hashnames"]); 229 | 230 | ?> 231 |
232 | 241 |
">
242 | 245 |
246 |
247 | Generate($options, $bb_errors); 249 | ?> 250 |
251 | $size) $key = $algo($key, true); 267 | $key = str_pad($key, $size, "\x00"); 268 | 269 | $y = strlen($key) - 1; 270 | for ($x = 0; $x < $y; $x++) 271 | { 272 | $opad[$x] = $opad[$x] ^ $key[$x]; 273 | $ipad[$x] = $ipad[$x] ^ $key[$x]; 274 | } 275 | 276 | $result = $algo($opad . $algo($ipad . $data, true), $raw_output); 277 | 278 | return $result; 279 | } 280 | } 281 | 282 | // Function swiped from Barebones CMS edit.php. 283 | // Create a valid language-level security token (also known as a 'nonce'). 284 | function BB_CreateSecurityToken($name, $extra = "") 285 | { 286 | global $bb_flexforms; 287 | 288 | return $bb_flexforms->CreateSecurityToken($name, $extra); 289 | } 290 | 291 | function BB_IsSecExtraOpt($opt) 292 | { 293 | global $bb_flexforms; 294 | 295 | return $bb_flexforms->IsSecExtraOpt($opt); 296 | } 297 | 298 | // Custom-built routines specifically for displaying the final page. 299 | function BB_ProcessPageToken($name) 300 | { 301 | global $bb_flexforms; 302 | 303 | return $bb_flexforms->CheckSecurityToken($name); 304 | } 305 | 306 | function BB_GetBackURL($query = array(), $fullrequest = false, $protocol = "") 307 | { 308 | return ($fullrequest ? BB_GetFullRequestURLBase($protocol) : BB_GetRequestURLBase()) . (count($query) ? "?" . implode("&", $query) : ""); 309 | } 310 | 311 | function BB_RedirectPage($msgtype = "", $msg = "", $query = array()) 312 | { 313 | if ($msgtype != "") 314 | { 315 | if (!isset($_REQUEST["bb_msgtype"]) || ($_REQUEST["bb_msgtype"] != "error" && $_REQUEST["bb_msgtype"] != "success" && $_REQUEST["bb_msgtype"] != "info")) $_REQUEST["bb_msgtype"] = $msgtype; 316 | else if ($msgtype == "error") $_REQUEST["bb_msgtype"] = "error"; 317 | else if ($msgtype == "info" && $_REQUEST["bb_msgtype"] != "error") $_REQUEST["bb_msgtype"] = "info"; 318 | else $_REQUEST["bb_msgtype"] = "success"; 319 | 320 | if (!isset($_REQUEST["bb_msg"])) $_REQUEST["bb_msg"] = $msg; 321 | else $_REQUEST["bb_msg"] = $msg . " " . $_REQUEST["bb_msg"]; 322 | 323 | $query[] = "bb_msgtype=" . urlencode($_REQUEST["bb_msgtype"]); 324 | $query[] = "bb_msg=" . urlencode($_REQUEST["bb_msg"]); 325 | } 326 | 327 | header("Location: " . BB_GetBackURL($query, true)); 328 | 329 | exit(); 330 | } 331 | 332 | function BB_SetPageMessage($msgtype, $msg, $field = false) 333 | { 334 | global $bb_errors; 335 | 336 | if (!isset($_REQUEST["bb_msgtype"]) || $msgtype == "error" || ($msgtype == "info" && $_REQUEST["bb_msgtype"] != "error") || ($msgtype == "success" && $_REQUEST["bb_msgtype"] == "success")) 337 | { 338 | $_REQUEST["bb_msgtype"] = $msgtype; 339 | $_REQUEST["bb_msg"] = $msg; 340 | 341 | if ($msgtype == "error" && $field !== false) $bb_errors[$field] = $msg; 342 | } 343 | } 344 | 345 | function BB_GetPageMessageType() 346 | { 347 | return (isset($_REQUEST["bb_msg"]) && isset($_REQUEST["bb_msgtype"]) ? ($_REQUEST["bb_msgtype"] == "error" || $_REQUEST["bb_msgtype"] == "success" ? $_REQUEST["bb_msgtype"] : "info") : ""); 348 | } 349 | 350 | function BB_NormalizeFiles($key) 351 | { 352 | return FlexForms::NormalizeFiles($key); 353 | } 354 | 355 | function BB_GetValue($key, $default) 356 | { 357 | return FlexForms::GetValue($key, $default); 358 | } 359 | 360 | function BB_SelectValues($data) 361 | { 362 | return FlexForms::GetSelectValues($data); 363 | } 364 | 365 | function BB_ProcessInfoDefaults($info, $defaults) 366 | { 367 | return FlexForms::ProcessInfoDefaults($info, $defaults); 368 | } 369 | 370 | function BB_SetNestedPathValue(&$data, $pathparts, $val) 371 | { 372 | return FlexForms::SetNestedPathValue($data, $pathparts, $val); 373 | } 374 | 375 | function BB_GetIDDiff($origids, $newids) 376 | { 377 | return FlexForms::GetIDDiff($origids, $newids); 378 | } 379 | 380 | function BB_InitLayouts() 381 | { 382 | global $bb_page_layout, $bb_page_layout_no_menu, $bb_menu_layout, $bb_menu_item_layout, $bb_message_layout, $bb_page_layout_bulkedit; 383 | 384 | if (!isset($bb_page_layout)) 385 | { 386 | ob_start(); 387 | ?> 388 | 389 | 390 | 391 | 392 | 393 | @TITLE@ 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 |
@CONTENT@
403 | 404 | 405 | 414 | 415 | 416 | 417 | 418 | 419 | @TITLE@ 420 | 421 | 422 | 423 | 424 | 425 | 426 |
@CONTENT@
427 | 428 | 429 | 438 |
@TITLE@
439 | @ITEMS@ 440 | 441 | EOF; 442 | } 443 | 444 | if (!isset($bb_menu_item_layout)) 445 | { 446 | $bb_menu_item_layout = <<@NAME@ 448 | EOF; 449 | } 450 | 451 | if (!isset($bb_message_layout)) 452 | { 453 | $bb_message_layout = <<
@MESSAGE@
455 | EOF; 456 | } 457 | 458 | // Bulk edit layout. 459 | if (!isset($bb_page_layout_bulkedit)) 460 | { 461 | ob_start(); 462 | ?> 463 | 464 | 465 | 466 | 467 | 468 | @TITLE@ 469 | 470 | 471 | 472 | 473 | 474 | 475 |
@ITEMS@
476 |
@TOPBAR@
477 |
@CONTENT@
478 |
@BOTTOMBAR@
479 |
480 | @SCRIPTS@ 481 | 482 | 483 | $items) 526 | { 527 | // Allows for injecting custom HTML into the menu. 528 | if (is_string($items)) $data2 .= $items; 529 | else 530 | { 531 | $data3 = ""; 532 | foreach ($items as $name => $opts) 533 | { 534 | if (!is_array($opts)) $opts = array("href" => $opts); 535 | 536 | $data5 = array(); 537 | foreach ($opts as $name2 => $val) 538 | { 539 | $data5[] = htmlspecialchars($name2) . "=\"" . htmlspecialchars($val) . "\""; 540 | } 541 | 542 | $data4 = str_replace("@OPTS@", implode(" ", $data5), $bb_menu_item_layout); 543 | 544 | $data3 .= str_replace("@NAME@", (substr($name, 0, 5) === "html:" ? BB_Translate(substr($name, 5)) : htmlspecialchars(BB_Translate($name))), $data4); 545 | } 546 | 547 | $data3 = str_replace("@ITEMS@", $data3, $bb_menu_layout); 548 | $data2 .= str_replace("@TITLE@", htmlspecialchars(BB_Translate($title)), $data3); 549 | } 550 | } 551 | $data = str_replace("@MENU@", $data2, $data); 552 | 553 | // Process and display the content. 554 | $pos = strpos($data, "@CONTENT@"); 555 | echo substr($data, 0, $pos); 556 | BB_PropertyForm($contentopts); 557 | echo substr($data, $pos + 9); 558 | 559 | if (!isset($contentopts["exit"]) || $contentopts["exit"]) exit(); 560 | } 561 | 562 | function BB_GenerateBulkEditPage($title, $contentopts) 563 | { 564 | global $bb_rootname, $bb_page_layout_bulkedit; 565 | 566 | if (!isset($contentopts["title"])) $contentopts["title"] = $title; 567 | 568 | header("Content-Type: text/html; charset=UTF-8"); 569 | 570 | BB_InitLayouts(); 571 | 572 | // Process the header. 573 | if (defined("BB_ROOT_URL")) $rooturl = BB_ROOT_URL; 574 | else if (defined("ROOT_URL")) $rooturl = ROOT_URL; 575 | else 576 | { 577 | $rooturl = BB_GetRequestURLBase(); 578 | if (substr($rooturl, -1) != "/") $rooturl = str_replace("\\", "/", dirname($rooturl)); 579 | if (substr($rooturl, -1) == "/") $rooturl = substr($rooturl, 0, -1); 580 | } 581 | 582 | if (defined("BB_SUPPORT_PATH")) $supportpath = BB_SUPPORT_PATH; 583 | else if (defined("SUPPORT_PATH")) $supportpath = SUPPORT_PATH; 584 | else $supportpath = "support"; 585 | 586 | $data = str_replace("@ROOTURL@", htmlspecialchars($rooturl), $bb_page_layout_bulkedit); 587 | $data = str_replace("@SUPPORTPATH@", htmlspecialchars($supportpath), $data); 588 | 589 | // Process the title and message. 590 | $data = str_replace("@TITLE@", htmlspecialchars(BB_Translate(($bb_rootname != "" ? $bb_rootname . " | " : "") . $title)), $data); 591 | $data = str_replace("@ROOTNAME@", htmlspecialchars(BB_Translate($bb_rootname)), $data); 592 | 593 | // Process the top and bottom bars, initial content, and necessary Javascript. 594 | $data = str_replace("@TOPBAR@", (isset($contentopts["topbarhtml"]) ? $contentopts["topbarhtml"] : ""), $data); 595 | $data = str_replace("@BOTTOMBAR@", (isset($contentopts["bottombarhtml"]) ? $contentopts["bottombarhtml"] : ""), $data); 596 | $data = str_replace("@CONTENT@", (isset($contentopts["initialhtml"]) ? $contentopts["initialhtml"] : ""), $data); 597 | $data = str_replace("@SCRIPTS@", (isset($contentopts["javascript"]) ? $contentopts["javascript"] : ""), $data); 598 | 599 | // Process and display the items. 600 | $pos = strpos($data, "@ITEMS@"); 601 | echo substr($data, 0, $pos); 602 | if (isset($contentopts["items_callback"]) && is_callable($contentopts["items_callback"])) $contentopts["items"] = call_user_func($contentopts["items_callback"]); 603 | if (isset($contentopts["items"])) 604 | { 605 | $altrow = false; 606 | while (count($contentopts["items"])) 607 | { 608 | foreach ($contentopts["items"] as $item) 609 | { 610 | echo "" . (isset($item["htmldisplay"]) ? $item["htmldisplay"] : htmlspecialchars($item["display"])) . "\n"; 611 | 612 | $altrow = !$altrow; 613 | } 614 | 615 | if (isset($contentopts["items_callback"]) && is_callable($contentopts["items_callback"])) $contentopts["items"] = call_user_func($contentopts["items_callback"]); 616 | else $contentopts["items"] = array(); 617 | } 618 | } 619 | echo substr($data, $pos + 7); 620 | 621 | if (!isset($contentopts["exit"]) || $contentopts["exit"]) exit(); 622 | } 623 | ?> -------------------------------------------------------------------------------- /server/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 | ?> -------------------------------------------------------------------------------- /server/support/sdk_drc_client.php: -------------------------------------------------------------------------------- 1 | drc_channels = array(); 20 | $this->drc_client_id = false; 21 | } 22 | 23 | public function Read($finished = true, $wait = false) 24 | { 25 | $result = parent::Read(); 26 | if (!$result["success"] || $result["data"] === false) return $result; 27 | 28 | $result["data"] = @json_decode($result["data"]["payload"], true); 29 | 30 | if (!is_array($result["data"])) $result["data"] = array("success" => false, "error" => "Invalid packet.", "errorcode" => "invalid_packet"); 31 | else if (!$result["data"]["success"]) return $result; 32 | else if (isset($result["data"]["cmd"]) && $result["data"]["cmd"] === "JOINED") 33 | { 34 | if (isset($result["data"]["channelname"]) && isset($result["data"]["protocol"]) && isset($result["data"]["clients"])) 35 | { 36 | $this->drc_channels[$result["data"]["channel"]] = $result["data"]; 37 | $this->drc_client_id = $result["data"]["id"]; 38 | } 39 | else if (isset($this->drc_channels[$result["data"]["channel"]])) 40 | { 41 | $this->drc_channels[$result["data"]["channel"]]["clients"][$result["data"]["id"]] = $result["data"]["info"]; 42 | } 43 | } 44 | else if (isset($result["data"]["cmd"]) && $result["data"]["cmd"] === "SET_EXTRA" && isset($this->drc_channels[$result["data"]["channel"]]) && isset($this->drc_channels[$result["data"]["channel"]]["clients"][$result["data"]["id"]])) 45 | { 46 | $this->drc_channels[$result["data"]["channel"]]["clients"][$result["data"]["id"]]["extra"] = $result["data"]["extra"]; 47 | } 48 | else if (isset($result["data"]["cmd"]) && $result["data"]["cmd"] === "LEFT" && isset($this->drc_channels[$result["data"]["channel"]])) 49 | { 50 | if ($result["data"]["id"] === $this->drc_client_id) unset($this->drc_channels[$result["data"]["channel"]]); 51 | else unset($this->drc_channels[$result["data"]["channel"]]["clients"][$result["data"]["id"]]); 52 | } 53 | 54 | return $result; 55 | } 56 | 57 | public function CreateToken($authtoken, $channelname, $protocol, $clientmode, $extra = array(), $wait = false, $makeauth = false) 58 | { 59 | $data = array( 60 | "cmd" => "GRANT", 61 | "channel" => $channelname, 62 | "protocol" => $protocol, 63 | "clientmode" => $clientmode, 64 | "extra" => $extra 65 | ); 66 | 67 | if ($authtoken !== false) $data["token"] = $authtoken; 68 | if ($makeauth !== false) $data["makeauth"] = true; 69 | 70 | $result = $this->Write(json_encode($data, JSON_UNESCAPED_SLASHES), WebSocket::FRAMETYPE_TEXT); 71 | if (!$result["success"]) return $result; 72 | 73 | if ($wait) 74 | { 75 | $ts = time(); 76 | $result = ($wait === true ? $this->Wait() : $this->Wait(1)); 77 | while ($result["success"]) 78 | { 79 | do 80 | { 81 | $result = $this->Read(); 82 | if (!$result["success"]) return $result; 83 | if ($result["data"] !== false && !$result["data"]["success"]) return $result["data"]; 84 | 85 | if ($result["data"] !== false && isset($result["data"]["cmd"]) && $result["data"]["cmd"] === "GRANTED" && isset($result["data"]["channelname"]) && $result["data"]["channelname"] === $channelname && isset($result["data"]["protocol"]) && $result["data"]["protocol"] === $protocol && isset($result["data"]["token"])) 86 | { 87 | return $result; 88 | } 89 | } while ($result["data"] !== false); 90 | 91 | if ($wait !== true && $ts + $wait <= time()) return array("success" => false, "error" => "Timed out while creating a token.", "errorcode" => "create_token_timeout"); 92 | 93 | $result = ($wait === true ? $this->Wait() : $this->Wait(1)); 94 | } 95 | } 96 | 97 | return $result; 98 | } 99 | 100 | public function JoinChannel($channelname, $protocol, $token, $wait = false, $allowipauth = true) 101 | { 102 | $data = array( 103 | "cmd" => "JOIN", 104 | "channel" => $channelname, 105 | "protocol" => $protocol 106 | ); 107 | 108 | if ($token !== false) $data["token"] = $token; 109 | if ($allowipauth === false) $data["ipauth"] = false; 110 | 111 | $result = $this->Write(json_encode($data, JSON_UNESCAPED_SLASHES), WebSocket::FRAMETYPE_TEXT); 112 | if (!$result["success"]) return $result; 113 | 114 | if ($wait) 115 | { 116 | $ts = time(); 117 | $result = ($wait === true ? $this->Wait() : $this->Wait(1)); 118 | while ($result["success"]) 119 | { 120 | do 121 | { 122 | $result = $this->Read(); 123 | if (!$result["success"]) return $result; 124 | if ($result["data"] !== false && !$result["data"]["success"]) return $result["data"]; 125 | 126 | if ($result["data"] !== false && isset($result["data"]["cmd"]) && $result["data"]["cmd"] === "JOINED" && isset($result["data"]["channelname"]) && $result["data"]["channelname"] === $channelname && isset($result["data"]["protocol"]) && $result["data"]["protocol"] === $protocol && isset($result["data"]["clients"])) 127 | { 128 | return $result; 129 | } 130 | } while ($result["data"] !== false); 131 | 132 | if ($wait !== true && $ts + $wait <= time()) return array("success" => false, "error" => "Timed out while joining the channel.", "errorcode" => "join_channel_timeout"); 133 | 134 | $result = ($wait === true ? $this->Wait() : $this->Wait(1)); 135 | } 136 | } 137 | 138 | return $result; 139 | } 140 | 141 | public function GetChannels() 142 | { 143 | return $this->drc_channels; 144 | } 145 | 146 | public function GetChannel($channel) 147 | { 148 | return (isset($this->drc_channels[$channel]) ? $this->drc_channels[$channel] : false); 149 | } 150 | 151 | public function GetClientID() 152 | { 153 | return $this->drc_client_id; 154 | } 155 | 156 | public function SetExtra($channel, $id, $extra = array(), $wait = false) 157 | { 158 | $data = array( 159 | "channel" => $channel, 160 | "cmd" => "SET_EXTRA", 161 | "id" => $id, 162 | "extra" => $extra 163 | ); 164 | 165 | $result = $this->Write(json_encode($data, JSON_UNESCAPED_SLASHES), WebSocket::FRAMETYPE_TEXT); 166 | if (!$result["success"]) return $result; 167 | 168 | if ($wait) 169 | { 170 | $ts = time(); 171 | $result = ($wait === true ? $this->Wait() : $this->Wait(1)); 172 | while ($result["success"]) 173 | { 174 | do 175 | { 176 | $result = $this->Read(); 177 | if (!$result["success"]) return $result; 178 | if ($result["data"] !== false && !$result["data"]["success"]) return $result["data"]; 179 | 180 | if ($result["data"] !== false && isset($result["data"]["cmd"]) && $result["data"]["cmd"] === "SET_EXTRA" && $result["data"]["id"] === $id) return $result; 181 | } while ($result["data"] !== false); 182 | 183 | if ($wait !== true && $ts + $wait <= time()) return array("success" => false, "error" => "Timed out while setting extra information.", "errorcode" => "set_extra_timeout"); 184 | 185 | $result = ($wait === true ? $this->Wait() : $this->Wait(1)); 186 | } 187 | } 188 | 189 | return $result; 190 | } 191 | 192 | public function SendCommand($channel, $cmd, $to, $options = array(), $wait = false, $waitcmd = false) 193 | { 194 | $data = array( 195 | "channel" => $channel, 196 | "cmd" => $cmd, 197 | "to" => $to 198 | ); 199 | 200 | $data = $data + $options; 201 | 202 | $result = $this->Write(json_encode($data, JSON_UNESCAPED_SLASHES), WebSocket::FRAMETYPE_TEXT); 203 | if (!$result["success"]) return $result; 204 | 205 | if (is_string($waitcmd)) 206 | { 207 | $ts = time(); 208 | $result = ($wait === true ? $this->Wait() : $this->Wait(1)); 209 | while ($result["success"]) 210 | { 211 | do 212 | { 213 | $result = $this->Read(); 214 | if (!$result["success"]) return $result; 215 | if ($result["data"] !== false && !$result["data"]["success"]) return $result["data"]; 216 | 217 | if ($result["data"] !== false && isset($result["data"]["cmd"]) && $result["data"]["cmd"] === $waitcmd && ($to < 0 || $result["data"]["from"] === $to)) return $result; 218 | } while ($result["data"] !== false); 219 | 220 | if ($wait !== true && $ts + $wait <= time()) return array("success" => false, "error" => "Timed out while waiting for a response to a sent command.", "errorcode" => "send_command_timeout"); 221 | 222 | $result = ($wait === true ? $this->Wait() : $this->Wait(1)); 223 | } 224 | } 225 | else if ($wait) 226 | { 227 | $ts = time(); 228 | do 229 | { 230 | if ($wait !== true && $ts + $wait <= time()) return array("success" => false, "error" => "Timed out while sending command.", "errorcode" => "send_command_timeout"); 231 | 232 | $result = ($wait === true ? $this->Wait() : $this->Wait(1)); 233 | } while ($result["success"] && $this->NeedsWrite()); 234 | } 235 | 236 | return $result; 237 | } 238 | 239 | public function GetRandomAuthClientID($channel) 240 | { 241 | if (!isset($this->drc_channels[$channel])) return false; 242 | 243 | $idmap = array(); 244 | foreach ($this->drc_channels[$channel]["clients"] as $id => $info) 245 | { 246 | if ($info["auth"]) $idmap[] = $id; 247 | } 248 | 249 | $y = count($idmap); 250 | if (!$y) return false; 251 | 252 | return $idmap[random_int(0, $y - 1)]; 253 | } 254 | 255 | public function SendCommandToAuthClients($channel, $cmd, $options = array(), $wait = false) 256 | { 257 | if (!isset($this->drc_channels[$channel])) return array("success" => false, "error" => "Invalid channel.", "errorcode" => "invalid_channel"); 258 | 259 | foreach ($this->drc_channels[$channel]["clients"] as $id => $info) 260 | { 261 | if ($info["auth"]) 262 | { 263 | $result = $this->SendCommand($channel, $cmd, $id, $options, $wait); 264 | if (!$result["success"]) return $result; 265 | } 266 | } 267 | 268 | return array("success" => true); 269 | } 270 | 271 | public function LeaveChannel($channel, $wait = false) 272 | { 273 | unset($this->drc_channels[$channel]); 274 | 275 | $data = array( 276 | "cmd" => "LEAVE", 277 | "channel" => $channel 278 | ); 279 | 280 | $result = $this->Write(json_encode($data, JSON_UNESCAPED_SLASHES), WebSocket::FRAMETYPE_TEXT); 281 | if (!$result["success"]) return $result; 282 | 283 | if ($wait) 284 | { 285 | $ts = time(); 286 | $result = ($wait === true ? $this->Wait() : $this->Wait(1)); 287 | while ($result["success"]) 288 | { 289 | do 290 | { 291 | $result = $this->Read(); 292 | if (!$result["success"]) return $result; 293 | if ($result["data"] !== false && !$result["data"]["success"]) return $result["data"]; 294 | 295 | if ($result["data"] !== false && isset($result["data"]["cmd"]) && $result["data"]["cmd"] === "LEFT" && !isset($this->drc_channels[$channel])) return $result; 296 | } while ($result["data"] !== false); 297 | 298 | if ($wait !== true && $ts + $wait <= time()) return array("success" => false, "error" => "Timed out while leaving the channel.", "errorcode" => "leave_channel_timeout"); 299 | 300 | $result = ($wait === true ? $this->Wait() : $this->Wait(1)); 301 | } 302 | } 303 | 304 | return $result; 305 | } 306 | } 307 | ?> -------------------------------------------------------------------------------- /server/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 | ?> -------------------------------------------------------------------------------- /server/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 | ?> -------------------------------------------------------------------------------- /server/support/view_print_layout.php: -------------------------------------------------------------------------------- 1 | 20 |
21 | 32 |
33 | 39 |
40 | 46 |
47 | 55 |
56 | 61 |
62 | 65 |
66 | 77 |
78 | 95 | 96 | $val) 98 | { 99 | if ($val != "" || !$compact) 100 | { 101 | ?> 102 | 103 | 107 |
\n", htmlspecialchars($val)) : $val); ?>
108 | 121 |
\n", htmlspecialchars($field["value"])) : $field["value"]); ?>
122 | 150 | 151 | 152 | 153 | @TITLE@ 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 |
162 |
@ROOTNAME@
163 |
164 |
165 | @CONTENT@ 166 |
167 |
168 | 169 |
170 | 171 | 172 | @MENU@', "", $bb_page_layout); 177 | ?> -------------------------------------------------------------------------------- /server/support/websocket.php: -------------------------------------------------------------------------------- 1 | Reset(); 36 | } 37 | 38 | public function __destruct() 39 | { 40 | $this->Disconnect(); 41 | } 42 | 43 | public function Reset() 44 | { 45 | $this->fp = false; 46 | $this->client = true; 47 | $this->extensions = array(); 48 | $this->csprng = false; 49 | $this->state = self::STATE_CONNECTING; 50 | $this->closemode = self::CLOSE_IMMEDIATELY; 51 | $this->readdata = ""; 52 | $this->maxreadframesize = 2000000; 53 | $this->readmessages = array(); 54 | $this->maxreadmessagesize = 10000000; 55 | $this->writedata = ""; 56 | $this->writemessages = array(); 57 | $this->keepalive = 30; 58 | $this->lastkeepalive = time(); 59 | $this->keepalivesent = false; 60 | $this->rawrecvsize = 0; 61 | $this->rawsendsize = 0; 62 | } 63 | 64 | public function SetServerMode() 65 | { 66 | $this->client = false; 67 | } 68 | 69 | public function SetClientMode() 70 | { 71 | $this->client = true; 72 | } 73 | 74 | public function SetExtensions($extensions) 75 | { 76 | $this->extensions = (array)$extensions; 77 | } 78 | 79 | public function SetCloseMode($mode) 80 | { 81 | $this->closemode = $mode; 82 | } 83 | 84 | public function SetKeepAliveTimeout($keepalive) 85 | { 86 | $this->keepalive = (int)$keepalive; 87 | } 88 | 89 | public function GetKeepAliveTimeout() 90 | { 91 | return $this->keepalive; 92 | } 93 | 94 | public function SetMaxReadFrameSize($maxsize) 95 | { 96 | $this->maxreadframesize = (is_bool($maxsize) ? false : (int)$maxsize); 97 | } 98 | 99 | public function SetMaxReadMessageSize($maxsize) 100 | { 101 | $this->maxreadmessagesize = (is_bool($maxsize) ? false : (int)$maxsize); 102 | } 103 | 104 | public function GetRawRecvSize() 105 | { 106 | return $this->rawrecvsize; 107 | } 108 | 109 | public function GetRawSendSize() 110 | { 111 | return $this->rawsendsize; 112 | } 113 | 114 | public function Connect($url, $origin, $options = array(), $web = false) 115 | { 116 | $this->Disconnect(); 117 | 118 | if (class_exists("CSPRNG", false) && $this->csprng === false) $this->csprng = new CSPRNG(); 119 | 120 | if (isset($options["connected_fp"]) && is_resource($options["connected_fp"])) $this->fp = $options["connected_fp"]; 121 | else 122 | { 123 | if (!class_exists("WebBrowser", false)) require_once str_replace("\\", "/", dirname(__FILE__)) . "/web_browser.php"; 124 | 125 | // Use WebBrowser to initiate the connection. 126 | if ($web === false) $web = new WebBrowser(); 127 | 128 | // Transform URL. 129 | $url2 = HTTP::ExtractURL($url); 130 | if ($url2["scheme"] != "ws" && $url2["scheme"] != "wss") return array("success" => false, "error" => self::WSTranslate("WebSocket::Connect() only supports the 'ws' and 'wss' protocols."), "errorcode" => "protocol_check"); 131 | $url2["scheme"] = str_replace("ws", "http", $url2["scheme"]); 132 | $url2 = HTTP::CondenseURL($url2); 133 | 134 | // Generate correct request headers. 135 | if (!isset($options["headers"])) $options["headers"] = array(); 136 | $options["headers"]["Connection"] = "keep-alive, Upgrade"; 137 | if ($origin != "") $options["headers"]["Origin"] = $origin; 138 | $options["headers"]["Pragma"] = "no-cache"; 139 | $key = base64_encode($this->PRNGBytes(16)); 140 | $options["headers"]["Sec-WebSocket-Key"] = $key; 141 | $options["headers"]["Sec-WebSocket-Version"] = "13"; 142 | $options["headers"]["Upgrade"] = "websocket"; 143 | 144 | // No async support for connecting at this time. Async mode is enabled AFTER connecting though. 145 | unset($options["async"]); 146 | 147 | // Connect to the WebSocket. 148 | $result = $web->Process($url2, $options); 149 | if (!$result["success"]) return $result; 150 | if ($result["response"]["code"] != 101) return array("success" => false, "error" => self::WSTranslate("WebSocket::Connect() failed to connect to the WebSocket. Server returned: %s %s", $result["response"]["code"], $result["response"]["meaning"]), "errorcode" => "incorrect_server_response"); 151 | if (!isset($result["headers"]["Sec-Websocket-Accept"])) return array("success" => false, "error" => self::WSTranslate("Server failed to include a 'Sec-WebSocket-Accept' header in its response to the request."), "errorcode" => "missing_server_websocket_accept_header"); 152 | 153 | // Verify the Sec-WebSocket-Accept response. 154 | if ($result["headers"]["Sec-Websocket-Accept"][0] !== base64_encode(sha1($key . self::KEY_GUID, true))) return array("success" => false, "error" => self::WSTranslate("The server's 'Sec-WebSocket-Accept' header is invalid."), "errorcode" => "invalid_server_websocket_accept_header"); 155 | 156 | $this->fp = $result["fp"]; 157 | } 158 | 159 | // Enable non-blocking mode. 160 | stream_set_blocking($this->fp, 0); 161 | 162 | $this->state = self::STATE_OPEN; 163 | 164 | $this->readdata = ""; 165 | $this->readmessages = array(); 166 | $this->writedata = ""; 167 | $this->writemessages = array(); 168 | $this->lastkeepalive = time(); 169 | $this->keepalivesent = false; 170 | $this->rawrecvsize = 0; 171 | $this->rawsendsize = 0; 172 | 173 | return array("success" => true); 174 | } 175 | 176 | public function Disconnect() 177 | { 178 | if ($this->fp !== false && $this->state === self::STATE_OPEN) 179 | { 180 | if ($this->closemode === self::CLOSE_IMMEDIATELY) $this->writemessages = array(); 181 | else if ($this->closemode === self::CLOSE_AFTER_CURRENT_MESSAGE) $this->writemessages = array_slice($this->writemessages, 0, 1); 182 | 183 | $this->state = self::STATE_CLOSE; 184 | 185 | $this->Write("", self::FRAMETYPE_CONNECTION_CLOSE, true, $this->client); 186 | 187 | $this->Wait($this->client ? false : 0); 188 | } 189 | 190 | if ($this->fp !== false) 191 | { 192 | @fclose($this->fp); 193 | 194 | $this->fp = false; 195 | } 196 | 197 | $this->state = self::STATE_CONNECTING; 198 | $this->readdata = ""; 199 | $this->readmessages = array(); 200 | $this->writedata = ""; 201 | $this->writemessages = array(); 202 | $this->lastkeepalive = time(); 203 | $this->keepalivesent = false; 204 | } 205 | 206 | // Reads the next message or message fragment (depending on $finished). Returns immediately unless $wait is not false. 207 | public function Read($finished = true, $wait = false) 208 | { 209 | if ($this->fp === false || $this->state === self::STATE_CONNECTING) return array("success" => false, "error" => self::WSTranslate("Connection not established."), "errorcode" => "no_connection"); 210 | 211 | if ($wait) 212 | { 213 | while (!count($this->readmessages) || ($finished && !$this->readmessages[0]["fin"])) 214 | { 215 | $result = $this->Wait(); 216 | if (!$result["success"]) return $result; 217 | } 218 | } 219 | 220 | $data = false; 221 | 222 | if (count($this->readmessages)) 223 | { 224 | if ($finished) 225 | { 226 | if ($this->readmessages[0]["fin"]) $data = array_shift($this->readmessages); 227 | } 228 | else 229 | { 230 | $data = $this->readmessages[0]; 231 | 232 | $this->readmessages[0]["payload"] = ""; 233 | } 234 | } 235 | 236 | return array("success" => true, "data" => $data); 237 | } 238 | 239 | // Adds the message to the write queue. Returns immediately unless $wait is not false. 240 | public function Write($message, $frametype, $last = true, $wait = false, $pos = false) 241 | { 242 | if ($this->fp === false || $this->state === self::STATE_CONNECTING) return array("success" => false, "error" => self::WSTranslate("Connection not established."), "errorcode" => "no_connection"); 243 | 244 | $message = (string)$message; 245 | 246 | $y = count($this->writemessages); 247 | $lastcompleted = (!$y || $this->writemessages[$y - 1]["fin"]); 248 | if ($frametype >= 0x08 || $lastcompleted) 249 | { 250 | if ($frametype >= 0x08) $last = true; 251 | else $pos = false; 252 | 253 | $frame = array( 254 | "fin" => (bool)$last, 255 | "framessent" => 0, 256 | "opcode" => $frametype, 257 | "payloads" => array($message) 258 | ); 259 | 260 | array_splice($this->writemessages, ($pos !== false ? $pos : $y), 0, array($frame)); 261 | } 262 | else 263 | { 264 | if ($frametype !== $this->writemessages[$y - 1]["opcode"]) return array("success" => false, "error" => self::WSTranslate("Mismatched frame type (opcode) specified."), "errorcode" => "mismatched_frame_type"); 265 | 266 | $this->writemessages[$y - 1]["fin"] = (bool)$last; 267 | $this->writemessages[$y - 1]["payloads"][] = $message; 268 | } 269 | 270 | if ($wait) 271 | { 272 | while ($this->NeedsWrite()) 273 | { 274 | $result = $this->Wait(); 275 | if (!$result["success"]) return $result; 276 | } 277 | } 278 | 279 | return array("success" => true); 280 | } 281 | 282 | public function NeedsWrite() 283 | { 284 | $this->FillWriteData(); 285 | 286 | return ($this->writedata !== ""); 287 | } 288 | 289 | public function NumWriteMessages() 290 | { 291 | return count($this->writemessages); 292 | } 293 | 294 | // Dangerous but allows for stream_select() calls on multiple, separate stream handles. 295 | public function GetStream() 296 | { 297 | return $this->fp; 298 | } 299 | 300 | // Waits until one or more events time out, handles reading and writing, processes the queues (handle control types automatically), and returns the latest status. 301 | public function Wait($timeout = false) 302 | { 303 | if ($this->fp === false || $this->state === self::STATE_CONNECTING) return array("success" => false, "error" => self::WSTranslate("Connection not established."), "errorcode" => "no_connection"); 304 | 305 | $result = $this->ProcessReadData(); 306 | if (!$result["success"]) return $result; 307 | 308 | $this->FillWriteData(); 309 | 310 | $readfp = array($this->fp); 311 | $writefp = ($this->writedata !== "" ? array($this->fp) : NULL); 312 | $exceptfp = NULL; 313 | if ($timeout === false || $timeout > $this->keepalive) $timeout = $this->keepalive; 314 | $result = @stream_select($readfp, $writefp, $exceptfp, $timeout); 315 | if ($result === false) return array("success" => false, "error" => self::WSTranslate("Wait() failed due to stream_select() failure. Most likely cause: Connection failure."), "errorcode" => "stream_select_failed"); 316 | 317 | // Process queues and timeouts. 318 | $result = $this->ProcessQueuesAndTimeoutState(($result > 0 && count($readfp)), ($result > 0 && $writefp !== NULL && count($writefp))); 319 | 320 | return $result; 321 | } 322 | 323 | // A mostly internal function. Useful for managing multiple simultaneous WebSocket connections. 324 | public function ProcessQueuesAndTimeoutState($read, $write, $readsize = 65536) 325 | { 326 | if ($this->fp === false || $this->state === self::STATE_CONNECTING) return array("success" => false, "error" => self::WSTranslate("Connection not established."), "errorcode" => "no_connection"); 327 | 328 | if ($read) 329 | { 330 | $result = @fread($this->fp, $readsize); 331 | if ($result === false || ($result === "" && feof($this->fp))) return array("success" => false, "error" => self::WSTranslate("ProcessQueuesAndTimeoutState() failed due to fread() failure. Most likely cause: Connection failure."), "errorcode" => "fread_failed"); 332 | 333 | if ($result !== "") 334 | { 335 | $this->rawrecvsize += strlen($result); 336 | $this->readdata .= $result; 337 | 338 | if ($this->maxreadframesize !== false && strlen($this->readdata) > $this->maxreadframesize) return array("success" => false, "error" => self::WSTranslate("ProcessQueuesAndTimeoutState() failed due to peer sending a single frame exceeding %s bytes of data.", $this->maxreadframesize), "errorcode" => "max_read_frame_size_exceeded"); 339 | 340 | $result = $this->ProcessReadData(); 341 | if (!$result["success"]) return $result; 342 | 343 | $this->lastkeepalive = time(); 344 | $this->keepalivesent = false; 345 | } 346 | } 347 | 348 | if ($write) 349 | { 350 | $result = @fwrite($this->fp, $this->writedata); 351 | if ($result === false || ($this->writedata === "" && feof($this->fp))) return array("success" => false, "error" => self::WSTranslate("ProcessQueuesAndTimeoutState() failed due to fwrite() failure. Most likely cause: Connection failure."), "errorcode" => "fwrite_failed"); 352 | 353 | if ($result) 354 | { 355 | $this->rawsendsize += $result; 356 | $this->writedata = (string)substr($this->writedata, $result); 357 | 358 | $this->lastkeepalive = time(); 359 | $this->keepalivesent = false; 360 | } 361 | } 362 | 363 | // Handle timeout state. 364 | if ($this->lastkeepalive < time() - $this->keepalive) 365 | { 366 | if ($this->keepalivesent) return array("success" => false, "error" => self::WSTranslate("ProcessQueuesAndTimeoutState() failed due to non-response from peer to ping frame. Most likely cause: Connection failure."), "errorcode" => "ping_failed"); 367 | else 368 | { 369 | $result = $this->Write(time(), self::FRAMETYPE_PING, true, false, 0); 370 | if (!$result["success"]) return $result; 371 | 372 | $this->lastkeepalive = time(); 373 | $this->keepalivesent = true; 374 | } 375 | } 376 | 377 | return array("success" => true); 378 | } 379 | 380 | protected function ProcessReadData() 381 | { 382 | while (($frame = $this->ReadFrame()) !== false) 383 | { 384 | // Verify that the opcode is probably valid. 385 | if (($frame["opcode"] >= 0x03 && $frame["opcode"] <= 0x07) || $frame["opcode"] >= 0x0B) return array("success" => false, "error" => self::WSTranslate("Invalid frame detected. Bad opcode 0x%02X.", $frame["opcode"]), "errorcode" => "bad_frame_opcode"); 386 | 387 | // No extension support (yet). 388 | if ($frame["rsv1"] || $frame["rsv2"] || $frame["rsv3"]) return array("success" => false, "error" => self::WSTranslate("Invalid frame detected. One or more reserved extension bits are set."), "errorcode" => "bad_reserved_bits_set"); 389 | 390 | if ($frame["opcode"] >= 0x08) 391 | { 392 | // Handle the control frame. 393 | if (!$frame["fin"]) return array("success" => false, "error" => self::WSTranslate("Invalid frame detected. Fragmented control frame was received."), "errorcode" => "bad_control_frame"); 394 | 395 | if ($frame["opcode"] === self::FRAMETYPE_CONNECTION_CLOSE) 396 | { 397 | if ($this->state === self::STATE_CLOSE) 398 | { 399 | // Already sent the close state. 400 | @fclose($this->fp); 401 | $this->fp = false; 402 | 403 | return array("success" => false, "error" => self::WSTranslate("Connection closed by peer."), "errorcode" => "connection_closed"); 404 | } 405 | else 406 | { 407 | // Change the state to close and send the connection close response to the peer at the appropriate time. 408 | if ($this->closemode === self::CLOSE_IMMEDIATELY) $this->writemessages = array(); 409 | else if ($this->closemode === self::CLOSE_AFTER_CURRENT_MESSAGE) $this->writemessages = array_slice($this->writemessages, 0, 1); 410 | 411 | $this->state = self::STATE_CLOSE; 412 | 413 | $result = $this->Write("", self::FRAMETYPE_CONNECTION_CLOSE); 414 | if (!$result["success"]) return $result; 415 | } 416 | } 417 | else if ($frame["opcode"] === self::FRAMETYPE_PING) 418 | { 419 | if ($this->state !== self::STATE_CLOSE) 420 | { 421 | // Received a ping. Respond with a pong with the same payload. 422 | $result = $this->Write($frame["payload"], self::FRAMETYPE_PONG, true, false, 0); 423 | if (!$result["success"]) return $result; 424 | } 425 | } 426 | else if ($frame["opcode"] === self::FRAMETYPE_PONG) 427 | { 428 | // Do nothing. 429 | } 430 | } 431 | else 432 | { 433 | // Add this frame to the read message queue. 434 | $lastcompleted = (!count($this->readmessages) || $this->readmessages[count($this->readmessages) - 1]["fin"]); 435 | if ($lastcompleted) 436 | { 437 | // Make sure the new frame is the start of a fragment or is not fragemented. 438 | if ($frame["opcode"] === self::FRAMETYPE_CONTINUATION) return array("success" => false, "error" => self::WSTranslate("Invalid frame detected. Fragment continuation frame was received at the start of a fragment."), "errorcode" => "bad_continuation_frame"); 439 | 440 | $this->readmessages[] = $frame; 441 | } 442 | else 443 | { 444 | // Make sure the frame is a continuation frame. 445 | if ($frame["opcode"] !== self::FRAMETYPE_CONTINUATION) return array("success" => false, "error" => self::WSTranslate("Invalid frame detected. Fragment continuation frame was not received for a fragment."), "errorcode" => "missing_continuation_frame"); 446 | 447 | $this->readmessages[count($this->readmessages) - 1]["fin"] = $frame["fin"]; 448 | $this->readmessages[count($this->readmessages) - 1]["payload"] .= $frame["payload"]; 449 | } 450 | 451 | if ($this->maxreadmessagesize !== false && strlen($this->readmessages[count($this->readmessages) - 1]["payload"]) > $this->maxreadmessagesize) return array("success" => false, "error" => self::WSTranslate("Peer sent a single message exceeding %s bytes of data.", $this->maxreadmessagesize), "errorcode" => "max_read_message_size_exceeded"); 452 | } 453 | 454 | //var_dump($frame); 455 | } 456 | 457 | return array("success" => true); 458 | } 459 | 460 | // Parses the current input data to see if there is enough information to extract a single frame. 461 | // Does not do any error checking beyond loading the frame and decoding any masked data. 462 | protected function ReadFrame() 463 | { 464 | if (strlen($this->readdata) < 2) return false; 465 | 466 | $chr = ord($this->readdata[0]); 467 | $fin = (($chr & 0x80) ? true : false); 468 | $rsv1 = (($chr & 0x40) ? true : false); 469 | $rsv2 = (($chr & 0x20) ? true : false); 470 | $rsv3 = (($chr & 0x10) ? true : false); 471 | $opcode = $chr & 0x0F; 472 | 473 | $chr = ord($this->readdata[1]); 474 | $mask = (($chr & 0x80) ? true : false); 475 | $length = $chr & 0x7F; 476 | if ($length == 126) $start = 4; 477 | else if ($length == 127) $start = 10; 478 | else $start = 2; 479 | 480 | if (strlen($this->readdata) < $start + ($mask ? 4 : 0)) return false; 481 | 482 | // Frame minus payload calculated. 483 | if ($length == 126) $length = self::UnpackInt(substr($this->readdata, 2, 2)); 484 | else if ($length == 127) $length = self::UnpackInt(substr($this->readdata, 2, 8)); 485 | 486 | if ($mask) 487 | { 488 | $maskingkey = substr($this->readdata, $start, 4); 489 | $start += 4; 490 | } 491 | 492 | if (strlen($this->readdata) < $start + $length) return false; 493 | 494 | $payload = substr($this->readdata, $start, $length); 495 | 496 | $this->readdata = substr($this->readdata, $start + $length); 497 | 498 | if ($mask) 499 | { 500 | // Decode the payload. 501 | for ($x = 0; $x < $length; $x++) 502 | { 503 | $payload[$x] = chr(ord($payload[$x]) ^ ord($maskingkey[$x % 4])); 504 | } 505 | } 506 | 507 | $result = array( 508 | "fin" => $fin, 509 | "rsv1" => $rsv1, 510 | "rsv2" => $rsv2, 511 | "rsv3" => $rsv3, 512 | "opcode" => $opcode, 513 | "mask" => $mask, 514 | "payload" => $payload 515 | ); 516 | 517 | return $result; 518 | } 519 | 520 | // Converts messages in the queue to a data stream of frames. 521 | protected function FillWriteData() 522 | { 523 | while (strlen($this->writedata) < 65536 && count($this->writemessages) && count($this->writemessages[0]["payloads"])) 524 | { 525 | $payload = array_shift($this->writemessages[0]["payloads"]); 526 | 527 | $fin = ($this->writemessages[0]["fin"] && !count($this->writemessages[0]["payloads"])); 528 | 529 | if ($this->writemessages[0]["framessent"] === 0) $opcode = $this->writemessages[0]["opcode"]; 530 | else $opcode = self::FRAMETYPE_CONTINUATION; 531 | 532 | $this->WriteFrame($fin, $opcode, $payload); 533 | 534 | $this->writemessages[0]["framessent"]++; 535 | 536 | if ($fin) array_shift($this->writemessages); 537 | } 538 | } 539 | 540 | // Generates the actual frame data to be sent. 541 | protected function WriteFrame($fin, $opcode, $payload) 542 | { 543 | $rsv1 = false; 544 | $rsv2 = false; 545 | $rsv3 = false; 546 | $mask = $this->client; 547 | 548 | $data = chr(($fin ? 0x80 : 0x00) | ($rsv1 ? 0x40 : 0x00) | ($rsv2 ? 0x20 : 0x00) | ($rsv3 ? 0x10 : 0x00) | ($opcode & 0x0F)); 549 | 550 | if (strlen($payload) < 126) $length = strlen($payload); 551 | else if (strlen($payload) < 65536) $length = 126; 552 | else $length = 127; 553 | 554 | $data .= chr(($mask ? 0x80 : 0x00) | ($length & 0x7F)); 555 | 556 | if ($length === 126) $data .= pack("n", strlen($payload)); 557 | else if ($length === 127) $data .= self::PackInt64(strlen($payload)); 558 | 559 | if ($mask) 560 | { 561 | $maskingkey = $this->PRNGBytes(4); 562 | $data .= $maskingkey; 563 | 564 | // Encode the payload. 565 | $y = strlen($payload); 566 | for ($x = 0; $x < $y; $x++) 567 | { 568 | $payload[$x] = chr(ord($payload[$x]) ^ ord($maskingkey[$x % 4])); 569 | } 570 | } 571 | 572 | $data .= $payload; 573 | 574 | $this->writedata .= $data; 575 | } 576 | 577 | // This function follows the specification IF CSPRNG is available, but it isn't necessary to do so. 578 | protected function PRNGBytes($length) 579 | { 580 | if ($this->csprng !== false) $result = $this->csprng->GetBytes($length); 581 | else 582 | { 583 | $result = ""; 584 | while (strlen($result) < $length) $result .= chr(mt_rand(0, 255)); 585 | } 586 | 587 | return $result; 588 | } 589 | 590 | public static function UnpackInt($data) 591 | { 592 | if ($data === false) return false; 593 | 594 | if (strlen($data) == 2) $result = unpack("n", $data); 595 | else if (strlen($data) == 4) $result = unpack("N", $data); 596 | else if (strlen($data) == 8) 597 | { 598 | $result = 0; 599 | for ($x = 0; $x < 8; $x++) 600 | { 601 | $result = ($result * 256) + ord($data[$x]); 602 | } 603 | 604 | return $result; 605 | } 606 | else return false; 607 | 608 | return $result[1]; 609 | } 610 | 611 | public static function PackInt64($num) 612 | { 613 | $result = ""; 614 | 615 | if (is_int(2147483648)) $floatlim = 9223372036854775808; 616 | else $floatlim = 2147483648; 617 | 618 | if (is_float($num)) 619 | { 620 | $num = floor($num); 621 | if ($num < (double)$floatlim) $num = (int)$num; 622 | } 623 | 624 | while (is_float($num)) 625 | { 626 | $byte = (int)fmod($num, 256); 627 | $result = chr($byte) . $result; 628 | 629 | $num = floor($num / 256); 630 | if (is_float($num) && $num < (double)$floatlim) $num = (int)$num; 631 | } 632 | 633 | while ($num > 0) 634 | { 635 | $byte = $num & 0xFF; 636 | $result = chr($byte) . $result; 637 | $num = $num >> 8; 638 | } 639 | 640 | $result = str_pad($result, 8, "\x00", STR_PAD_LEFT); 641 | $result = substr($result, -8); 642 | 643 | return $result; 644 | } 645 | 646 | public static function WSTranslate() 647 | { 648 | $args = func_get_args(); 649 | if (!count($args)) return ""; 650 | 651 | return call_user_func_array((defined("CS_TRANSLATE_FUNC") && function_exists(CS_TRANSLATE_FUNC) ? CS_TRANSLATE_FUNC : "sprintf"), $args); 652 | } 653 | } 654 | ?> --------------------------------------------------------------------------------