├── .gitignore ├── support ├── flex_forms_error.png ├── fancy-file-uploader │ ├── fancy_okay.png │ ├── fancy_remove.png │ ├── fancy_upload.png │ ├── cors │ │ ├── jquery.xdr-transport.js │ │ └── jquery.postmessage-transport.js │ ├── jquery.iframe-transport.js │ ├── fancy_fileupload.css │ └── jquery.ui.widget.js ├── install.css ├── cft.css ├── csdb │ ├── db_mysql_lite.php │ ├── db_sqlite_lite.php │ ├── db_pgsql_lite.php │ ├── db_oci_lite.php │ ├── db_pgsql.php │ └── db_sqlite.php ├── flex_forms.css ├── str_basics.php ├── cft.js ├── flex_forms_fileuploader.php └── random.php ├── base.php ├── send └── index.php ├── recv └── index.php ├── cft.php ├── README.md └── install.php /.gitignore: -------------------------------------------------------------------------------- 1 | /db/* 2 | /test/* 3 | /config.php 4 | -------------------------------------------------------------------------------- /support/flex_forms_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cubiclesoft/php-cool-file-transfer/master/support/flex_forms_error.png -------------------------------------------------------------------------------- /support/fancy-file-uploader/fancy_okay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cubiclesoft/php-cool-file-transfer/master/support/fancy-file-uploader/fancy_okay.png -------------------------------------------------------------------------------- /support/fancy-file-uploader/fancy_remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cubiclesoft/php-cool-file-transfer/master/support/fancy-file-uploader/fancy_remove.png -------------------------------------------------------------------------------- /support/fancy-file-uploader/fancy_upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cubiclesoft/php-cool-file-transfer/master/support/fancy-file-uploader/fancy_upload.png -------------------------------------------------------------------------------- /support/install.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | padding: 0; 4 | background-color: #FFFFFF; 5 | color: #222222; 6 | font-family: Verdana, Arial, Helvetica, sans-serif; 7 | } 8 | 9 | #headerwrap { 10 | padding: 0.5em; 11 | background-color: #222222; 12 | color: #FEFEFE; 13 | font-size: 1.7em; 14 | -webkit-box-shadow: 0px 3px 3px 0px rgba(0,0,0,0.15); 15 | -moz-box-shadow: 0px 3px 3px 0px rgba(0,0,0,0.15); 16 | box-shadow: 0px 3px 3px 0px rgba(0,0,0,0.15); 17 | } 18 | 19 | #header { 20 | max-width: 800px; 21 | margin-left: auto; 22 | margin-right: auto; 23 | } 24 | 25 | #contentwrap { 26 | padding: 0em 0.5em; 27 | } 28 | 29 | #content { 30 | max-width: 800px; 31 | margin-left: auto; 32 | margin-right: auto; 33 | } 34 | 35 | #footerwrap { 36 | margin-top: 3.0em; 37 | border-top: 1px dashed #CCCCCC; 38 | padding: 1.0em 0.5em 1.0em; 39 | } 40 | 41 | #footer { 42 | max-width: 800px; 43 | margin-left: auto; 44 | margin-right: auto; 45 | } -------------------------------------------------------------------------------- /support/cft.css: -------------------------------------------------------------------------------- 1 | .cft_hidden { display: none; } 2 | 3 | .cft_notifications_wrap { position: fixed; width: 400px; bottom: 0; right: 0; z-index: 5000; overflow: hidden; } 4 | .cft_notifications { position: relative; } 5 | .cft_notifications .cft_user_request { overflow: hidden; margin: 1em; border: 1px solid #157EFB; border-radius: 5px; background-color: #FFFFFF; color: #333333; box-shadow: 3px 3px 5px 0px rgba(0, 0, 0, 0.15); } 6 | .cft_notifications .cft_user_request .cft_user_name_wrap { padding: 0.5em 0.7em; background-color: #157EFB; color: #FFFFFF; font-size: 0.85em; font-weight: bold; } 7 | .cft_notifications .cft_user_request .cft_user_name { overflow: hidden; } 8 | .cft_notifications .cft_user_request .cft_user_send_message { padding: 0.5em 0.7em 0; font-size: 0.85em; } 9 | .cft_notifications .cft_user_request .cft_files { padding: 0 0.7em 0.5em; font-size: 0.85em; } 10 | .cft_notifications .cft_user_request .cft_files .cft_file { margin-top: 1.0em; } 11 | .cft_notifications .cft_user_request .cft_files .cft_file_controls { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } 12 | .cft_notifications .cft_user_request .cft_files .cft_file_controls a.cft_file_remove { display: inline-block; margin-right: 0.3em; color: #A94442 !important; font-weight: bold; text-decoration: none !important; } 13 | .cft_notifications .cft_user_request .cft_files .cft_file_controls a.cft_file_remove:hover { color: #DC423F !important; } 14 | .cft_notifications .cft_user_request .cft_files .cft_file_info { white-space: nowrap; color: #666666; } 15 | 16 | @media (max-width: 675px) { 17 | .cft_notifications_wrap { width: 300px; } 18 | } 19 | 20 | @media (max-width: 500px) { 21 | .cft_notifications_wrap { left: 0; right: auto; } 22 | } 23 | 24 | @media (max-width: 320px) { 25 | .cft_notifications_wrap { width: 250px; left: 0; right: auto; } 26 | } 27 | -------------------------------------------------------------------------------- /base.php: -------------------------------------------------------------------------------- 1 | false, 15 | "error" => $msg, 16 | "errorcode" => $msgcode 17 | ); 18 | 19 | echo json_encode($result, JSON_UNESCAPED_SLASHES); 20 | exit(); 21 | } 22 | 23 | function CFT_SendResult($result) 24 | { 25 | if (!headers_sent()) header("Content-Type: application/json"); 26 | 27 | echo json_encode($result, JSON_UNESCAPED_SLASHES); 28 | exit(); 29 | } 30 | 31 | // Constant-time string comparison. Ported from CubicleSoft C++ code. 32 | function CFT_CTstrcmp($secret, $userinput) 33 | { 34 | $sx = 0; 35 | $sy = strlen($secret); 36 | $uy = strlen($userinput); 37 | $result = $sy - $uy; 38 | for ($ux = 0; $ux < $uy; $ux++) 39 | { 40 | $result |= ord($userinput[$ux]) ^ ord($secret[$sx]); 41 | $sx = ($sx + 1) % $sy; 42 | } 43 | 44 | return $result; 45 | } 46 | 47 | // Disable PHP's internal timeout. 48 | set_time_limit(0); 49 | 50 | // Validate input. 51 | Str::ProcessAllInput(); 52 | 53 | if (!isset($_REQUEST["id"]) || !is_string($_REQUEST["id"])) CFT_DisplayError("Missing 'id'.", "missing_id"); 54 | if (!isset($_REQUEST["token"]) || !is_string($_REQUEST["token"])) CFT_DisplayError("Missing 'token'.", "missing_token"); 55 | 56 | // Connect to the database. 57 | require_once $config["rootpath"] . "/support/csdb/db_" . $config["db_select"] . ".php"; 58 | 59 | $dbclassname = "CSDB_" . $config["db_select"]; 60 | 61 | try 62 | { 63 | if ($config["db_master_dsn"] != "") $db = new $dbclassname($config["db_select"] . ":" . $config["db_master_dsn"], ($config["db_login"] ? $config["db_master_user"] : false), ($config["db_login"] ? $config["db_master_pass"] : false)); 64 | else $db = new $dbclassname($config["db_select"] . ":" . $config["db_dsn"], ($config["db_login"] ? $config["db_user"] : false), ($config["db_login"] ? $config["db_pass"] : false)); 65 | 66 | $db->Query("USE", $config["db_name"]); 67 | } 68 | catch (Exception $e) 69 | { 70 | CFT_DisplayError("Database connection failed.", "db_connect_failed"); 71 | } 72 | 73 | $dbprefix = $config["db_table_prefix"]; 74 | $cft_db_files = $dbprefix . "files"; 75 | 76 | // Retrieve waiting send file information. 77 | try 78 | { 79 | $row = $db->GetRow("SELECT", array( 80 | "*", 81 | "FROM" => "?", 82 | "WHERE" => "id = ?", 83 | ), $cft_db_files, $_REQUEST["id"]); 84 | } 85 | catch (Exception $e) 86 | { 87 | CFT_DisplayError("Unable to retrieve file. A database error occurred.", "db_error"); 88 | } 89 | 90 | if (!$row) CFT_DisplayError("File does not exist or is invalid.", "missing_file"); 91 | ?> -------------------------------------------------------------------------------- /support/csdb/db_mysql_lite.php: -------------------------------------------------------------------------------- 1 | Query("SET", "NAMES 'utf8mb4'"); 25 | } 26 | 27 | public function GetInsertID($name = null) 28 | { 29 | return $this->GetOne("SELECT", array("LAST_INSERT_ID()")); 30 | } 31 | 32 | public function QuoteIdentifier($str) 33 | { 34 | return "`" . str_replace(array("`", "?"), array("``", ""), $str) . "`"; 35 | } 36 | 37 | protected function GenerateSQL(&$master, &$sql, &$opts, $cmd, $queryinfo, $args, $subquery) 38 | { 39 | switch ($cmd) 40 | { 41 | case "SELECT": 42 | { 43 | $supported = array( 44 | "PRECOLUMN" => array("DISTINCT" => "bool", "HIGH_PRIORITY" => "bool", "SUBQUERIES" => true), 45 | "FROM" => array("SUBQUERIES" => true), 46 | "WHERE" => array("SUBQUERIES" => true), 47 | "GROUP BY" => true, 48 | "HAVING" => true, 49 | "ORDER BY" => true, 50 | "LIMIT" => ", " 51 | ); 52 | 53 | return $this->ProcessSELECT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 54 | } 55 | case "INSERT": 56 | { 57 | $supported = array( 58 | "PREINTO" => array("LOW_PRIORITY" => "bool", "DELAYED" => "bool", "HIGH_PRIORITY" => "bool", "IGNORE" => "bool"), 59 | "SELECT" => true, 60 | "BULKINSERT" => true 61 | ); 62 | 63 | return $this->ProcessINSERT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 64 | } 65 | case "UPDATE": 66 | { 67 | $supported = array( 68 | "PRETABLE" => array("LOW_PRIORITY" => "bool", "IGNORE" => "bool"), 69 | "WHERE" => array("SUBQUERIES" => true), 70 | "ORDER BY" => true, 71 | "LIMIT" => ", " 72 | ); 73 | 74 | return $this->ProcessUPDATE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 75 | } 76 | case "DELETE": 77 | { 78 | $supported = array( 79 | "PREFROM" => array("LOW_PRIORITY" => "bool", "QUICK" => "bool", "IGNORE" => "bool"), 80 | "WHERE" => array("SUBQUERIES" => true), 81 | "ORDER BY" => true, 82 | "LIMIT" => ", " 83 | ); 84 | 85 | return $this->ProcessDELETE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 86 | } 87 | case "SET": 88 | { 89 | $sql = "SET " . $queryinfo; 90 | 91 | return array("success" => true); 92 | } 93 | case "USE": 94 | { 95 | $sql = "USE " . $this->QuoteIdentifier($queryinfo); 96 | 97 | return array("success" => true); 98 | } 99 | case "TRUNCATE TABLE": 100 | { 101 | $master = true; 102 | 103 | $sql = "TRUNCATE TABLE " . $this->QuoteIdentifier($queryinfo[0]); 104 | 105 | return array("success" => true); 106 | } 107 | } 108 | 109 | return array("success" => false, "error" => CSDB::DB_Translate("Unknown query command '%s'.", $cmd), "errorcode" => "unknown_query_command"); 110 | } 111 | } 112 | ?> -------------------------------------------------------------------------------- /support/fancy-file-uploader/cors/jquery.xdr-transport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery XDomainRequest Transport Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2011, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * https://opensource.org/licenses/MIT 10 | * 11 | * Based on Julian Aubourg's ajaxHooks xdr.js: 12 | * https://github.com/jaubourg/ajaxHooks/ 13 | */ 14 | 15 | /* global define, require, window, XDomainRequest */ 16 | 17 | ;(function (factory) { 18 | 'use strict'; 19 | if (typeof define === 'function' && define.amd) { 20 | // Register as an anonymous AMD module: 21 | define(['jquery'], factory); 22 | } else if (typeof exports === 'object') { 23 | // Node/CommonJS: 24 | factory(require('jquery')); 25 | } else { 26 | // Browser globals: 27 | factory(window.jQuery); 28 | } 29 | }(function ($) { 30 | 'use strict'; 31 | if (window.XDomainRequest && !$.support.cors) { 32 | $.ajaxTransport(function (s) { 33 | if (s.crossDomain && s.async) { 34 | if (s.timeout) { 35 | s.xdrTimeout = s.timeout; 36 | delete s.timeout; 37 | } 38 | var xdr; 39 | return { 40 | send: function (headers, completeCallback) { 41 | var addParamChar = /\?/.test(s.url) ? '&' : '?'; 42 | function callback(status, statusText, responses, responseHeaders) { 43 | xdr.onload = xdr.onerror = xdr.ontimeout = $.noop; 44 | xdr = null; 45 | completeCallback(status, statusText, responses, responseHeaders); 46 | } 47 | xdr = new XDomainRequest(); 48 | // XDomainRequest only supports GET and POST: 49 | if (s.type === 'DELETE') { 50 | s.url = s.url + addParamChar + '_method=DELETE'; 51 | s.type = 'POST'; 52 | } else if (s.type === 'PUT') { 53 | s.url = s.url + addParamChar + '_method=PUT'; 54 | s.type = 'POST'; 55 | } else if (s.type === 'PATCH') { 56 | s.url = s.url + addParamChar + '_method=PATCH'; 57 | s.type = 'POST'; 58 | } 59 | xdr.open(s.type, s.url); 60 | xdr.onload = function () { 61 | callback( 62 | 200, 63 | 'OK', 64 | {text: xdr.responseText}, 65 | 'Content-Type: ' + xdr.contentType 66 | ); 67 | }; 68 | xdr.onerror = function () { 69 | callback(404, 'Not Found'); 70 | }; 71 | if (s.xdrTimeout) { 72 | xdr.ontimeout = function () { 73 | callback(0, 'timeout'); 74 | }; 75 | xdr.timeout = s.xdrTimeout; 76 | } 77 | xdr.send((s.hasContent && s.data) || null); 78 | }, 79 | abort: function () { 80 | if (xdr) { 81 | xdr.onerror = $.noop(); 82 | xdr.abort(); 83 | } 84 | } 85 | }; 86 | } 87 | }); 88 | } 89 | })); 90 | -------------------------------------------------------------------------------- /support/csdb/db_sqlite_lite.php: -------------------------------------------------------------------------------- 1 | dbprefix = ""; 24 | 25 | parent::Connect($dsn, $username, $password, $options); 26 | } 27 | 28 | public function GetInsertID($name = null) 29 | { 30 | return $this->GetOne("SELECT", array("LAST_INSERT_ROWID()")); 31 | } 32 | 33 | public function QuoteIdentifier($str) 34 | { 35 | return "\"" . str_replace(array("\"", "?"), array("\"\"", ""), $str) . "\""; 36 | } 37 | 38 | protected function GenerateSQL(&$master, &$sql, &$opts, $cmd, $queryinfo, $args, $subquery) 39 | { 40 | switch ($cmd) 41 | { 42 | case "SELECT": 43 | { 44 | $supported = array( 45 | "DBPREFIX" => $this->dbprefix, 46 | "PRECOLUMN" => array("DISTINCT" => "bool", "SUBQUERIES" => true), 47 | "FROM" => array("SUBQUERIES" => true), 48 | "WHERE" => array("SUBQUERIES" => true), 49 | "GROUP BY" => true, 50 | "HAVING" => true, 51 | "ORDER BY" => true, 52 | "LIMIT" => ", " 53 | ); 54 | 55 | return $this->ProcessSELECT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 56 | } 57 | case "INSERT": 58 | { 59 | $supported = array( 60 | "DBPREFIX" => $this->dbprefix, 61 | "PREINTO" => array("LOW_PRIORITY" => "bool", "DELAYED" => "bool", "HIGH_PRIORITY" => "bool", "IGNORE" => "bool"), 62 | "SELECT" => true, 63 | "BULKINSERT" => true, 64 | "BULKINSERTLIMIT" => 900, 65 | ); 66 | 67 | return $this->ProcessINSERT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 68 | } 69 | case "UPDATE": 70 | { 71 | $supported = array( 72 | "DBPREFIX" => $this->dbprefix, 73 | "PRETABLE" => array("LOW_PRIORITY" => "bool", "IGNORE" => "bool"), 74 | "WHERE" => array("SUBQUERIES" => true), 75 | "ORDER BY" => true, 76 | "LIMIT" => ", " 77 | ); 78 | 79 | return $this->ProcessUPDATE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 80 | } 81 | case "DELETE": 82 | { 83 | $supported = array( 84 | "DBPREFIX" => $this->dbprefix, 85 | "PREFROM" => array("LOW_PRIORITY" => "bool", "QUICK" => "bool", "IGNORE" => "bool"), 86 | "WHERE" => array("SUBQUERIES" => true), 87 | "ORDER BY" => true, 88 | "LIMIT" => ", " 89 | ); 90 | 91 | return $this->ProcessDELETE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 92 | } 93 | case "SET": 94 | { 95 | return array("success" => false, "errorcode" => "skip_sql_query"); 96 | } 97 | case "USE": 98 | { 99 | $this->dbprefix = $this->GetDBPrefix($queryinfo); 100 | 101 | return array("success" => false, "errorcode" => "skip_sql_query"); 102 | } 103 | case "TRUNCATE TABLE": 104 | { 105 | $supported = array( 106 | "DBPREFIX" => $this->dbprefix, 107 | "PREFROM" => array() 108 | ); 109 | 110 | $queryinfo = array($queryinfo[0]); 111 | 112 | return $this->ProcessDELETE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 113 | } 114 | } 115 | 116 | return array("success" => false, "error" => CSDB::DB_Translate("Unknown query command '%s'.", $cmd), "errorcode" => "unknown_query_command"); 117 | } 118 | 119 | private function GetDBPrefix($str) 120 | { 121 | $str = preg_replace('/\s+/', "_", trim(str_replace("_", " ", $str))); 122 | 123 | return ($str != "" ? $str . "__" : ""); 124 | } 125 | } 126 | ?> -------------------------------------------------------------------------------- /support/flex_forms.css: -------------------------------------------------------------------------------- 1 | .ff_formwrap .ff_formwrapinner .formitem { 2 | margin-top: 1.0em; 3 | } 4 | 5 | .ff_formwrap .ff_formwrapinner .formitemtitle { 6 | color: #222222; 7 | margin-bottom: 0.2em; 8 | font-weight: bold; 9 | } 10 | 11 | .ff_formwrap .ff_formwrapinner .formfields > hr { 12 | border: 0 none; 13 | border-top: 1px dashed #BBBBBB; 14 | } 15 | 16 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata input, .ff_formwrap .ff_formwrapinner .formitem .formitemdata textarea, .ff_formwrap .ff_formwrapinner .formitem .formitemdata select { 17 | outline: none; 18 | } 19 | 20 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata input.text { 21 | box-sizing: border-box; 22 | width: 100%; 23 | font-size: 0.9em; 24 | padding: 0.3em; 25 | border: 1px solid #BBBBBB; 26 | } 27 | 28 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata input.text:focus { 29 | border: 1px solid #888888; 30 | } 31 | 32 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata input.text:hover { 33 | border: 1px solid #888888; 34 | } 35 | 36 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata textarea { 37 | box-sizing: border-box; 38 | width: 100%; 39 | font-size: 0.9em; 40 | padding: 0.3em; 41 | border: 1px solid #BBBBBB; 42 | resize: vertical; 43 | } 44 | 45 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata textarea:focus { 46 | border: 1px solid #888888; 47 | } 48 | 49 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata textarea:hover { 50 | border: 1px solid #888888; 51 | } 52 | 53 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata .radioitemwrap { 54 | margin-left: 1.7em; 55 | text-indent: -1.7em; 56 | margin-bottom: 0.3em; 57 | } 58 | 59 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata .checkboxitemwrap { 60 | margin-left: 1.7em; 61 | text-indent: -1.7em; 62 | margin-bottom: 0.3em; 63 | } 64 | 65 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata select { 66 | box-sizing: border-box; 67 | width: 100%; 68 | font-size: 0.9em; 69 | border: 1px solid #BBBBBB; 70 | } 71 | 72 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata select:focus { 73 | border: 1px solid #888888; 74 | } 75 | 76 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata select:hover { 77 | border: 1px solid #888888; 78 | } 79 | 80 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata .staticwrap { 81 | margin-left: 0.5em; 82 | } 83 | 84 | .ff_formwrap .ff_formwrapinner .formitem .formitemdesc { 85 | color: #333333; 86 | margin-bottom: 0.2em; 87 | margin-left: 0.5em; 88 | font-size: 0.9em; 89 | } 90 | 91 | .ff_formwrap .ff_formwrapinner .formitem .formitemresult { 92 | margin-left: 0.5em; 93 | } 94 | 95 | .ff_formwrap .ff_formwrapinner .formitem .formitemresult .formitemerror { 96 | background: url('flex_forms_error.png') 0 0.1em no-repeat; 97 | padding-left: 25px; 98 | color: #A94442; 99 | font-weight: bold; 100 | } 101 | 102 | .ff_formwrap .ff_formwrapinner .fieldtablewrap > table.rowwrap { 103 | border-collapse: collapse; 104 | } 105 | 106 | .ff_formwrap .ff_formwrapinner .fieldtablewrap > table.rowwrap > tbody > tr > td { 107 | padding-right: 1.0em; 108 | vertical-align: top; 109 | } 110 | 111 | .ff_formwrap .ff_formwrapinner .fieldtablewrap .formitemtitle { 112 | white-space: nowrap; 113 | } 114 | 115 | .ff_formwrap .ff_formwrapinner .nowrap { 116 | white-space: nowrap; 117 | } 118 | 119 | .ff_formwrap .ff_formwrapinner .formsubmit { 120 | margin-top: 1.2em; 121 | } 122 | 123 | .ff_formwrap .ff_formwrapinner .formsubmit input { 124 | padding: 0.2em 0.5em; 125 | font-weight: bold; 126 | font-size: 1.0em; 127 | color: #1F1F1F; 128 | outline: none; 129 | } 130 | 131 | .ff_formmessagewrap .ff_formmessagewrapinner .message { 132 | border: 1px solid transparent; 133 | border-radius: 4px; 134 | margin-top: 1.0em; 135 | padding: 0.5em 0.7em; 136 | } 137 | 138 | .ff_formmessagewrap .ff_formmessagewrapinner .messagesuccess { 139 | border-color: #B2DBA1; 140 | background-color: #DFF0D8; 141 | background-image: linear-gradient(to bottom, #DFF0D8 0px, #C8E5BC 100%); 142 | background-repeat: repeat-x; 143 | color: #3C763D; 144 | } 145 | 146 | .ff_formmessagewrap .ff_formmessagewrapinner .messagewarning { 147 | border-color: #F5E79E; 148 | background-color: #FCF8E3; 149 | background-image: linear-gradient(to bottom, #FCF8E3 0px, #F8EFC0 100%); 150 | background-repeat: repeat-x; 151 | color: #8A6D3B; 152 | } 153 | 154 | .ff_formmessagewrap .ff_formmessagewrapinner .messageerror { 155 | border-color: #DCA7A7; 156 | background-color: #F2DEDE; 157 | background-image: linear-gradient(to bottom, #F2DEDE 0px, #E7C3C3 100%); 158 | background-repeat: repeat-x; 159 | color: #A94442; 160 | } 161 | 162 | .ff_formmessagewrap .ff_formmessagewrapinner .messageinfo { 163 | border-color: #9ACFEA; 164 | background-color: #D9EDF7; 165 | background-image: linear-gradient(to bottom, #D9EDF7 0px, #B9DEF0 100%); 166 | background-repeat: repeat-x; 167 | color: #31708F; 168 | } 169 | -------------------------------------------------------------------------------- /support/str_basics.php: -------------------------------------------------------------------------------- 1 | $val) 10 | { 11 | if (is_string($val)) $_REQUEST[$key] = trim($val); 12 | else if (is_array($val)) 13 | { 14 | $_REQUEST[$key] = array(); 15 | foreach ($val as $key2 => $val2) $_REQUEST[$key][$key2] = (is_string($val2) ? trim($val2) : $val2); 16 | } 17 | else $_REQUEST[$key] = $val; 18 | } 19 | } 20 | 21 | // Cleans up all PHP input issues so that $_REQUEST may be used as expected. 22 | public static function ProcessAllInput() 23 | { 24 | self::ProcessSingleInput($_COOKIE); 25 | self::ProcessSingleInput($_GET); 26 | self::ProcessSingleInput($_POST); 27 | } 28 | 29 | public static function ExtractPathname($dirfile) 30 | { 31 | $dirfile = str_replace("\\", "/", $dirfile); 32 | $pos = strrpos($dirfile, "/"); 33 | if ($pos === false) $dirfile = ""; 34 | else $dirfile = substr($dirfile, 0, $pos + 1); 35 | 36 | return $dirfile; 37 | } 38 | 39 | public static function ExtractFilename($dirfile) 40 | { 41 | $dirfile = str_replace("\\", "/", $dirfile); 42 | $pos = strrpos($dirfile, "/"); 43 | if ($pos !== false) $dirfile = substr($dirfile, $pos + 1); 44 | 45 | return $dirfile; 46 | } 47 | 48 | public static function ExtractFileExtension($dirfile) 49 | { 50 | $dirfile = self::ExtractFilename($dirfile); 51 | $pos = strrpos($dirfile, "."); 52 | if ($pos !== false) $dirfile = substr($dirfile, $pos + 1); 53 | else $dirfile = ""; 54 | 55 | return $dirfile; 56 | } 57 | 58 | public static function ExtractFilenameNoExtension($dirfile) 59 | { 60 | $dirfile = self::ExtractFilename($dirfile); 61 | $pos = strrpos($dirfile, "."); 62 | if ($pos !== false) $dirfile = substr($dirfile, 0, $pos); 63 | 64 | return $dirfile; 65 | } 66 | 67 | // Makes an input filename safe for use. 68 | // Allows a very limited number of characters through. 69 | public static function FilenameSafe($filename) 70 | { 71 | return preg_replace('/\s+/', "-", trim(trim(preg_replace('/[^A-Za-z0-9_.\-]/', " ", $filename), "."))); 72 | } 73 | 74 | public static function ReplaceNewlines($replacewith, $data) 75 | { 76 | $data = str_replace("\r\n", "\n", $data); 77 | $data = str_replace("\r", "\n", $data); 78 | $data = str_replace("\n", $replacewith, $data); 79 | 80 | return $data; 81 | } 82 | 83 | public static function LineInput($data, &$pos) 84 | { 85 | $CR = ord("\r"); 86 | $LF = ord("\n"); 87 | 88 | $result = ""; 89 | $y = strlen($data); 90 | if ($pos > $y) $pos = $y; 91 | while ($pos < $y && ord($data[$pos]) != $CR && ord($data[$pos]) != $LF) 92 | { 93 | $result .= $data[$pos]; 94 | $pos++; 95 | } 96 | if ($pos + 1 < $y && ord($data[$pos]) == $CR && ord($data[$pos + 1]) == $LF) $pos++; 97 | if ($pos < $y) $pos++; 98 | 99 | return $result; 100 | } 101 | 102 | // Constant-time string comparison. Ported from CubicleSoft C++ code. 103 | public static function CTstrcmp($secret, $userinput) 104 | { 105 | $sx = 0; 106 | $sy = strlen($secret); 107 | $uy = strlen($userinput); 108 | $result = $sy - $uy; 109 | for ($ux = 0; $ux < $uy; $ux++) 110 | { 111 | $result |= ord($userinput[$ux]) ^ ord($secret[$sx]); 112 | $sx = ($sx + 1) % $sy; 113 | } 114 | 115 | return $result; 116 | } 117 | 118 | public static function ConvertUserStrToBytes($str) 119 | { 120 | $str = trim($str); 121 | $num = (double)$str; 122 | if (strtoupper(substr($str, -1)) == "B") $str = substr($str, 0, -1); 123 | switch (strtoupper(substr($str, -1))) 124 | { 125 | case "P": $num *= 1024; 126 | case "T": $num *= 1024; 127 | case "G": $num *= 1024; 128 | case "M": $num *= 1024; 129 | case "K": $num *= 1024; 130 | } 131 | 132 | return $num; 133 | } 134 | 135 | public static function ConvertBytesToUserStr($num) 136 | { 137 | $num = (double)$num; 138 | 139 | if ($num < 0) return "0 B"; 140 | if ($num < 1024) return number_format($num, 0) . " B"; 141 | if ($num < 1048576) return str_replace(".0 ", "", number_format($num / 1024, 1)) . " KB"; 142 | if ($num < 1073741824) return str_replace(".0 ", "", number_format($num / 1048576, 1)) . " MB"; 143 | if ($num < 1099511627776.0) return str_replace(".0 ", "", number_format($num / 1073741824.0, 1)) . " GB"; 144 | if ($num < 1125899906842624.0) return str_replace(".0 ", "", number_format($num / 1099511627776.0, 1)) . " TB"; 145 | 146 | return str_replace(".0 ", "", number_format($num / 1125899906842624.0, 1)) . " PB"; 147 | } 148 | 149 | public static function JSSafe($data) 150 | { 151 | return str_replace(array("'", "\r", "\n"), array("\\'", "\\r", "\\n"), $data); 152 | } 153 | } 154 | ?> -------------------------------------------------------------------------------- /support/csdb/db_pgsql_lite.php: -------------------------------------------------------------------------------- 1 | lastid = 0; 24 | 25 | parent::Connect($dsn, $username, $password, $options); 26 | 27 | // Set Unicode support. 28 | $this->Query("SET", "client_encoding TO 'UTF-8'"); 29 | } 30 | 31 | public function GetInsertID($name = null) 32 | { 33 | return $this->lastid; 34 | } 35 | 36 | public function QuoteIdentifier($str) 37 | { 38 | return "\"" . str_replace(array("\"", "?"), array("\"\"", ""), $str) . "\""; 39 | } 40 | 41 | protected function GenerateSQL(&$master, &$sql, &$opts, $cmd, $queryinfo, $args, $subquery) 42 | { 43 | switch ($cmd) 44 | { 45 | case "SELECT": 46 | { 47 | $supported = array( 48 | "PRECOLUMN" => array("DISTINCT" => "bool", "SUBQUERIES" => true), 49 | "FROM" => array("SUBQUERIES" => true), 50 | "WHERE" => array("SUBQUERIES" => true), 51 | "GROUP BY" => true, 52 | "HAVING" => true, 53 | "ORDER BY" => true, 54 | "LIMIT" => " OFFSET " 55 | ); 56 | 57 | return $this->ProcessSELECT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 58 | } 59 | case "INSERT": 60 | { 61 | $supported = array( 62 | "PREINTO" => array(), 63 | "POSTVALUES" => array("RETURNING" => "key_identifier"), 64 | "SELECT" => true, 65 | "BULKINSERT" => true 66 | ); 67 | 68 | // To get the last insert ID via GetInsertID(), the field that contains a 'serial' (auto increment) field must be specified. 69 | if (isset($queryinfo["AUTO INCREMENT"])) $queryinfo["RETURNING"] = $queryinfo["AUTO INCREMENT"]; 70 | 71 | $this->lastid = 0; 72 | $result = $this->ProcessINSERT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 73 | if ($result["success"] && isset($queryinfo["AUTO INCREMENT"])) $result["filter_opts"] = array("mode" => "INSERT", "queryinfo" => $queryinfo); 74 | 75 | return $result; 76 | } 77 | case "UPDATE": 78 | { 79 | // No ORDER BY or LIMIT support. 80 | $supported = array( 81 | "PRETABLE" => array("ONLY" => "bool"), 82 | "WHERE" => array("SUBQUERIES" => true) 83 | ); 84 | 85 | return $this->ProcessUPDATE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 86 | } 87 | case "DELETE": 88 | { 89 | // No ORDER BY or LIMIT support. 90 | $supported = array( 91 | "PREFROM" => array("ONLY" => "bool"), 92 | "WHERE" => array("SUBQUERIES" => true) 93 | ); 94 | 95 | return $this->ProcessDELETE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 96 | } 97 | case "SET": 98 | { 99 | $sql = "SET " . $queryinfo; 100 | 101 | return array("success" => true); 102 | } 103 | case "USE": 104 | { 105 | // Fake multiple databases with PostgreSQL schemas. 106 | // http://www.postgresql.org/docs/7.3/static/ddl-schemas.html 107 | $sql = "SET search_path TO " . ($queryinfo != "" ? $this->QuoteIdentifier($queryinfo) . "," : "") . "\"\$user\",public"; 108 | 109 | return array("success" => true); 110 | } 111 | case "TRUNCATE TABLE": 112 | { 113 | $master = true; 114 | 115 | $sql = "TRUNCATE TABLE " . $this->QuoteIdentifier($queryinfo[0]); 116 | 117 | return array("success" => true); 118 | } 119 | } 120 | 121 | return array("success" => false, "error" => CSDB::DB_Translate("Unknown query command '%s'.", $cmd), "errorcode" => "unknown_query_command"); 122 | } 123 | 124 | protected function RunStatementFilter(&$stmt, &$filteropts) 125 | { 126 | if ($filteropts["mode"] == "INSERT") 127 | { 128 | // Force the last ID value to be extracted for INSERT queries. 129 | $result = new CSDB_PDO_Statement($this, $stmt, $filteropts); 130 | $row = $result->NextRow(); 131 | 132 | $stmt = false; 133 | } 134 | 135 | parent::RunStatementFilter($stmt, $filteropts); 136 | } 137 | 138 | public function RunRowFilter(&$row, &$filteropts, &$fetchnext) 139 | { 140 | switch ($filteropts["mode"]) 141 | { 142 | case "INSERT": 143 | { 144 | if ($row !== false) 145 | { 146 | $key = $filteropts["queryinfo"]["AUTO INCREMENT"]; 147 | $this->lastid = $row->$key; 148 | } 149 | 150 | break; 151 | } 152 | } 153 | 154 | if (!$fetchnext) parent::RunRowFilter($row, $filteropts, $fetchnext); 155 | } 156 | } 157 | ?> -------------------------------------------------------------------------------- /support/fancy-file-uploader/cors/jquery.postmessage-transport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery postMessage Transport Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2011, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * https://opensource.org/licenses/MIT 10 | */ 11 | 12 | /* global define, require, window, document */ 13 | 14 | ;(function (factory) { 15 | 'use strict'; 16 | if (typeof define === 'function' && define.amd) { 17 | // Register as an anonymous AMD module: 18 | define(['jquery'], factory); 19 | } else if (typeof exports === 'object') { 20 | // Node/CommonJS: 21 | factory(require('jquery')); 22 | } else { 23 | // Browser globals: 24 | factory(window.jQuery); 25 | } 26 | }(function ($) { 27 | 'use strict'; 28 | 29 | var counter = 0, 30 | names = [ 31 | 'accepts', 32 | 'cache', 33 | 'contents', 34 | 'contentType', 35 | 'crossDomain', 36 | 'data', 37 | 'dataType', 38 | 'headers', 39 | 'ifModified', 40 | 'mimeType', 41 | 'password', 42 | 'processData', 43 | 'timeout', 44 | 'traditional', 45 | 'type', 46 | 'url', 47 | 'username' 48 | ], 49 | convert = function (p) { 50 | return p; 51 | }; 52 | 53 | $.ajaxSetup({ 54 | converters: { 55 | 'postmessage text': convert, 56 | 'postmessage json': convert, 57 | 'postmessage html': convert 58 | } 59 | }); 60 | 61 | $.ajaxTransport('postmessage', function (options) { 62 | if (options.postMessage && window.postMessage) { 63 | var iframe, 64 | loc = $('').prop('href', options.postMessage)[0], 65 | target = loc.protocol + '//' + loc.host, 66 | xhrUpload = options.xhr().upload; 67 | // IE always includes the port for the host property of a link 68 | // element, but not in the location.host or origin property for the 69 | // default http port 80 and https port 443, so we strip it: 70 | if (/^(http:\/\/.+:80)|(https:\/\/.+:443)$/.test(target)) { 71 | target = target.replace(/:(80|443)$/, ''); 72 | } 73 | return { 74 | send: function (_, completeCallback) { 75 | counter += 1; 76 | var message = { 77 | id: 'postmessage-transport-' + counter 78 | }, 79 | eventName = 'message.' + message.id; 80 | iframe = $( 81 | '' 84 | ).bind('load', function () { 85 | $.each(names, function (i, name) { 86 | message[name] = options[name]; 87 | }); 88 | message.dataType = message.dataType.replace('postmessage ', ''); 89 | $(window).bind(eventName, function (e) { 90 | e = e.originalEvent; 91 | var data = e.data, 92 | ev; 93 | if (e.origin === target && data.id === message.id) { 94 | if (data.type === 'progress') { 95 | ev = document.createEvent('Event'); 96 | ev.initEvent(data.type, false, true); 97 | $.extend(ev, data); 98 | xhrUpload.dispatchEvent(ev); 99 | } else { 100 | completeCallback( 101 | data.status, 102 | data.statusText, 103 | {postmessage: data.result}, 104 | data.headers 105 | ); 106 | iframe.remove(); 107 | $(window).unbind(eventName); 108 | } 109 | } 110 | }); 111 | iframe[0].contentWindow.postMessage( 112 | message, 113 | target 114 | ); 115 | }).appendTo(document.body); 116 | }, 117 | abort: function () { 118 | if (iframe) { 119 | iframe.remove(); 120 | } 121 | } 122 | }; 123 | } 124 | }); 125 | 126 | })); 127 | -------------------------------------------------------------------------------- /send/index.php: -------------------------------------------------------------------------------- 1 | sendtoken, $_REQUEST["token"])) CFT_DisplayError("File send token is invalid.", "invalid_token"); 9 | 10 | if (isset($_FILES["file"])) 11 | { 12 | // Handle incoming file data. 13 | require_once $config["rootpath"] . "/support/flex_forms.php"; 14 | require_once $config["rootpath"] . "/support/flex_forms_fileuploader.php"; 15 | 16 | $files = FlexForms::NormalizeFiles("file"); 17 | if (!isset($files[0])) CFT_DisplayError("File data was submitted but is missing.", "bad_input"); 18 | if (!$files[0]["success"]) CFT_DisplayError($files[0]["error"], $files[0]["errorcode"]); 19 | 20 | if ($row->port < 1) CFT_DisplayError("Receiver not initialized.", "recv_not_initialized"); 21 | 22 | // Update the database. 23 | try 24 | { 25 | $db->Query("UPDATE", array($cft_db_files, array( 26 | "lastrequest" => time(), 27 | ), "WHERE" => "id = ?"), $row->id); 28 | } 29 | catch (Exception $e) 30 | { 31 | CFT_DisplayError("Unable to update the file. A database error occurred.", "db_error"); 32 | } 33 | 34 | // Start response. Prevent the browser from disconnecting. 35 | header("Content-Type: application/json"); 36 | @ob_flush(); 37 | @flush(); 38 | 39 | // Connect to the server. 40 | $fp = @stream_socket_client("tcp://127.0.0.1:" . $row->port, $errornum, $errorstr, 30, STREAM_CLIENT_CONNECT); 41 | if ($fp === false) CFT_DisplayError("Failed to connect to local transport interface.", "local_transport_failed"); 42 | 43 | // Send the information to the server. 44 | // There might be a security vulnerability here with hijacking uploaded data via TCP/IP port reuse. 45 | // To solve, the server could present some information first (e.g. the recvtoken and, if valid, this code then sends sendtoken). 46 | // But doing all of that requires an extra round trip over TCP/IP, which seems overkill. An irony since I tend to do overkill. 47 | $data = array( 48 | "token" => $row->recvtoken, 49 | "filename" => $files[0]["file"] 50 | ); 51 | 52 | if (isset($_SERVER["HTTP_CONTENT_RANGE"])) $data["startpos"] = FlexForms_FileUploader::GetFileStartPosition(); 53 | 54 | @fwrite($fp, json_encode($data, JSON_UNESCAPED_SLASHES) . "\n"); 55 | 56 | $data = @fgets($fp); 57 | 58 | fclose($fp); 59 | 60 | $result = @json_decode($data, true); 61 | if (!is_array($result) || !isset($result["success"])) CFT_DisplayError("Receiver responded with invalid response.", "invalid_receiver_response"); 62 | 63 | CFT_SendResult($result); 64 | } 65 | else if (isset($_REQUEST["action"]) && $_REQUEST["action"] === "cancel") 66 | { 67 | // Cancelling is a matter of deleting the database row. 68 | try 69 | { 70 | $db->Query("DELETE", array($cft_db_files, "WHERE" => "id = ?"), $row->id); 71 | } 72 | catch (Exception $e) 73 | { 74 | CFT_DisplayError("Unable to cancel the file. A database error occurred.", "db_error"); 75 | } 76 | } 77 | else 78 | { 79 | // Start a TCP/IP server on a random port number. 80 | $serverfp = @stream_socket_server("tcp://127.0.0.1:0", $errornum, $errorstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN); 81 | if ($serverfp === false) CFT_DisplayError("Failed to start local transport interface.", "local_transport_failed"); 82 | 83 | $serverinfo = stream_socket_get_name($serverfp, false); 84 | $pos = strrpos($serverinfo, ":"); 85 | $serverip = substr($serverinfo, 0, $pos); 86 | $serverport = (int)substr($serverinfo, $pos + 1); 87 | 88 | // Update the database with the new server port. 89 | try 90 | { 91 | $db->Query("UPDATE", array($cft_db_files, array( 92 | "lastrequest" => time(), 93 | "port" => -$serverport, 94 | ), "WHERE" => "id = ? AND port < 1"), $row->id); 95 | } 96 | catch (Exception $e) 97 | { 98 | CFT_DisplayError("Unable to update the file. A database error occurred.", "db_error"); 99 | } 100 | 101 | // Prepare for the main loop. Prevent the browser from disconnecting. 102 | header("Content-Type: application/json"); 103 | @ob_flush(); 104 | @flush(); 105 | 106 | // Main loop. 107 | $lastdbupdate = $lastoutput = time(); 108 | do 109 | { 110 | // Wait for a connection. 111 | // I'm aware that this is NOT the correct way to use stream_select(). 112 | // It's for non-blocking use only but stream_socket_accept() doesn't block until there is a connection AND I don't want early termination of the TCP/IP handshake. 113 | $readfps = array($serverfp); 114 | $writefps = array(); 115 | $exceptfps = NULL; 116 | $timeout = min($lastdbupdate + 60 - time(), $lastoutput + 15 - time()); 117 | if ($timeout < 1) $timeout = 0; 118 | $timeout++; 119 | $result = @stream_select($readfps, $writefps, $exceptfps, $timeout); 120 | if ($result === false) CFT_DisplayError("Failed to wait for local transport interface.", "local_transport_failed"); 121 | 122 | if (count($readfps) && ($fp = @stream_socket_accept($serverfp)) !== false) 123 | { 124 | $lastdbupdate = 0; 125 | 126 | fclose($fp); 127 | } 128 | 129 | // Update the database once per minute. 130 | if ($lastdbupdate < time() - 60) 131 | { 132 | try 133 | { 134 | $db->Query("UPDATE", array($cft_db_files, array( 135 | "lastrequest" => time(), 136 | ), "WHERE" => "id = ?"), $row->id); 137 | } 138 | catch (Exception $e) 139 | { 140 | CFT_DisplayError("Unable to update the file. A database error occurred.", "db_error"); 141 | } 142 | 143 | try 144 | { 145 | $row = $db->GetRow("SELECT", array( 146 | "*", 147 | "FROM" => "?", 148 | "WHERE" => "id = ?", 149 | ), $cft_db_files, $row->id); 150 | } 151 | catch (Exception $e) 152 | { 153 | CFT_DisplayError("Unable to retrieve file. A database error occurred.", "db_error"); 154 | } 155 | 156 | if (!$row) CFT_DisplayError("Recipient rejected the request. Try again later.", "recipient_rejected"); 157 | 158 | if ($row->port > 0) break; 159 | 160 | $lastdbupdate = time(); 161 | } 162 | 163 | // Output some whitespace every 15 seconds to keep the client connection alive. 164 | if ($lastoutput < time() - 15) 165 | { 166 | echo " "; 167 | @ob_flush(); 168 | @flush(); 169 | 170 | $lastoutput = time(); 171 | } 172 | 173 | } while (1); 174 | } 175 | 176 | $result = array( 177 | "success" => true 178 | ); 179 | 180 | CFT_SendResult($result); 181 | ?> -------------------------------------------------------------------------------- /support/cft.js: -------------------------------------------------------------------------------- 1 | // Cool File Transfer 2 | // (C) 2017 CubicleSoft. All Rights Reserved. 3 | 4 | window.CoolFileTransfer = window.CoolFileTransfer || { 5 | 'targeturl' : '', 6 | 'initrequest' : {}, 7 | 'langmap' : {}, 8 | 9 | Translate : function(str) { 10 | return (CoolFileTransfer.langmap[str] ? CoolFileTransfer.langmap[str] : str); 11 | }, 12 | 13 | EscapeHTML : function(text) { 14 | var map = { 15 | '&': '&', 16 | '<': '<', 17 | '>': '>', 18 | '"': '"', 19 | "'": ''' 20 | }; 21 | 22 | return text.replace(/[&<>"']/g, function(m) { return map[m]; }); 23 | }, 24 | 25 | FormatStr : function(format) { 26 | var args = Array.prototype.slice.call(arguments, 1); 27 | 28 | return format.replace(/{(\d+)}/g, function(match, number) { 29 | return (typeof args[number] != 'undefined' ? args[number] : match); 30 | }); 31 | }, 32 | 33 | MaxChunkSize : function(options) { 34 | if (options._progress.loaded === 0) return 65536; 35 | 36 | var size = Math.floor(options._progress.bitrate * 1.25 / 8); 37 | 38 | if (size < 65536) size = 65536; 39 | 40 | return Math.min(options.origMaxChunkSize, size); 41 | }, 42 | 43 | PreInit : function(settings) { 44 | // Alter the max chunk size option to use dynamic adaptation based on the bit rate. 45 | settings.fileupload.origMaxChunkSize = settings.fileupload.maxChunkSize; 46 | settings.fileupload.maxChunkSize = CoolFileTransfer.MaxChunkSize; 47 | }, 48 | 49 | StartUpload : function(SubmitUpload, e, data) { 50 | var $this = this; 51 | 52 | $this.find('.ff_fileupload_fileinfo').text(data.ff_info.displayfilesize + ' | ' + CoolFileTransfer.Translate('Initializing...')); 53 | 54 | var failed = function(result) { 55 | if (typeof(result.error) !== 'string') result.error = CoolFileTransfer.Translate('The server indicated that the upload was not able to be started. No additional information is available.'); 56 | if (typeof(result.errorcode) !== 'string') result.errorcode = 'server_response'; 57 | 58 | data.ff_info.errors.push(CoolFileTransfer.FormatStr(CoolFileTransfer.Translate('The upload failed. {0} ({1})'), CoolFileTransfer.EscapeHTML(result.error), CoolFileTransfer.EscapeHTML(result.errorcode))); 59 | 60 | this.find('.ff_fileupload_errors').html(data.ff_info.errors.join('
')).removeClass('ff_fileupload_hidden'); 61 | 62 | this.removeClass('ff_fileupload_starting'); 63 | 64 | // Hide the progress bar. 65 | this.find('.ff_fileupload_progress_background').addClass('ff_fileupload_hidden'); 66 | 67 | // Alter remove buttons. 68 | this.find('button.ff_fileupload_remove_file').attr('aria-label', CoolFileTransfer.Translate('Remove from list')); 69 | }; 70 | 71 | data.cft_info = { 72 | 'retries' : 5 73 | }; 74 | 75 | // Make an initial request to start a file transfer. 76 | var options = { 77 | 'url' : CoolFileTransfer.targeturl, 78 | 'data' : jQuery.extend({}, CoolFileTransfer.initrequest, { 79 | 'filename' : data.files[0].uploadName || data.files[0].name, 80 | 'filesize' : data.files[0].size 81 | }), 82 | 'dataType' : 'json', 83 | 'success' : function(result) { 84 | if (data.cft_info) 85 | { 86 | if (data.cft_info.ajax) delete data.cft_info.ajax; 87 | 88 | if (!result.success) failed.call($this, result); 89 | else 90 | { 91 | // Request approved. Set up the required parameters. 92 | data.url = result.sendurl; 93 | 94 | data.formData = { 95 | 'id' : result.id, 96 | 'token' : result.token 97 | }; 98 | 99 | // For later use if either side decides to cancel the upload. 100 | data.cft_info = { 101 | 'id' : result.id, 102 | 'token' : result.token, 103 | 'cancelurl' : result.cancelurl 104 | }; 105 | 106 | $this.find('.ff_fileupload_fileinfo').text(data.ff_info.displayfilesize + ' | ' + CoolFileTransfer.Translate('Waiting for recipient...')); 107 | 108 | // Wait for the request to be accepted by the destination user. 109 | var options2 = { 110 | 'url' : data.url, 111 | 'data' : data.formData, 112 | 'dataType' : 'json', 113 | 'success' : function(result2) { 114 | if (data.cft_info) 115 | { 116 | // User has accepted the file upload. 117 | if (data.cft_info.ajax) delete data.cft_info.ajax; 118 | 119 | if (!result2.success) failed.call($this, result2); 120 | else 121 | { 122 | // Submit the upload. 123 | SubmitUpload(); 124 | } 125 | 126 | delete data.cft_info; 127 | } 128 | }, 129 | 'error' : function() { 130 | if (data.cft_info && data.cft_info.retries > 1) 131 | { 132 | data.cft_info.retries--; 133 | 134 | setTimeout(function() { if (data.cft_info) data.cft_info.ajax = jQuery.ajax(options2); }, 1000); 135 | } 136 | else if (data.ff_info) 137 | { 138 | $this.find('.ff_fileupload_fileinfo').text(data.ff_info.displayfilesize + ' | ' + CoolFileTransfer.Translate('Failed')); 139 | 140 | var result2 = { 141 | 'success' : false, 142 | 'error' : CoolFileTransfer.Translate('A permanent network or data error occurred.') 143 | }; 144 | 145 | failed.call($this, result2); 146 | } 147 | } 148 | }; 149 | 150 | data.cft_info.ajax = jQuery.ajax(options2); 151 | } 152 | } 153 | }, 154 | 'error' : function() { 155 | if (data.cft_info && data.cft_info.retries > 1) 156 | { 157 | data.cft_info.retries--; 158 | 159 | setTimeout(function() { if (data.cft_info) data.cft_info.ajax = jQuery.ajax(options); }, 1000); 160 | } 161 | else if (data.ff_info) 162 | { 163 | $this.find('.ff_fileupload_fileinfo').text(data.ff_info.displayfilesize + ' | ' + CoolFileTransfer.Translate('Failed')); 164 | 165 | var result = { 166 | 'success' : false, 167 | 'error' : CoolFileTransfer.Translate('A permanent network or data error occurred.') 168 | }; 169 | 170 | failed.call($this, result); 171 | } 172 | } 173 | }; 174 | 175 | data.cft_info.ajax = jQuery.ajax(options); 176 | }, 177 | 178 | UploadCancelled : function(e, data) { 179 | if (data.cft_info) 180 | { 181 | data.cft_info.retries = 0; 182 | if (data.cft_info.ajax) data.cft_info.ajax.abort(); 183 | 184 | // Trigger a send to the server to remove the record. 185 | // There's no particular reason to care about the server's response. 186 | if (data.cft_info.cancelurl) jQuery.ajax({ 'url' : data.cft_info.cancelurl }); 187 | 188 | delete data.cft_info; 189 | } 190 | } 191 | }; 192 | -------------------------------------------------------------------------------- /recv/index.php: -------------------------------------------------------------------------------- 1 | recvtoken, $_REQUEST["token"])) CFT_DisplayError("File request token is invalid.", "invalid_token"); 9 | 10 | if (isset($_REQUEST["action"]) && $_REQUEST["action"] === "remove") 11 | { 12 | // Removing is a matter of deleting the database row. 13 | try 14 | { 15 | $db->Query("DELETE", array($cft_db_files, "WHERE" => "id = ?"), $row->id); 16 | } 17 | catch (Exception $e) 18 | { 19 | CFT_DisplayError("Unable to remove the file. A database error occurred.", "db_error"); 20 | } 21 | 22 | // Trigger the notification to the client that the request has been rejected. This will generally happen immediately and never timeout. 23 | if ($row->port < 0) 24 | { 25 | $fp = @stream_socket_client("tcp://127.0.0.1:" . -$row->port, $errornum, $errorstr, 5, STREAM_CLIENT_CONNECT); 26 | if ($fp !== false) @fclose($fp); 27 | } 28 | 29 | $result = array( 30 | "success" => true 31 | ); 32 | 33 | CFT_SendResult($result); 34 | } 35 | else 36 | { 37 | // Check the port number. 38 | if ($row->port > 0) CFT_DisplayError("File is already being retrieved.", "in_progress"); 39 | 40 | // Verify that the request is active. 41 | if ($row->lastrequest < time() - 120) CFT_DisplayError("Sender is no longer connected.", "sender_missing"); 42 | 43 | // Start a TCP/IP server on a random port number. 44 | $serverfp = @stream_socket_server("tcp://127.0.0.1:0", $errornum, $errorstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN); 45 | if ($serverfp === false) CFT_DisplayError("Failed to start local transport interface.", "local_transport_failed"); 46 | 47 | $serverinfo = stream_socket_get_name($serverfp, false); 48 | $pos = strrpos($serverinfo, ":"); 49 | $serverip = substr($serverinfo, 0, $pos); 50 | $serverport = (int)substr($serverinfo, $pos + 1); 51 | 52 | // Update the database with the new server port. 53 | try 54 | { 55 | $db->Query("UPDATE", array($cft_db_files, array( 56 | "port" => $serverport, 57 | ), "WHERE" => "id = ? AND port < 1"), $row->id); 58 | } 59 | catch (Exception $e) 60 | { 61 | CFT_DisplayError("Unable to update the file. A database error occurred.", "db_error"); 62 | } 63 | 64 | // Trigger the notification to the client that the request has been accepted. This will generally happen immediately and never timeout. 65 | if ($row->port < 0) 66 | { 67 | $fp = @stream_socket_client("tcp://127.0.0.1:" . -$row->port, $errornum, $errorstr, 5, STREAM_CLIENT_CONNECT); 68 | if ($fp !== false) @fclose($fp); 69 | } 70 | 71 | // Initialize the file download. Disable automatic gzip compression so that large files can be sent. 72 | if (function_exists("apache_setenv")) @apache_setenv("no-gzip", 1); 73 | @ini_set("zlib.output_compression", 0); 74 | header("Content-Disposition: attachment; filename=\"" . $row->filename . "\""); 75 | header("Content-Type: application/octet-stream"); 76 | header("Content-Encoding: none"); 77 | header("Content-Length: " . $row->filesize); 78 | @ob_flush(); 79 | @flush(); 80 | 81 | $lastdbcheck = time(); 82 | $sent = 0; 83 | while ($sent < $row->filesize) 84 | { 85 | // Wait for a connection. 86 | // I'm aware that this is NOT the correct way to use stream_select(). 87 | // It's for non-blocking use only but stream_socket_accept() doesn't block until there is a connection AND I want the TCP/IP handshake to complete *before* fgets() executes. 88 | $readfps = array($serverfp); 89 | $writefps = array(); 90 | $exceptfps = NULL; 91 | $timeout = $lastdbcheck + 20 - time(); 92 | if ($timeout < 1) $timeout = 0; 93 | $timeout++; 94 | $result = @stream_select($readfps, $writefps, $exceptfps, $timeout); 95 | if ($result === false) CFT_DisplayError("Failed to wait for local transport interface.", "local_transport_failed"); 96 | 97 | if (count($readfps) && ($fp = @stream_socket_accept($serverfp)) !== false) 98 | { 99 | @stream_set_timeout($fp, 5); 100 | $data = @fgets($fp); 101 | 102 | $data = @json_decode($data, true); 103 | 104 | // There might be a security vulnerability here if the process can be coerced to transfer, for example, /etc/passwd. 105 | // However, assuming this is an actual issue, then aren't there several far easier ways to obtain said files? 106 | if (is_array($data) && isset($data["token"]) && is_string($data["token"]) && isset($data["filename"]) && is_string($data["filename"]) && CFT_CTstrcmp($row->recvtoken, $data["token"]) == 0 && file_exists($data["filename"]) && ($fp2 = @fopen($data["filename"], "rb")) !== false) 107 | { 108 | if (isset($data["startpos"]) && $data["startpos"] < 0) 109 | { 110 | $result = array( 111 | "success" => false, 112 | "error" => "Starting position is less than 0.", 113 | "errorcode" => "invalid_startpos" 114 | ); 115 | } 116 | else if (isset($data["startpos"]) && $data["startpos"] > $sent) 117 | { 118 | $result = array( 119 | "success" => false, 120 | "error" => "Starting position is greater than amount transferred.", 121 | "errorcode" => "invalid_startpos" 122 | ); 123 | } 124 | else 125 | { 126 | if (isset($data["startpos"]) && $data["startpos"] < $sent) 127 | { 128 | // Seek to the correct starting point in the file. 129 | $size = @filesize($data["filename"]); 130 | if ($data["startpos"] + $size < $sent) fseek($fp2, 0, SEEK_END); 131 | else fseek($fp2, $sent - $data["startpos"], SEEK_SET); 132 | } 133 | 134 | do 135 | { 136 | $data2 = @fread($fp2, ($row->filesize - $sent > 65536 ? 65536 : $row->filesize - $sent)); 137 | if ($data2 === false) $data2 = ""; 138 | 139 | echo $data2; 140 | @ob_flush(); 141 | @flush(); 142 | 143 | $sent += strlen($data2); 144 | } while ($data2 !== ""); 145 | 146 | $result = array( 147 | "success" => true 148 | ); 149 | } 150 | 151 | fclose($fp2); 152 | } 153 | else 154 | { 155 | // Wrong/bad connection. Maybe the client is trying to connect to a reused socket? 156 | $result = array( 157 | "success" => false, 158 | "error" => "Request is for another file transfer in progress. Was the previous transfer cancelled?", 159 | "errorcode" => "invalid_input" 160 | ); 161 | } 162 | 163 | @fwrite($fp, json_encode($result, JSON_UNESCAPED_SLASHES) . "\n"); 164 | 165 | fclose($fp); 166 | 167 | $lastdbcheck = time(); 168 | } 169 | 170 | // Check the database every 20 seconds of non-contact for download cancellation. 171 | if ($lastdbcheck < time() - 20) 172 | { 173 | try 174 | { 175 | $row = $db->GetRow("SELECT", array( 176 | "*", 177 | "FROM" => "?", 178 | "WHERE" => "id = ?", 179 | ), $cft_db_files, $row->id); 180 | } 181 | catch (Exception $e) 182 | { 183 | CFT_DisplayError("Unable to retrieve file. A database error occurred.", "db_error"); 184 | } 185 | 186 | if (!$row) exit(); 187 | 188 | if ($row->lastrequest < time() - 120) break; 189 | 190 | $lastdbcheck = time(); 191 | } 192 | } 193 | 194 | // Remove the file from the transfer list. 195 | try 196 | { 197 | $db->Query("DELETE", array($cft_db_files, "WHERE" => "id = ?"), $row->id); 198 | } 199 | catch (Exception $e) 200 | { 201 | } 202 | } 203 | ?> -------------------------------------------------------------------------------- /support/csdb/db_oci_lite.php: -------------------------------------------------------------------------------- 1 | lastid = 0; 43 | 44 | parent::Connect($dsn, $username, $password, $options); 45 | 46 | // Convert DB NULL values into empty strings for use in code. 47 | $this->dbobj->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_TO_STRING); 48 | 49 | // Converts all uppercase table names into lowercase table names. 50 | $this->dbobj->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); 51 | 52 | // Set Oracle session variables to use standard date formats. 53 | $this->Query("SET", "NLS_DATE_FORMAT='YYYY-MM-DD'"); 54 | $this->Query("SET", "NLS_TIMESTAMP_FORMAT='YYYY-MM-DD HH24:MI:SS'"); 55 | 56 | // Set Unicode support. 57 | // TODO: Figure out unicode support for Oracle 58 | //$this->Query("SET", "NLS_LANGUAGE='UTF8'"); 59 | } 60 | 61 | public function GetVersion() 62 | { 63 | $tableExists = $this->TableExists("SSO_USER"); 64 | 65 | return $this->GetOne("SELECT",array( 66 | "banner", 67 | "FROM" => "v\$version", 68 | "WHERE" => "banner LIKE 'Oracle%'" 69 | )); 70 | } 71 | 72 | public function GetInsertID($name = null) 73 | { 74 | return $this->lastid; 75 | } 76 | 77 | public function TableExists($name) 78 | { 79 | return ($this->GetOne("SHOW TABLES", array("LIKE" => $name)) === false ? false : true); 80 | } 81 | 82 | public function QuoteIdentifier($str) 83 | { 84 | return preg_replace('/[^A-Za-z0-9_]/', "_", $str); 85 | } 86 | 87 | // This function is used to get the last inserted sequence value by table name. 88 | // 89 | // Uses automatically geneerated sequences as part of the Oracle 12c IDENTITY 90 | // column. This is not available in 11g and older Oracle databases. 91 | // See the ProcessColumnDefinition() function for more detail. 92 | private function GetOracleInsertID($tableName) 93 | { 94 | // Query the "all_tab_columns" for the oracle IDENTITY column and identify the sequence 95 | $seqName = $this->GetOne("SELECT", array( 96 | "data_default", 97 | "FROM" => "all_tab_columns", 98 | "WHERE" => "identity_column = 'YES' AND table_name = ?" 99 | ), strtoupper($tableName)); 100 | 101 | // The previous query returned "nextval" with the sequence name, 102 | // however we need the current sequence value 103 | $seqName = str_replace(".nextval", ".CURRVAL", $seqName); 104 | 105 | // This grabs the current value from the sequence identified above 106 | $retVal = $this->GetOne("SELECT", array( 107 | $seqName, 108 | "FROM" => "DUAL" 109 | )); 110 | 111 | // Return the current sequence value 112 | return $retVal; 113 | } 114 | 115 | protected function GenerateSQL(&$master, &$sql, &$opts, $cmd, $queryinfo, $args, $subquery) 116 | { 117 | $mystr = "test"; 118 | switch ($cmd) 119 | { 120 | case "SELECT": 121 | { 122 | $supported = array( 123 | "PRECOLUMN" => array("DISTINCT" => "bool", "SUBQUERIES" => true), 124 | "FROM" => array("SUBQUERIES" => true), 125 | "WHERE" => array("SUBQUERIES" => true), 126 | "GROUP BY" => true, 127 | "HAVING" => true, 128 | "ORDER BY" => true, 129 | // Haven't figured out the LIMIT problem yet 130 | // TODO: Figure out how to use Oracle's ROWNUM where clause functionalitty 131 | // instead of the LIMIT function 132 | //"LIMIT" => " OFFSET " 133 | ); 134 | 135 | // Oracle does not support aliasing table names in the FROM clause. 136 | // However, alias' are supported in COLUMN names. 137 | // AS is used in the Oracle FROM clause to process nested queries, 138 | // but does not support alias'. 139 | $queryinfo["FROM"] = str_replace("? AS ", "? ", $queryinfo["FROM"]); 140 | 141 | return $this->ProcessSELECT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 142 | } 143 | case "INSERT": 144 | { 145 | $supported = array( 146 | "PREINTO" => array(), 147 | "POSTVALUES" => array("RETURNING" => "key_identifier"), 148 | "SELECT" => true, 149 | "BULKINSERT" => false 150 | ); 151 | 152 | $result = $this->ProcessINSERT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 153 | if ($result["success"] && isset($queryinfo["AUTO INCREMENT"])) $result["filter_opts"] = array("mode" => "INSERT", "queryinfo" => $queryinfo); 154 | 155 | // Handle bulk insert by rewriting the queries because, well, Oracle. 156 | // http://stackoverflow.com/questions/39576/best-way-to-do-multi-row-insert-in-oracle 157 | if ($result["success"] && is_array($sql)) 158 | { 159 | $sql2 = "INSERT ALL"; 160 | foreach ($sql as $entry) $sql2 .= substr($entry, 6); 161 | $sql2 .= " SELECT 1 FROM DUAL"; 162 | $sql = $sql2; 163 | } 164 | 165 | return $result; 166 | } 167 | case "UPDATE": 168 | { 169 | // No ORDER BY or LIMIT support. 170 | $supported = array( 171 | "PRETABLE" => array("ONLY" => "bool"), 172 | "WHERE" => array("SUBQUERIES" => true) 173 | ); 174 | 175 | return $this->ProcessUPDATE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 176 | } 177 | case "DELETE": 178 | { 179 | // No ORDER BY or LIMIT support. 180 | $supported = array( 181 | "PREFROM" => array("ONLY" => "bool"), 182 | "WHERE" => array("SUBQUERIES" => true) 183 | ); 184 | 185 | return $this->ProcessDELETE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 186 | } 187 | case "SET": 188 | { 189 | $sql = "ALTER SESSION SET " . $queryinfo; 190 | 191 | return array("success" => true); 192 | } 193 | 194 | case "USE": 195 | { 196 | // Fake multiple databases with Oracle schemas. 197 | // SCHEMA is already selected with user 198 | // $sql = "SELECT 1 FROM DUAL"; 199 | 200 | return array("success" => false, "errorcode" => "skip_sql_query"); 201 | } 202 | case "TRUNCATE TABLE": 203 | { 204 | $master = true; 205 | 206 | $sql = "TRUNCATE TABLE " . $this->QuoteIdentifier($queryinfo[0]); 207 | 208 | return array("success" => true); 209 | } 210 | } 211 | 212 | return array("success" => false, "error" => CSDB::DB_Translate("Unknown query command '%s'.", $cmd), "errorcode" => "unknown_query_command"); 213 | } 214 | 215 | protected function RunStatementFilter(&$stmt, &$filteropts) 216 | { 217 | if ($filteropts["mode"] == "INSERT") 218 | { 219 | // Force the last ID value to be extracted for INSERT queries. 220 | // Unable to find a way to get Oracle to return a row without 221 | // Using PL/SQL functions. 222 | $result = new CSDB_PDO_Statement($this, $stmt, $filteropts); 223 | $row = $result->NextRow(); 224 | 225 | $stmt = false; 226 | } 227 | 228 | parent::RunStatementFilter($stmt, $filteropts); 229 | } 230 | 231 | public function RunRowFilter(&$row, &$filteropts, &$fetchnext) 232 | { 233 | switch ($filteropts["mode"]) 234 | { 235 | case "INSERT": 236 | { 237 | // Use the private function provided above to get the Last Inserted ID 238 | $this->lastid = $this->GetOracleInsertID($filteropts["queryinfo"][0]); 239 | 240 | break; 241 | } 242 | } 243 | 244 | if (!$fetchnext) parent::RunRowFilter($row, $filteropts, $fetchnext); 245 | } 246 | } 247 | ?> -------------------------------------------------------------------------------- /cft.php: -------------------------------------------------------------------------------- 1 | config = $config; 18 | $this->db = false; 19 | 20 | $dbprefix = $this->config["db_table_prefix"]; 21 | $this->cft_db_files = $dbprefix . "files"; 22 | } 23 | 24 | public function StartSendFile($srcuser, $destuser, $filename, $filesize) 25 | { 26 | $result = $this->InitDB(); 27 | if (!$result["success"]) return $result; 28 | 29 | // Generate valid tokens. 30 | if (!class_exists("CSPRNG", false)) require_once $this->config["rootpath"] . "/support/random.php"; 31 | 32 | $rng = new CSPRNG(); 33 | 34 | $data = array( 35 | "srcuser" => (string)$srcuser, 36 | "destuser" => (string)$destuser, 37 | "sendtoken" => $rng->GenerateString(64), 38 | "recvtoken" => $rng->GenerateString(64), 39 | "filename" => trim(str_replace("\"", "", preg_replace('/\s+/', " ", $filename))), 40 | "filesize" => preg_replace('/[^0-9]/', "", $filesize) 41 | ); 42 | 43 | try 44 | { 45 | // Remove old requests. 46 | $this->db->Query("DELETE", array($this->cft_db_files, "WHERE" => "lastrequest < ?"), time() - 3600); 47 | 48 | // Insert the new file request into the database. 49 | $this->db->Query("INSERT", array($this->cft_db_files, array( 50 | "lastrequest" => time(), 51 | "srcuser" => $data["srcuser"], 52 | "destuser" => $data["destuser"], 53 | "sendtoken" => $data["sendtoken"], 54 | "recvtoken" => $data["recvtoken"], 55 | "filename" => $data["filename"], 56 | "filesize" => $data["filesize"], 57 | "port" => 0 58 | ), "AUTO INCREMENT" => "id")); 59 | 60 | $id = $this->db->GetInsertID(); 61 | } 62 | catch (Exception $e) 63 | { 64 | return array("success" => false, "error" => self::CFT_Translate("Unable to start a new file. A database error occurred."), "errorcode" => "db_error", "info" => $e->getMessage()); 65 | } 66 | 67 | return array("success" => true, "sendurl" => $this->config["rooturl"] . "send/", "id" => $id, "token" => $data["sendtoken"], "cancelurl" => $this->config["rooturl"] . "send/?id=" . $id . "&token=" . $data["sendtoken"] . "&action=cancel"); 68 | } 69 | 70 | public function GetDefaultCSS() 71 | { 72 | return $this->config["rooturl"] . "support/cft.css"; 73 | } 74 | 75 | public function GetRecvList($destuser) 76 | { 77 | $result = $this->InitDB(); 78 | if (!$result["success"]) return $result; 79 | 80 | $users = array(); 81 | 82 | try 83 | { 84 | $result = $this->db->Query("SELECT", array( 85 | "*", 86 | "FROM" => "?", 87 | "WHERE" => "destuser = ? AND lastrequest >= ? AND port < 0", 88 | "ORDER BY" => "srcuser, filename, filesize DESC", 89 | ), $this->cft_db_files, $destuser, time() - 120); 90 | } 91 | catch (Exception $e) 92 | { 93 | return array("success" => false, "error" => self::CFT_Translate("Unable to retrieve file transfer request list. A database error occurred."), "errorcode" => "db_error", "info" => $e->getMessage()); 94 | } 95 | 96 | // Retrieve content so it is broken down by source user. 97 | $lastuser = false; 98 | while ($row = $result->NextRow()) 99 | { 100 | if ($lastuser !== $row->srcuser) 101 | { 102 | if ($lastuser !== false) $users[$lastuser] = $files; 103 | 104 | $files = array(); 105 | $lastuser = $row->srcuser; 106 | } 107 | 108 | $url = $this->config["rooturl"] . "recv/?id=" . $row->id . "&token=" . $row->recvtoken; 109 | 110 | $files[] = array( 111 | "filename" => $row->filename, 112 | "filesize" => $row->filesize, 113 | "download" => $url, 114 | "remove" => $url . "&action=remove" 115 | ); 116 | } 117 | 118 | if ($lastuser !== false) $users[$lastuser] = $files; 119 | 120 | return array("success" => true, "users" => $users); 121 | } 122 | 123 | public function OutputHTMLNotifications($users) 124 | { 125 | ?> 126 | 139 | 140 | 141 |
142 |
143 | $files) 146 | { 147 | ?> 148 | 168 | 171 |
172 |
173 | config["rooturl"] . "support/cft.js"; 179 | } 180 | 181 | private function InitDB() 182 | { 183 | if ($this->db !== false) return array("success" => true); 184 | 185 | // Connect to the database. 186 | $dbclassname = "CSDB_" . $this->config["db_select"]; 187 | 188 | if (!class_exists($dbclassname, false)) require_once $this->config["rootpath"] . "/support/csdb/db_" . $this->config["db_select"] . ".php"; 189 | 190 | try 191 | { 192 | $db = new $dbclassname($this->config["db_select"] . ":" . $this->config["db_dsn"], ($this->config["db_login"] ? $this->config["db_user"] : false), ($this->config["db_login"] ? $this->config["db_pass"] : false)); 193 | if ($this->config["db_master_dsn"] != "") $db->SetMaster($this->config["db_select"] . ":" . $this->config["db_master_dsn"], ($this->config["db_login"] ? $this->config["db_master_user"] : false), ($this->config["db_login"] ? $this->config["db_master_pass"] : false)); 194 | 195 | $db->Query("USE", $this->config["db_name"]); 196 | } 197 | catch (Exception $e) 198 | { 199 | return array("success" => false, "error" => self::CFT_Translate("Database connection failed."), "errorcode" => "db_connect_failed", "info" => $e->getMessage()); 200 | } 201 | 202 | $this->db = $db; 203 | 204 | return array("success" => true); 205 | } 206 | 207 | // Copy included for Cool File Transfer self-containment. 208 | public static function ConvertBytesToUserStr($num) 209 | { 210 | $num = (double)$num; 211 | 212 | if ($num < 0) return "0 B"; 213 | if ($num < 1024) return number_format($num, 0) . " B"; 214 | if ($num < 1048576) return str_replace(".0 ", "", number_format($num / 1024, 1)) . " KB"; 215 | if ($num < 1073741824) return str_replace(".0 ", "", number_format($num / 1048576, 1)) . " MB"; 216 | if ($num < 1099511627776.0) return str_replace(".0 ", "", number_format($num / 1073741824.0, 1)) . " GB"; 217 | if ($num < 1125899906842624.0) return str_replace(".0 ", "", number_format($num / 1099511627776.0, 1)) . " TB"; 218 | 219 | return str_replace(".0 ", "", number_format($num / 1125899906842624.0, 1)) . " PB"; 220 | } 221 | 222 | protected static function CFT_Translate() 223 | { 224 | $args = func_get_args(); 225 | if (!count($args)) return ""; 226 | 227 | return call_user_func_array((defined("CS_TRANSLATE_FUNC") && function_exists(CS_TRANSLATE_FUNC) ? CS_TRANSLATE_FUNC : "sprintf"), $args); 228 | } 229 | } 230 | } 231 | ?> -------------------------------------------------------------------------------- /support/fancy-file-uploader/jquery.iframe-transport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Iframe Transport Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2011, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * https://opensource.org/licenses/MIT 10 | */ 11 | 12 | /* global define, require */ 13 | 14 | (function (factory) { 15 | 'use strict'; 16 | if (typeof define === 'function' && define.amd) { 17 | // Register as an anonymous AMD module: 18 | define(['jquery'], factory); 19 | } else if (typeof exports === 'object') { 20 | // Node/CommonJS: 21 | factory(require('jquery')); 22 | } else { 23 | // Browser globals: 24 | factory(window.jQuery); 25 | } 26 | })(function ($) { 27 | 'use strict'; 28 | 29 | // Helper variable to create unique names for the transport iframes: 30 | var counter = 0, 31 | jsonAPI = $, 32 | jsonParse = 'parseJSON'; 33 | 34 | if ('JSON' in window && 'parse' in JSON) { 35 | jsonAPI = JSON; 36 | jsonParse = 'parse'; 37 | } 38 | 39 | // The iframe transport accepts four additional options: 40 | // options.fileInput: a jQuery collection of file input fields 41 | // options.paramName: the parameter name for the file form data, 42 | // overrides the name property of the file input field(s), 43 | // can be a string or an array of strings. 44 | // options.formData: an array of objects with name and value properties, 45 | // equivalent to the return data of .serializeArray(), e.g.: 46 | // [{name: 'a', value: 1}, {name: 'b', value: 2}] 47 | // options.initialIframeSrc: the URL of the initial iframe src, 48 | // by default set to "javascript:false;" 49 | $.ajaxTransport('iframe', function (options) { 50 | if (options.async) { 51 | // javascript:false as initial iframe src 52 | // prevents warning popups on HTTPS in IE6: 53 | // eslint-disable-next-line no-script-url 54 | var initialIframeSrc = options.initialIframeSrc || 'javascript:false;', 55 | form, 56 | iframe, 57 | addParamChar; 58 | return { 59 | send: function (_, completeCallback) { 60 | form = $('
'); 61 | form.attr('accept-charset', options.formAcceptCharset); 62 | addParamChar = /\?/.test(options.url) ? '&' : '?'; 63 | // XDomainRequest only supports GET and POST: 64 | if (options.type === 'DELETE') { 65 | options.url = options.url + addParamChar + '_method=DELETE'; 66 | options.type = 'POST'; 67 | } else if (options.type === 'PUT') { 68 | options.url = options.url + addParamChar + '_method=PUT'; 69 | options.type = 'POST'; 70 | } else if (options.type === 'PATCH') { 71 | options.url = options.url + addParamChar + '_method=PATCH'; 72 | options.type = 'POST'; 73 | } 74 | // IE versions below IE8 cannot set the name property of 75 | // elements that have already been added to the DOM, 76 | // so we set the name along with the iframe HTML markup: 77 | counter += 1; 78 | iframe = $( 79 | '' 84 | ).on('load', function () { 85 | var fileInputClones, 86 | paramNames = $.isArray(options.paramName) 87 | ? options.paramName 88 | : [options.paramName]; 89 | iframe.off('load').on('load', function () { 90 | var response; 91 | // Wrap in a try/catch block to catch exceptions thrown 92 | // when trying to access cross-domain iframe contents: 93 | try { 94 | response = iframe.contents(); 95 | // Google Chrome and Firefox do not throw an 96 | // exception when calling iframe.contents() on 97 | // cross-domain requests, so we unify the response: 98 | if (!response.length || !response[0].firstChild) { 99 | throw new Error(); 100 | } 101 | } catch (e) { 102 | response = undefined; 103 | } 104 | // The complete callback returns the 105 | // iframe content document as response object: 106 | completeCallback(200, 'success', { iframe: response }); 107 | // Fix for IE endless progress bar activity bug 108 | // (happens on form submits to iframe targets): 109 | $('').appendTo( 110 | form 111 | ); 112 | window.setTimeout(function () { 113 | // Removing the form in a setTimeout call 114 | // allows Chrome's developer tools to display 115 | // the response result 116 | form.remove(); 117 | }, 0); 118 | }); 119 | form 120 | .prop('target', iframe.prop('name')) 121 | .prop('action', options.url) 122 | .prop('method', options.type); 123 | if (options.formData) { 124 | $.each(options.formData, function (index, field) { 125 | $('') 126 | .prop('name', field.name) 127 | .val(field.value) 128 | .appendTo(form); 129 | }); 130 | } 131 | if ( 132 | options.fileInput && 133 | options.fileInput.length && 134 | options.type === 'POST' 135 | ) { 136 | fileInputClones = options.fileInput.clone(); 137 | // Insert a clone for each file input field: 138 | options.fileInput.after(function (index) { 139 | return fileInputClones[index]; 140 | }); 141 | if (options.paramName) { 142 | options.fileInput.each(function (index) { 143 | $(this).prop('name', paramNames[index] || options.paramName); 144 | }); 145 | } 146 | // Appending the file input fields to the hidden form 147 | // removes them from their original location: 148 | form 149 | .append(options.fileInput) 150 | .prop('enctype', 'multipart/form-data') 151 | // enctype must be set as encoding for IE: 152 | .prop('encoding', 'multipart/form-data'); 153 | // Remove the HTML5 form attribute from the input(s): 154 | options.fileInput.removeAttr('form'); 155 | } 156 | form.submit(); 157 | // Insert the file input fields at their original location 158 | // by replacing the clones with the originals: 159 | if (fileInputClones && fileInputClones.length) { 160 | options.fileInput.each(function (index, input) { 161 | var clone = $(fileInputClones[index]); 162 | // Restore the original name and form properties: 163 | $(input) 164 | .prop('name', clone.prop('name')) 165 | .attr('form', clone.attr('form')); 166 | clone.replaceWith(input); 167 | }); 168 | } 169 | }); 170 | form.append(iframe).appendTo(document.body); 171 | }, 172 | abort: function () { 173 | if (iframe) { 174 | // javascript:false as iframe src aborts the request 175 | // and prevents warning popups on HTTPS in IE6. 176 | iframe.off('load').prop('src', initialIframeSrc); 177 | } 178 | if (form) { 179 | form.remove(); 180 | } 181 | } 182 | }; 183 | } 184 | }); 185 | 186 | // The iframe transport returns the iframe content document as response. 187 | // The following adds converters from iframe to text, json, html, xml 188 | // and script. 189 | // Please note that the Content-Type for JSON responses has to be text/plain 190 | // or text/html, if the browser doesn't include application/json in the 191 | // Accept header, else IE will show a download dialog. 192 | // The Content-Type for XML responses on the other hand has to be always 193 | // application/xml or text/xml, so IE properly parses the XML response. 194 | // See also 195 | // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation 196 | $.ajaxSetup({ 197 | converters: { 198 | 'iframe text': function (iframe) { 199 | return iframe && $(iframe[0].body).text(); 200 | }, 201 | 'iframe json': function (iframe) { 202 | return iframe && jsonAPI[jsonParse]($(iframe[0].body).text()); 203 | }, 204 | 'iframe html': function (iframe) { 205 | return iframe && $(iframe[0].body).html(); 206 | }, 207 | 'iframe xml': function (iframe) { 208 | var xmlDoc = iframe && iframe[0]; 209 | return xmlDoc && $.isXMLDoc(xmlDoc) 210 | ? xmlDoc 211 | : $.parseXML( 212 | (xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) || 213 | $(xmlDoc.body).html() 214 | ); 215 | }, 216 | 'iframe script': function (iframe) { 217 | return iframe && $.globalEval($(iframe[0].body).text()); 218 | } 219 | } 220 | }); 221 | }); 222 | -------------------------------------------------------------------------------- /support/flex_forms_fileuploader.php: -------------------------------------------------------------------------------- 1 | "link", "dependency" => false, "src" => $state["supporturl"] . "/fancy-file-uploader/fancy_fileupload.css"); 21 | $state["js"]["modules-fileuploader-base"] = array("mode" => "src", "dependency" => "jqueryui", "src" => $state["supporturl"] . "/fancy-file-uploader/jquery.fileupload.js", "detect" => "jQuery.fn.fileupload"); 22 | $state["js"]["modules-fileuploader-iframe"] = array("mode" => "src", "dependency" => "modules-fileuploader-base", "src" => $state["supporturl"] . "/fancy-file-uploader/jquery.iframe-transport.js", "detect" => "jQuery.fn.FancyFileUpload"); 23 | $state["js"]["modules-fileuploader-fancy"] = array("mode" => "src", "dependency" => "modules-fileuploader-iframe", "src" => $state["supporturl"] . "/fancy-file-uploader/jquery.fancy-fileupload.js", "detect" => "jQuery.fn.FancyFileUpload"); 24 | 25 | $state["modules_file_uploader"] = true; 26 | } 27 | 28 | $options = array( 29 | "params" => $state["hidden"], 30 | "fileupload" => array("__flexforms" => true) 31 | ); 32 | 33 | $options["params"]["fileuploader"] = "1"; 34 | 35 | if (isset($field["maxchunk"])) $options["fileupload"]["maxChunkSize"] = floor($field["maxchunk"]); 36 | else if (!isset($field["maxsize"])) $options["maxfilesize"] = self::GetMaxUploadFileSize(); 37 | else $options["maxfilesize"] = floor($field["maxsize"]); 38 | 39 | if (isset($options["maxfilesize"]) && $options["maxfilesize"] >= 1) $options["fileupload"]["limitMultiFileUploadSize"] = $options["maxfilesize"]; 40 | 41 | // Allow the file uploader to be fully customized beyond basic support. 42 | // Uses dot notation for array key references: See 'jquery.fancy-fileupload.js' and https://github.com/blueimp/jQuery-File-Upload/wiki/Options 43 | if (isset($field["uploader_options"])) 44 | { 45 | foreach ($field["uploader_options"] as $key => $val) 46 | { 47 | $parts = explode(".", $key); 48 | 49 | FlexForms::SetNestedPathValue($options, $parts, $val); 50 | } 51 | } 52 | 53 | // Queue up the necessary Javascript for later output. 54 | ob_start(); 55 | ?> 56 | jQuery(function() { 57 | var options = ; 58 | $val) 62 | { 63 | $parts = explode(".", $key); 64 | 65 | ?> 66 | options = ; 67 | 71 | 72 | if (jQuery.fn.FancyFileUpload) 73 | { 74 | jQuery('#').FancyFileUpload(options); 75 | } 76 | else 77 | { 78 | alert(''); 79 | } 80 | }); 81 | "inline", "dependency" => "modules-fileuploader-fancy", "src" => ob_get_contents()); 83 | ob_end_clean(); 84 | } 85 | } 86 | 87 | public static function GetMaxUploadFileSize() 88 | { 89 | $maxpostsize = floor(self::ConvertUserStrToBytes(ini_get("post_max_size")) * 3 / 4); 90 | if ($maxpostsize > 4096) $maxpostsize -= 4096; 91 | 92 | $maxuploadsize = self::ConvertUserStrToBytes(ini_get("upload_max_filesize")); 93 | if ($maxuploadsize < 1) $maxuploadsize = ($maxpostsize < 1 ? -1 : $maxpostsize); 94 | 95 | return ($maxpostsize < 1 ? $maxuploadsize : min($maxpostsize, $maxuploadsize)); 96 | } 97 | 98 | // Copy included for FlexForms self-containment. 99 | public static function ConvertUserStrToBytes($str) 100 | { 101 | $str = trim($str); 102 | $num = (double)$str; 103 | if (strtoupper(substr($str, -1)) == "B") $str = substr($str, 0, -1); 104 | switch (strtoupper(substr($str, -1))) 105 | { 106 | case "P": $num *= 1024; 107 | case "T": $num *= 1024; 108 | case "G": $num *= 1024; 109 | case "M": $num *= 1024; 110 | case "K": $num *= 1024; 111 | } 112 | 113 | return $num; 114 | } 115 | 116 | public static function GetChunkFilename() 117 | { 118 | if (isset($_SERVER["HTTP_CONTENT_DISPOSITION"])) 119 | { 120 | // Content-Disposition: attachment; filename="urlencodedstr" 121 | $str = $_SERVER["HTTP_CONTENT_DISPOSITION"]; 122 | if (strtolower(substr($str, 0, 11)) === "attachment;") 123 | { 124 | $pos = strpos($str, "\"", 11); 125 | $pos2 = strrpos($str, "\""); 126 | 127 | if ($pos !== false && $pos2 !== false && $pos < $pos2) 128 | { 129 | $str = FlexForms::FilenameSafe(rawurldecode(substr($str, $pos + 1, $pos2 - $pos - 1))); 130 | 131 | if ($str !== "") return $str; 132 | } 133 | } 134 | } 135 | 136 | return false; 137 | } 138 | 139 | public static function GetFileStartPosition() 140 | { 141 | if (isset($_SERVER["HTTP_CONTENT_RANGE"]) || isset($_SERVER["HTTP_RANGE"])) 142 | { 143 | // Content-Range: bytes (*|integer-integer)/(*|integer-integer) 144 | $vals = explode(" ", preg_replace('/\s+/', " ", str_replace(",", "", (isset($_SERVER["HTTP_CONTENT_RANGE"]) ? $_SERVER["HTTP_CONTENT_RANGE"] : $_SERVER["HTTP_RANGE"])))); 145 | if (count($vals) === 2 && strtolower($vals[0]) === "bytes") 146 | { 147 | $vals = explode("/", trim($vals[1])); 148 | if (count($vals) === 2) 149 | { 150 | $vals = explode("-", trim($vals[0])); 151 | 152 | if (count($vals) === 2) return (double)$vals[0]; 153 | } 154 | } 155 | } 156 | 157 | return 0; 158 | } 159 | 160 | public static function HandleUpload($filekey, $options = array()) 161 | { 162 | if (isset($_REQUEST["fileuploader"])) 163 | { 164 | header("Content-Type: application/json"); 165 | 166 | if (isset($options["allowed_exts"])) 167 | { 168 | $allowedexts = array(); 169 | 170 | if (is_string($options["allowed_exts"])) $options["allowed_exts"] = explode(",", $options["allowed_exts"]); 171 | 172 | foreach ($options["allowed_exts"] as $ext) 173 | { 174 | $ext = strtolower(trim(trim($ext), ".")); 175 | if ($ext !== "") $allowedexts[$ext] = true; 176 | } 177 | } 178 | 179 | $files = FlexForms::NormalizeFiles($filekey); 180 | if (!isset($files[0])) $result = array("success" => false, "error" => FlexForms::FFTranslate("File data was submitted but is missing."), "errorcode" => "bad_input"); 181 | else if (!$files[0]["success"]) $result = $files[0]; 182 | else if (isset($options["allowed_exts"]) && !isset($allowedexts[strtolower($files[0]["ext"])])) 183 | { 184 | $result = array( 185 | "success" => false, 186 | "error" => FlexForms::FFTranslate("Invalid file extension. Must be one of %s.", "'." . implode("', '.", array_keys($allowedexts)) . "'"), 187 | "errorcode" => "invalid_file_ext" 188 | ); 189 | } 190 | else 191 | { 192 | // For chunked file uploads, get the current filename and starting position from the incoming headers. 193 | $name = self::GetChunkFilename(); 194 | if ($name !== false) 195 | { 196 | $startpos = self::GetFileStartPosition(); 197 | 198 | $name = substr($name, 0, -(strlen($files[0]["ext"]) + 1)); 199 | 200 | if (isset($options["filename_callback"]) && is_callable($options["filename_callback"])) $filename = call_user_func_array($options["filename_callback"], array($name, strtolower($files[0]["ext"]), $files[0])); 201 | else if (isset($options["filename"])) $filename = str_replace(array("{name}", "{ext}"), array($name, strtolower($files[0]["ext"])), $options["filename"]); 202 | else $filename = false; 203 | 204 | if (!is_string($filename)) $result = array("success" => false, "error" => FlexForms::FFTranslate("The server did not set a valid filename."), "errorcode" => "invalid_filename"); 205 | else 206 | { 207 | if (file_exists($filename) && $startpos === filesize($filename)) $fp = @fopen($filename, "ab"); 208 | else 209 | { 210 | $fp = @fopen($filename, ($startpos > 0 ? "r+b" : "wb")); 211 | if ($fp !== false) @fseek($fp, $startpos, SEEK_SET); 212 | } 213 | 214 | $fp2 = @fopen($files[0]["file"], "rb"); 215 | 216 | if ($fp === false) $result = array("success" => false, "error" => FlexForms::FFTranslate("Unable to open a required file for writing."), "errorcode" => "open_failed", "info" => $filename); 217 | else if ($fp2 === false) $result = array("success" => false, "error" => FlexForms::FFTranslate("Unable to open a required file for reading."), "errorcode" => "open_failed", "info" => $files[0]["file"]); 218 | else 219 | { 220 | do 221 | { 222 | $data2 = @fread($fp2, 10485760); 223 | if ($data2 === false) $data2 = ""; 224 | @fwrite($fp, $data2); 225 | } while ($data2 !== ""); 226 | 227 | fclose($fp2); 228 | fclose($fp); 229 | 230 | $result = array( 231 | "success" => true 232 | ); 233 | } 234 | } 235 | } 236 | else 237 | { 238 | $name = substr($files[0]["name"], 0, -(strlen($files[0]["ext"]) + 1)); 239 | 240 | if (isset($options["filename_callback"]) && is_callable($options["filename_callback"])) $filename = call_user_func_array($options["filename_callback"], array($name, strtolower($files[0]["ext"]), $files[0])); 241 | else if (isset($options["filename"])) $filename = str_replace(array("{name}", "{ext}"), array($name, strtolower($files[0]["ext"])), $options["filename"]); 242 | else $filename = false; 243 | 244 | if (!is_string($filename)) $result = array("success" => false, "error" => FlexForms::FFTranslate("The server did not set a valid filename."), "errorcode" => "invalid_filename"); 245 | else 246 | { 247 | @copy($files[0]["file"], $filename); 248 | 249 | $result = array( 250 | "success" => true 251 | ); 252 | } 253 | } 254 | } 255 | 256 | if ($result["success"] && isset($options["result_callback"]) && is_callable($options["result_callback"])) call_user_func_array($options["result_callback"], array(&$result, $filename, $name, strtolower($files[0]["ext"]), $files[0])); 257 | 258 | echo json_encode($result, JSON_UNESCAPED_SLASHES); 259 | exit(); 260 | } 261 | } 262 | } 263 | 264 | // Register form handlers. 265 | if (is_callable("FlexForms::RegisterFormHandler")) 266 | { 267 | FlexForms::RegisterFormHandler("init", "FlexForms_FileUploader::Init"); 268 | FlexForms::RegisterFormHandler("field_type", "FlexForms_FileUploader::FieldType"); 269 | } 270 | ?> -------------------------------------------------------------------------------- /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 | ?> -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Cool File Transfer 2 | ================== 3 | 4 | Directly transfer files between two devices using nothing more than a web browser and a standard PHP enabled web server. Choose from a MIT or LGPL license. 5 | 6 | Instead of using third-party services such as Dropbox or Google Drive to transfer files, Cool File Transfer directly transfers files through a host that both devices have access to. It's great for moving large files without requiring intermediate storage! 7 | 8 | Your PC, tablet, smartphone, refridgerator, and IT department thank you for choosing Cool File Transfer. 9 | 10 | [![Awesome demo of Cool File Transfer](https://user-images.githubusercontent.com/1432111/29055577-1d21076c-7bb2-11e7-86bd-a46b825ecf27.png)](https://www.youtube.com/watch?v=haWIVLhefnA "Awesome demo of Cool File Transfer") 11 | 12 | [![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/) 13 | 14 | Features 15 | -------- 16 | 17 | * Transfer files of any size. 18 | * Unlimited user ecosystem. 19 | * Beautiful, elegant user interface. 20 | * Easy integration into websites (e.g. Intranets). 21 | * Uploading works on any device with a relatively modern-ish Javascript enabled web browser that has HTML 5 blob support. 22 | * Downloading works on any device/web browser that supports downloading files. 23 | * Can be used as the basis of a script that automatically picks up and processes content streams as they upload but without storing anything to disk. 24 | * Fabulous 2-minute installer. 25 | * Multilingual support. 26 | * Has a liberal open source license. MIT or LGPL, your choice. 27 | * Designed for relatively painless integration into your environment. 28 | * Sits on GitHub for all of that pull request and issue tracker goodness to easily submit changes and ideas respectively. 29 | 30 | Getting Started 31 | --------------- 32 | 33 | Put the contents of this repository on a web server running PHP. Then run the `install.php` file using your web browser. SQLite is a great starting database that sets up quickly but MySQL and Postgres are supported too. Assuming you have a decent setup, the installer takes only a couple of minutes. 34 | 35 | Once Cool File Transfer is installed, secure the installation path and then start using it. A nifty class that is creatively called `CoolFileTransfer` is inside `cft.php`. It's designed to be an intermediate, isolated layer between the configuration (config.php), the database, and an application (or API). Also included with Cool File Transfer is [jQuery Fancy File Uploader](https://github.com/cubiclesoft/jquery-fancyfileuploader). Let's use the `CoolFileTransfer` class to display a file transfer dropzone: 36 | 37 | ```php 38 | false, 51 | "error" => "Missing 'destuser', 'filename', or 'filesize'.", 52 | "errorcode" => "missing_required_fields" 53 | ); 54 | } 55 | else 56 | { 57 | $result = $cft->StartSendFile($srcuser, $_REQUEST["destuser"], $_REQUEST["filename"], $_REQUEST["filesize"]); 58 | } 59 | 60 | header("Content-Type: application/json"); 61 | 62 | echo json_encode($result, JSON_UNESCAPED_SLASHES); 63 | } 64 | else 65 | { 66 | // Obviously, you'll use something more dynamic for production use. 67 | $destuser = "Bob (bob@forapples.com)"; 68 | 69 | require_once "/var/www/cool-file-transfer/support/flex_forms.php"; 70 | require_once "/var/www/cool-file-transfer/support/flex_forms_fileuploader.php"; 71 | ?> 72 |
73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 98 | 99 | 108 | 111 | ``` 112 | 113 | Okay, that's half of the equation. That puts the uploader widget onto a page. Target users have to be aware that there is a file waiting for them, so the `CoolFileTransfer` class makes it easy to display notifications to users: 114 | 115 | ```php 116 | require_once "/var/www/cool-file-transfer/cft.php"; 117 | 118 | $cft = new CoolFileTransfer(); 119 | 120 | $srcuser = "Bob (bob@forapples.com)"; 121 | 122 | // Retrieve the notification list. 123 | $result = $cft->GetRecvList($srcuser); 124 | if ($result["success"] && count($result["users"])) 125 | { 126 | ?> 127 | 128 | OutputHTMLNotifications($result["users"]); 130 | } 131 | ``` 132 | 133 | Just put that code somewhere before the closing `body` tag in your HTML and your users will see nice notification boxes whenever a file is ready to transfer. 134 | 135 | Options 136 | ------- 137 | 138 | The default Javascript implementation of Cool File Transfer accepts the following options: 139 | 140 | * targeturl - A URL to send a request to in order to initiate a file transfer. The destination server is expected to return a valid result containing target URLs and tokens from `$cft->StartSendFile()` or a standard error JSON object (Default is ''). 141 | * initrequest - A Javascript object that contains additional information to pass to the server during the initial request for a token. This can be used to pass along information such as the user to send the file to (Default is an empty object). 142 | * langmap - An object containing translation strings. Support exists for most of the user interface (Default is an empty object). 143 | 144 | How it Works 145 | ------------ 146 | 147 | Other than PHP's default behavior for file handling, Cool File Transfer doesn't store file data to disk. Instead, the sending and receiving endpoints set up and connect to TCP/IP servers at various times to perform the necessary communication between two separate PHP processes. Most third-party services store a file upload until the recipient comes along and retrieves it. However, doing that requires storage space, which may or may not be feasible. Regardless, Cool File Transfer is a very different tool. 148 | 149 | The sending side attempts to self-regulate its speed to a bitrate of 1.25 seconds of transferred data per request. This allows both sides to send and receive data at roughly the same rate during a lengthy transfer process. Since most web browsers will use keep-alive connections, a little extra overhead is fine. This approach prevents stalling either side for any significant amount of time. 150 | 151 | Fun Facts 152 | --------- 153 | 154 | This product uses a LOT of existing CubicleSoft software (FlexForms, FlexForms Modules, CSDB, jQuery Fancy File Uploader, and some previously unpublished code). Completing the entire project in one weekend would not have been feasible without the existing technology stack. 155 | 156 | You read that right: This entire project was produced in just one weekend. Fully releasing everything to the Interwebs took another day though, but only because making a video to showcase a project takes time. 157 | 158 | Admin Pack/FlexForms Integration 159 | -------------------------------- 160 | 161 | If you use [Admin Pack](https://github.com/cubiclesoft/admin-pack-with-extras), then you can integrate the first half like this: 162 | 163 | ```php 164 | false, 177 | "error" => "Missing 'destuser', 'filename', or 'filesize'.", 178 | "errorcode" => "missing_required_fields" 179 | ); 180 | } 181 | else 182 | { 183 | $result = $cft->StartSendFile($srcuser, $_REQUEST["destuser"], $_REQUEST["filename"], $_REQUEST["filesize"]); 184 | } 185 | 186 | header("Content-Type: application/json"); 187 | 188 | echo json_encode($result, JSON_UNESCAPED_SLASHES); 189 | } 190 | else 191 | { 192 | // Obviously, you'll use something more dynamic for production use. 193 | $destuser = "Bob (bob@forapples.com)"; 194 | 195 | require_once "support/flex_forms_fileuploader.php"; 196 | 197 | $desc = "
"; 198 | 199 | ob_start(); 200 | ?> 201 | 202 | 211 | "You are signed in as '" . $srcuser . "'. Send files to '" . $destuser . "'.", 217 | "htmldesc" => $desc, 218 | "fields" => array( 219 | array( 220 | "type" => "file", 221 | "name" => "file", 222 | "uploader" => true, 223 | "maxchunk" => FlexForms_FileUploader::GetMaxUploadFileSize(), 224 | "uploader_callbacks" => array( 225 | "preinit" => "CoolFileTransfer.PreInit", 226 | "startupload" => "CoolFileTransfer.StartUpload", 227 | "uploadcancelled" => "CoolFileTransfer.UploadCancelled" 228 | ) 229 | ) 230 | ) 231 | ); 232 | 233 | BB_GeneratePage("Transfer Files", $menuopts, $contentopts); 234 | } 235 | ``` 236 | 237 | For [FlexForms Extras](https://github.com/cubiclesoft/php-flexforms-extras), the above will be similar but use the usual `$ff->Generate($contentopts)` method. 238 | 239 | To implement the second half in Admin Pack, abusing FlexForms CSS dependency injection via `BB_InjectLayoutHead()` is the easiest way to get Cool File Transfer notifications to appear regardless of where the user is located in the application: 240 | 241 | ```php 242 | 249 | 254 | GetRecvList($srcuser); 258 | if ($result["success"] && count($result["users"])) 259 | { 260 | // Add everything to the FlexForms instance by (ab)using CSS options. 261 | $bb_flexforms->AddCSS("cft-default", array("mode" => "link", "dependency" => false, "src" => $cft->GetDefaultCSS())); 262 | 263 | ob_start(); 264 | $cft->OutputHTMLNotifications($result["users"]); 265 | 266 | $bb_flexforms->AddCSS("cft-notifications", array("mode" => "inline", "dependency" => "cft-default", "src" => ob_get_contents())); 267 | ob_end_clean(); 268 | } 269 | } 270 | ``` 271 | -------------------------------------------------------------------------------- /support/fancy-file-uploader/fancy_fileupload.css: -------------------------------------------------------------------------------- 1 | .ff_fileupload_hidden { display: none; } 2 | 3 | .ff_fileupload_wrap .ff_fileupload_dropzone_wrap { position: relative; } 4 | 5 | .ff_fileupload_wrap .ff_fileupload_dropzone { display: block; width: 100%; height: 200px; box-sizing: border-box; border: 2px dashed #A2B4CA; border-radius: 3px; padding: 0; background-color: #FCFCFC; background-image: url('fancy_upload.png'); background-repeat: no-repeat; background-position: center center; opacity: 0.85; cursor: pointer; outline: none; } 6 | .ff_fileupload_wrap .ff_fileupload_dropzone::-moz-focus-inner { border: 0; } 7 | .ff_fileupload_wrap .ff_fileupload_dropzone:hover, .ff_fileupload_wrap .ff_fileupload_dropzone:focus, .ff_fileupload_wrap .ff_fileupload_dropzone:active { opacity: 1; background-color: #FDFDFD; border-color: #157EFB; } 8 | 9 | .ff_fileupload_wrap .ff_fileupload_dropzone_tools { position: absolute; right: 10px; top: 0; } 10 | .ff_fileupload_wrap .ff_fileupload_dropzone_tool { display: block; margin-top: 10px; width: 40px; height: 40px; box-sizing: border-box; border: 1px solid #A2B4CA; border-radius: 3px; padding: 0; background-color: #FDFDFD; background-repeat: no-repeat; background-position: center center; opacity: 0.85; cursor: pointer; outline: none; } 11 | .ff_fileupload_wrap .ff_fileupload_dropzone_tool::-moz-focus-inner { border: 0; } 12 | .ff_fileupload_wrap .ff_fileupload_dropzone_tool:hover, .ff_fileupload_wrap .ff_fileupload_dropzone_tool:focus, .ff_fileupload_wrap .ff_fileupload_dropzone_tool:active { opacity: 1; background-color: #FFFFFF; border-color: #157EFB; } 13 | 14 | .ff_fileupload_wrap .ff_fileupload_recordaudio { background-image: url('fancy_microphone.png'); } 15 | .ff_fileupload_wrap .ff_fileupload_recordvideo { background-image: url('fancy_webcam.png'); } 16 | .ff_fileupload_wrap .ff_fileupload_recordvideo_preview { position: absolute; display: block; right: 60px; top: 10px; width: 320px; max-width: calc(100% - 70px); height: calc(100% - 20px); background-color: #222222; } 17 | .ff_fileupload_wrap .ff_fileupload_recordvideo_preview.ff_fileupload_hidden { display: none; } 18 | 19 | @keyframes ff_fileupload_recording_animate { 20 | from { border-color: #EF1F1F; } 21 | to { border-color: #C9A1A1; } 22 | } 23 | 24 | .ff_fileupload_wrap .ff_fileupload_recording { animation: ff_fileupload_recording_animate 1.2s infinite alternate; } 25 | 26 | .ff_fileupload_wrap table.ff_fileupload_uploads { width: 100%; border-collapse: collapse !important; border: 0 none; } 27 | .ff_fileupload_wrap table.ff_fileupload_uploads tr, .ff_fileupload_wrap table.ff_fileupload_uploads td { margin: 0; border: 0 none; padding: 0; } 28 | .ff_fileupload_wrap table.ff_fileupload_uploads td { vertical-align: top; padding: 1em 0; white-space: nowrap; line-height: normal; } 29 | 30 | @keyframes ff_fileupload_bounce_animate { 31 | 10%, 90% { transform: translateY(-1px); } 32 | 20%, 80% { transform: translateY(2px); } 33 | 30%, 50%, 70% { transform: translateY(-3px); } 34 | 40%, 60% { transform: translateY(3px); } 35 | } 36 | 37 | .ff_fileupload_wrap table.ff_fileupload_uploads tr.ff_fileupload_bounce { animation: ff_fileupload_bounce_animate 0.82s cubic-bezier(.36,.07,.19,.97) both; transform: translateY(0); } 38 | 39 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview { width: 1px; } 40 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_image { display: block; box-sizing: border-box; border: 0 none; padding: 0; background-color: #DDDDDD; background-size: cover; background-repeat: no-repeat; background-position: center center; width: 50px; height: 50px; border-radius: 5px; opacity: 0.75; text-align: center; font-size: 12px; font-weight: bold; color: #222222; overflow: hidden; outline: none; cursor: default; } 41 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_image::-moz-focus-inner { border: 0; } 42 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_image_has_preview { cursor: pointer; } 43 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_image:hover, .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_image:focus, .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_image:active { opacity: 1; } 44 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text { display: block; margin: 0 auto; width: 70%; overflow: hidden; } 45 | 46 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_actions button { display: inline-block; vertical-align: top; width: 26px; height: 26px; box-sizing: border-box; border: 1px solid #A2B4CA; border-radius: 3px; padding: 0; background-color: #FCFCFC; background-repeat: no-repeat; background-position: center center; opacity: 0.85; cursor: pointer; outline: none; } 47 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_actions button::-moz-focus-inner { border: 0; } 48 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_actions button:hover, .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_actions button:focus, .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_actions button:active { opacity: 1; background-color: #FDFDFD; border-color: #157EFB; } 49 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_actions button.ff_fileupload_start_upload { margin-right: 0.5em; } 50 | 51 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_actions_mobile { display: none; } 52 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_actions_mobile button { display: block; margin-top: 0.3em; width: 100%; height: 28px; box-sizing: border-box; border: 1px solid #A2B4CA; border-radius: 3px; padding: 0; background-color: #FCFCFC; background-repeat: no-repeat; background-position: center center; opacity: 0.85; cursor: pointer; outline: none; } 53 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_actions_mobile button::-moz-focus-inner { border: 0; } 54 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_actions_mobile button:hover, .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_actions_mobile button:focus, .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_actions_mobile button:active { opacity: 1; background-color: #FDFDFD; border-color: #157EFB; } 55 | 56 | .ff_fileupload_wrap table.ff_fileupload_uploads button.ff_fileupload_start_upload { background-image: url('fancy_okay.png'); } 57 | .ff_fileupload_wrap table.ff_fileupload_uploads button.ff_fileupload_remove_file { background-image: url('fancy_remove.png'); } 58 | 59 | /* Colored buttons based on file extension for non-images. */ 60 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_with_color { color: #FFFFFF; } 61 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_a { background-color: #F03C3C; } 62 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_b { background-color: #F05A3C; } 63 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_c { background-color: #F0783C; } 64 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_d { background-color: #F0963C; } 65 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_e { background-color: #E0862B; } 66 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_f { background-color: #DCA12B; } 67 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_g { background-color: #C7AB1E; } 68 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_h { background-color: #C7C71E; } 69 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_i { background-color: #ABC71E; } 70 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_j { background-color: #8FC71E; } 71 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_k { background-color: #72C71E; } 72 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_l { background-color: #56C71E; } 73 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_m { background-color: #3AC71E; } 74 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_n { background-color: #1EC71E; } 75 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_o { background-color: #1EC73A; } 76 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_p { background-color: #1EC756; } 77 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_q { background-color: #1EC78F; } 78 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_r { background-color: #1EC7AB; } 79 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_s { background-color: #1EC7C7; } 80 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_t { background-color: #1EABC7; } 81 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_u { background-color: #1E8FC7; } 82 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_v { background-color: #1E72C7; } 83 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_w { background-color: #3C78F0; } 84 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_x { background-color: #3C5AF0; } 85 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_y { background-color: #3C3CF0; } 86 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_z { background-color: #5A3CF0; } 87 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_0 { background-color: #783CF0; } 88 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_1 { background-color: #963CF0; } 89 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_2 { background-color: #B43CF0; } 90 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_3 { background-color: #D23CF0; } 91 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_4 { background-color: #F03CF0; } 92 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_5 { background-color: #F03CD2; } 93 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_6 { background-color: #F03CB4; } 94 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_7 { background-color: #F03C96; } 95 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_8 { background-color: #F03C78; } 96 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_text_9 { background-color: #F03C5A; } 97 | 98 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_summary { padding: 1em; font-size: 0.9em; white-space: normal; } 99 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_summary .ff_fileupload_filename { width: 100%; font-weight: bold; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } 100 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_summary .ff_fileupload_filename input { box-sizing: border-box; width: 100%; padding: 0.3em; margin-bottom: 0.1em; font-size: 1.0em; font-weight: normal; line-height: normal; border: 1px solid #BBBBBB; border-radius: 0; box-shadow: none; } 101 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_summary .ff_fileupload_filename input:focus, .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_summary .ff_fileupload_filename input:hover { border: 1px solid #888888; } 102 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_summary .ff_fileupload_errors { color: #A94442; font-weight: bold; } 103 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_summary .ff_fileupload_progress_background { margin-top: 0.5em; background-color: #CCCCCC; height: 2px; } 104 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_summary .ff_fileupload_progress_bar { background-color: #157EFB; width: 0; height: 2px; } 105 | 106 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_actions { width: 1px; text-align: right; } 107 | 108 | @media (max-width: 420px) { 109 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_preview_image { width: 36px; height: 36px; font-size: 11px; } 110 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_summary { padding-right: 0; } 111 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_actions { display: none; } 112 | .ff_fileupload_wrap table.ff_fileupload_uploads td.ff_fileupload_preview .ff_fileupload_actions_mobile { display: block; } 113 | } 114 | 115 | .ff_fileupload_dialog_background { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.85); z-index: 10000; } 116 | .ff_fileupload_dialog_main { position: absolute; top: 10%; left: 10%; width: 80%; height: 80%; text-align: center; } 117 | .ff_fileupload_dialog_main img { position: relative; top: 50%; transform: perspective(1px) translateY(-50%); max-width: 100%; max-height: 100%; } 118 | .ff_fileupload_dialog_main audio { position: relative; top: 50%; transform: perspective(1px) translateY(-50%); width: 100%; } 119 | .ff_fileupload_dialog_main video { position: relative; top: 50%; transform: perspective(1px) translateY(-50%); max-width: 100%; max-height: 100%; } 120 | -------------------------------------------------------------------------------- /support/csdb/db_pgsql.php: -------------------------------------------------------------------------------- 1 | lastid = 0; 24 | 25 | parent::Connect($dsn, $username, $password, $options); 26 | 27 | // Set Unicode support. 28 | $this->Query("SET", "client_encoding TO 'UTF-8'"); 29 | } 30 | 31 | public function GetVersion() 32 | { 33 | return $this->GetOne("SELECT", array("VERSION()")); 34 | } 35 | 36 | public function GetInsertID($name = null) 37 | { 38 | return $this->lastid; 39 | } 40 | 41 | public function TableExists($name) 42 | { 43 | return ($this->GetOne("SHOW TABLES", array("LIKE" => $name)) === false ? false : true); 44 | } 45 | 46 | public function QuoteIdentifier($str) 47 | { 48 | return "\"" . str_replace(array("\"", "?"), array("\"\"", ""), $str) . "\""; 49 | } 50 | 51 | protected function GenerateSQL(&$master, &$sql, &$opts, $cmd, $queryinfo, $args, $subquery) 52 | { 53 | switch ($cmd) 54 | { 55 | case "SELECT": 56 | { 57 | $supported = array( 58 | "PRECOLUMN" => array("DISTINCT" => "bool", "SUBQUERIES" => true), 59 | "FROM" => array("SUBQUERIES" => true), 60 | "WHERE" => array("SUBQUERIES" => true), 61 | "GROUP BY" => true, 62 | "HAVING" => true, 63 | "ORDER BY" => true, 64 | "LIMIT" => " OFFSET " 65 | ); 66 | 67 | return $this->ProcessSELECT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 68 | } 69 | case "INSERT": 70 | { 71 | $supported = array( 72 | "PREINTO" => array(), 73 | "POSTVALUES" => array("RETURNING" => "key_identifier"), 74 | "SELECT" => true, 75 | "BULKINSERT" => true 76 | ); 77 | 78 | // To get the last insert ID via GetInsertID(), the field that contains a 'serial' (auto increment) field must be specified. 79 | if (isset($queryinfo["AUTO INCREMENT"])) $queryinfo["RETURNING"] = $queryinfo["AUTO INCREMENT"]; 80 | 81 | $this->lastid = 0; 82 | $result = $this->ProcessINSERT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 83 | if ($result["success"] && isset($queryinfo["AUTO INCREMENT"])) $result["filter_opts"] = array("mode" => "INSERT", "queryinfo" => $queryinfo); 84 | 85 | return $result; 86 | } 87 | case "UPDATE": 88 | { 89 | // No ORDER BY or LIMIT support. 90 | $supported = array( 91 | "PRETABLE" => array("ONLY" => "bool"), 92 | "WHERE" => array("SUBQUERIES" => true) 93 | ); 94 | 95 | return $this->ProcessUPDATE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 96 | } 97 | case "DELETE": 98 | { 99 | // No ORDER BY or LIMIT support. 100 | $supported = array( 101 | "PREFROM" => array("ONLY" => "bool"), 102 | "WHERE" => array("SUBQUERIES" => true) 103 | ); 104 | 105 | return $this->ProcessDELETE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 106 | } 107 | case "SET": 108 | { 109 | $sql = "SET " . $queryinfo; 110 | 111 | return array("success" => true); 112 | } 113 | case "USE": 114 | { 115 | // Fake multiple databases with PostgreSQL schemas. 116 | // http://www.postgresql.org/docs/7.3/static/ddl-schemas.html 117 | $sql = "SET search_path TO " . ($queryinfo != "" ? $this->QuoteIdentifier($queryinfo) . "," : "") . "\"\$user\",public"; 118 | 119 | return array("success" => true); 120 | } 121 | case "CREATE DATABASE": 122 | { 123 | $master = true; 124 | 125 | $sql = "CREATE SCHEMA " . $this->QuoteIdentifier($queryinfo[0]); 126 | 127 | return array("success" => true); 128 | } 129 | case "DROP DATABASE": 130 | { 131 | $master = true; 132 | 133 | $sql = "DROP SCHEMA " . $this->QuoteIdentifier($queryinfo[0]); 134 | 135 | return array("success" => true); 136 | } 137 | case "CREATE TABLE": 138 | { 139 | // UTF-8 support has to be declared at the database (not schema or table) level. 140 | // CREATE DATABASE whatever WITH ENCODING 'UTF8'; 141 | // See also: http://stackoverflow.com/questions/9961795/ 142 | $supported = array( 143 | "TEMPORARY" => "CREATE TEMPORARY TABLE", 144 | "AS_SELECT" => true, 145 | "PRE_AS" => array(), 146 | "PROCESSKEYS" => true, 147 | "POSTCREATE" => array() 148 | ); 149 | 150 | $result = $this->ProcessCREATE_TABLE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 151 | if (!$result["success"]) return $result; 152 | 153 | // Handle named keys and fulltext searches. 154 | $sql = array($sql); 155 | $opts = array($opts); 156 | if (isset($queryinfo[2]) && is_array($queryinfo[2])) 157 | { 158 | foreach ($queryinfo[2] as $info) 159 | { 160 | if (isset($info["NAME"])) 161 | { 162 | if (strtoupper($info[0]) == "KEY" || strtoupper($info[0]) == "FULLTEXT") 163 | { 164 | $sql2 = ""; 165 | $opts2 = array(); 166 | $result = $this->GenerateSQL($master, $sql2, $opts2, "ADD INDEX", array($queryinfo[0], $info), array(), false); 167 | if (!$result["success"]) return $result; 168 | 169 | $sql[] = $sql2; 170 | $opts[] = $opts2; 171 | } 172 | } 173 | } 174 | } 175 | 176 | return $result; 177 | } 178 | case "ADD COLUMN": 179 | { 180 | $master = true; 181 | 182 | $result = $this->ProcessColumnDefinition($queryinfo[2]); 183 | if (!$result["success"]) return $result; 184 | 185 | $sql = "ALTER TABLE " . $this->QuoteIdentifier($queryinfo[0]) . " ADD COLUMN " . $this->QuoteIdentifier($queryinfo[1]) . " " . $result["sql"]; 186 | 187 | return array("success" => true); 188 | } 189 | case "DROP COLUMN": 190 | { 191 | $master = true; 192 | 193 | $sql = "ALTER TABLE " . $this->QuoteIdentifier($queryinfo[0]) . " DROP COLUMN " . $this->QuoteIdentifier($queryinfo[1]); 194 | 195 | return array("success" => true); 196 | } 197 | case "ADD INDEX": 198 | { 199 | $master = true; 200 | 201 | $keyinfo = $queryinfo[1]; 202 | $type = strtoupper($keyinfo[0]); 203 | foreach ($keyinfo[1] as $num => $field) $keyinfo[1][$num] = $this->QuoteIdentifier($field); 204 | 205 | if (!isset($keyinfo["NAME"])) return array("success" => false, "errorcode" => "skip_sql_query"); 206 | 207 | if ($type == "PRIMARY" || $type == "UNIQUE") $sql = "CREATE UNIQUE INDEX "; 208 | else if ($type == "FOREIGN") return array("success" => false, "errorcode" => "skip_sql_query"); 209 | else $sql = "CREATE INDEX "; 210 | 211 | $sql .= $this->QuoteIdentifier($keyinfo["NAME"]) . " ON " . $this->QuoteIdentifier($queryinfo[0]) . ($type == "FULLTEXT" ? " USING GIN" : "") . " (" . implode(", ", $keyinfo[1]) . ")"; 212 | 213 | return array("success" => true); 214 | } 215 | case "DROP INDEX": 216 | { 217 | $master = true; 218 | 219 | if (!isset($queryinfo[2])) return array("success" => false, "errorcode" => "skip_sql_query"); 220 | 221 | $sql = "DROP INDEX " . $this->QuoteIdentifier($queryinfo[2]); 222 | 223 | return array("success" => true); 224 | } 225 | case "TRUNCATE TABLE": 226 | { 227 | $master = true; 228 | 229 | $sql = "TRUNCATE TABLE " . $this->QuoteIdentifier($queryinfo[0]); 230 | 231 | return array("success" => true); 232 | } 233 | case "DROP TABLE": 234 | { 235 | $master = true; 236 | 237 | $sql = "DROP TABLE " . $this->QuoteIdentifier($queryinfo[0]); 238 | if (isset($queryinfo["RESTRICT"]) && $queryinfo["RESTRICT"]) $sql .= " RESTRICT"; 239 | else if (isset($queryinfo["CASCADE"]) && $queryinfo["CASCADE"]) $sql .= " CASCADE"; 240 | 241 | return array("success" => true); 242 | } 243 | case "SHOW DATABASES": 244 | { 245 | // Not a perfect query. Any schema starting with 'pg_' will be skipped. 246 | $sql = "SELECT schema_name AS name FROM information_schema.schemata WHERE schema_name NOT LIKE 'pg_%' AND schema_name <> 'information_schema'"; 247 | if (isset($queryinfo[0]) && $queryinfo[0] == "") unset($queryinfo[0]); 248 | 249 | return array("success" => true, "filter_opts" => array("mode" => "SHOW DATABASES", "queryinfo" => $queryinfo)); 250 | } 251 | case "SHOW TABLES": 252 | { 253 | $sql = "SELECT " . (isset($queryinfo["FULL"]) && $queryinfo["FULL"] ? "*" : "table_name AS name") . " FROM information_schema.tables WHERE (table_schema = ? OR table_type = 'LOCAL TEMPORARY')" . (isset($queryinfo["LIKE"]) ? " AND table_name LIKE ?" : "") . " ORDER BY table_name"; 254 | $opts[] = (isset($queryinfo["FROM"]) ? $queryinfo["FROM"] : ($this->currdb !== false ? $this->currdb : "public")); 255 | if (isset($queryinfo[0]) && $queryinfo[0] == "") unset($queryinfo[0]); 256 | if (isset($queryinfo["LIKE"])) $opts[] = $queryinfo["LIKE"]; 257 | 258 | return array("success" => true, "filter_opts" => array("mode" => "SHOW TABLES", "queryinfo" => $queryinfo)); 259 | } 260 | case "SHOW CREATE DATABASE": 261 | { 262 | // Not a perfect query. Any schema starting with 'pg_' will be skipped. 263 | $sql = "SELECT schema_name AS name FROM information_schema.schemata WHERE schema_name NOT LIKE 'pg_%' AND schema_name <> 'information_schema' AND schema_name = ?"; 264 | $opts[] = $queryinfo[0]; 265 | 266 | return array("success" => true, "filter_opts" => array("mode" => "SHOW CREATE DATABASE")); 267 | } 268 | case "SHOW CREATE TABLE": 269 | { 270 | $sql = "SELECT table_schema, table_name, table_type FROM information_schema.tables WHERE (table_schema = ? OR table_type = 'LOCAL TEMPORARY') AND table_name = ?"; 271 | $opts[] = ($this->currdb !== false ? $this->currdb : "public"); 272 | $opts[] = $queryinfo[0]; 273 | 274 | return array("success" => true, "filter_opts" => array("mode" => "SHOW CREATE TABLE", "hints" => (isset($queryinfo["EXPORT HINTS"]) ? $queryinfo["EXPORT HINTS"] : array()))); 275 | } 276 | case "BULK IMPORT MODE": 277 | { 278 | $master = true; 279 | 280 | if ($queryinfo) $sql = "SET synchronous_commit TO OFF"; 281 | else $sql = "SET synchronous_commit TO DEFAULT"; 282 | 283 | return array("success" => true); 284 | } 285 | } 286 | 287 | return array("success" => false, "error" => CSDB::DB_Translate("Unknown query command '%s'.", $cmd), "errorcode" => "unknown_query_command"); 288 | } 289 | 290 | protected function RunStatementFilter(&$stmt, &$filteropts) 291 | { 292 | if ($filteropts["mode"] == "INSERT") 293 | { 294 | // Force the last ID value to be extracted for INSERT queries. 295 | $result = new CSDB_PDO_Statement($this, $stmt, $filteropts); 296 | $row = $result->NextRow(); 297 | 298 | $stmt = false; 299 | } 300 | 301 | parent::RunStatementFilter($stmt, $filteropts); 302 | } 303 | 304 | public function RunRowFilter(&$row, &$filteropts, &$fetchnext) 305 | { 306 | switch ($filteropts["mode"]) 307 | { 308 | case "INSERT": 309 | { 310 | if ($row !== false) 311 | { 312 | $key = $filteropts["queryinfo"]["AUTO INCREMENT"]; 313 | $this->lastid = $row->$key; 314 | } 315 | 316 | break; 317 | } 318 | case "SHOW CREATE DATABASE": 319 | { 320 | if ($row !== false) 321 | { 322 | $row->cmd = "CREATE DATABASE"; 323 | $row->opts = array($row->name); 324 | } 325 | 326 | break; 327 | } 328 | case "SHOW CREATE TABLE": 329 | { 330 | if ($row !== false) 331 | { 332 | $cols = array(); 333 | $result2 = $this->Query("SELECT", array( 334 | "*", 335 | "FROM" => "information_schema.columns", 336 | "WHERE" => "table_schema = ? AND table_name = ?", 337 | "ORDER BY" => "ordinal_position" 338 | ), $row->table_schema, $row->table_name); 339 | while ($row2 = $result2->NextRow()) 340 | { 341 | if (isset($filteropts["hints"]) && isset($filteropts["hints"][$row2->column_name])) $col = $filteropts["hints"][$row2->column_name]; 342 | else 343 | { 344 | $row2->data_type = str_replace(array(" with timezone", "without timezone", "[]"), "", $row2->data_type); 345 | 346 | switch ($row2->data_type) 347 | { 348 | case "integer": $col = array("INTEGER", 4); break; 349 | case "bigint": $col = array("INTEGER", 8); break; 350 | case "smallint": $col = array("INTEGER", 2); break; 351 | case "real": $col = array("FLOAT", 4); break; 352 | case "double precision": $col = array("FLOAT", 8); break; 353 | case "numeric": $col = array("DECIMAL", $row2->numeric_precision, $row2->numeric_scale); break; 354 | case "\"char\"": $col = array("STRING", 1, $row2->character_maximum_length, "FIXED" => true); break; 355 | case "character varying": $col = array("STRING", 1, $row2->character_maximum_length); break; 356 | case "text": $col = array("STRING", 4); break; 357 | case "bytea": $col = array("BINARY", 4); break; 358 | case "date": $col = array("DATE"); break; 359 | case "time": $col = array("TIME"); break; 360 | case "timestamp": $col = array("DATETIME"); break; 361 | case "boolean": $col = array("BOOLEAN"); break; 362 | 363 | default: return; 364 | } 365 | 366 | if ($row2->is_nullable == "NO") $col["NOT NULL"] = true; 367 | if (isset($row2->column_default)) 368 | { 369 | if (strtolower(substr($row2->column_default, 0, 8)) == "nextval(" && strtolower(substr($row2->column_default, -11)) == "::regclass)") $col["AUTO INCREMENT"] = true; 370 | else $col["DEFAULT"] = $row2->column_default; 371 | } 372 | } 373 | 374 | $cols[$row2->column_name] = $col; 375 | } 376 | 377 | // Process indexes. 378 | $lastindex = 0; 379 | $keys = array(); 380 | $result2 = $this->Query("SELECT", array( 381 | "c.oid, c.relname, a.attname, a.attnum, i.indisprimary, i.indisunique", 382 | "FROM" => "pg_index AS i, pg_class AS c, pg_attribute AS a", 383 | "WHERE" => "i.indexrelid = c.oid AND i.indexrelid = a.attrelid AND i.indrelid = " . $this->Quote($this->QuoteIdentifier($row->table_schema) . "." . $this->QuoteIdentifier($row->table_name)) . "::regclass", 384 | "ORDER BY" => "c.oid, a.attnum" 385 | )); 386 | while ($row2 = $result2->NextRow()) 387 | { 388 | if ($lastindex != $row2->oid) 389 | { 390 | if ($lastindex > 0) $keys[] = $key; 391 | 392 | // FULLTEXT index extraction is missing. Feel free to submit a patch. 393 | if ($row2->indisprimary) $type = "PRIMARY"; 394 | else if ($row2->indisunique) $type = "UNIQUE"; 395 | else $type = "KEY"; 396 | 397 | $key = array($type, array(), "NAME" => $row2->relname); 398 | $lastindex = $row2->oid; 399 | } 400 | 401 | $key[1][] = $row2->attname; 402 | } 403 | if ($lastindex > 0) $keys[] = $key; 404 | 405 | // Process foreign keys? It would be nice to see some examples. 406 | 407 | // Generate the final CREATE TABLE information. 408 | $row->cmd = "CREATE TABLE"; 409 | $row->opts = array($row->table_name, $cols); 410 | if (count($keys)) $row->opts[] = $keys; 411 | if ($row->table_type == "LOCAL TEMPORARY") $row->opts["TEMPORARY"] = true; 412 | } 413 | 414 | break; 415 | } 416 | } 417 | 418 | if (!$fetchnext) parent::RunRowFilter($row, $filteropts, $fetchnext); 419 | } 420 | 421 | protected function ProcessColumnDefinition($info) 422 | { 423 | $sql = ""; 424 | $type = strtoupper($info[0]); 425 | switch ($type) 426 | { 427 | case "INTEGER": 428 | { 429 | $varbytes = (isset($info[1]) && (int)$info[1] > 0 ? (int)$info[1] : 4); 430 | if (isset($info["AUTO INCREMENT"]) && $info["AUTO INCREMENT"]) 431 | { 432 | if ($varbytes < 4) $sql .= " SERIAL"; 433 | else $sql .= " BIGSERIAL"; 434 | } 435 | else 436 | { 437 | if ($varbytes < 3) $sql .= " SMALLINT"; 438 | else if ($varbytes < 5) $sql .= " INTEGER"; 439 | else $sql .= " BIGINT"; 440 | 441 | // No UNSIGNED support in PostgreSQL. 442 | } 443 | 444 | break; 445 | } 446 | case "FLOAT": 447 | { 448 | $varbytes = (isset($info[1]) ? (int)$info[1] : 4); 449 | if ($varbytes <= 4) $sql .= " REAL"; 450 | else $sql .= " DOUBLE PRECISION"; 451 | 452 | break; 453 | } 454 | case "DECIMAL": 455 | { 456 | $sql .= " NUMERIC"; 457 | if (isset($info[1])) $sql .= "(" . $info[1] . (isset($info[2]) ? ", " . $info[2] : "") . ")"; 458 | 459 | break; 460 | } 461 | case "STRING": 462 | { 463 | $varbytes = $info[1]; 464 | if ($varbytes == 1) 465 | { 466 | if (isset($info["FIXED"]) && $info["FIXED"]) $sql .= " CHAR(" . $info[2] . ")"; 467 | else $sql .= " VARCHAR(" . $info[2] . ")"; 468 | } 469 | else 470 | { 471 | $sql .= " TEXT"; 472 | } 473 | 474 | break; 475 | } 476 | case "BINARY": $sql .= " BYTEA"; break; 477 | case "DATE": $sql .= " DATE"; break; 478 | case "TIME": $sql .= " TIME"; break; 479 | case "DATETIME": $sql .= " TIMESTAMP"; break; 480 | case "BOOLEAN": $sql .= " BOOLEAN"; break; 481 | default: return array("success" => false, "error" => CSDB::DB_Translate("Unknown column type '%s'.", $type), "errorcode" => "unknown_column_type"); 482 | } 483 | 484 | if (isset($info["NOT NULL"]) && $info["NOT NULL"]) $sql .= " NOT NULL"; 485 | if (isset($info["DEFAULT"])) $sql .= " DEFAULT " . $this->Quote($info["DEFAULT"]); 486 | if (isset($info["PRIMARY KEY"]) && $info["PRIMARY KEY"]) $sql .= " PRIMARY KEY"; 487 | if (isset($info["UNIQUE KEY"]) && $info["UNIQUE KEY"]) $sql .= " UNIQUE"; 488 | if (isset($info["REFERENCES"])) $sql .= " REFERENCES " . $this->ProcessReferenceDefinition($info["REFERENCES"]); 489 | 490 | return array("success" => true, "sql" => $sql); 491 | } 492 | 493 | protected function ProcessKeyDefinition($info) 494 | { 495 | $sql = ""; 496 | $type = strtoupper($info[0]); 497 | foreach ($info[1] as $num => $field) $info[1][$num] = $this->QuoteIdentifier($field); 498 | switch ($type) 499 | { 500 | case "PRIMARY": 501 | { 502 | if (isset($info["CONSTRAINT"])) $sql .= "CONSTRAINT " . $info["CONSTRAINT"] . " "; 503 | $sql .= "PRIMARY KEY"; 504 | $sql .= " (" . implode(", ", $info[1]) . ")"; 505 | if (isset($info["OPTION"])) $sql .= " " . $info["OPTION"]; 506 | 507 | break; 508 | } 509 | case "KEY": 510 | { 511 | // PostgreSQL CREATE TABLE doesn't support regular KEY indexes, but ADD INDEX does. 512 | 513 | break; 514 | } 515 | case "UNIQUE": 516 | { 517 | if (isset($info["CONSTRAINT"])) $sql .= "CONSTRAINT " . $info["CONSTRAINT"] . " "; 518 | $sql .= "UNIQUE"; 519 | $sql .= " (" . implode(", ", $info[1]) . ")"; 520 | if (isset($info["OPTION"])) $sql .= " " . $info["OPTION"]; 521 | 522 | break; 523 | } 524 | case "FULLTEXT": 525 | { 526 | // PostgreSQL CREATE TABLE doesn't support regular FULLTEXT indexes, but ADD INDEX does. 527 | 528 | break; 529 | } 530 | case "FOREIGN": 531 | { 532 | if (isset($info["CONSTRAINT"])) $sql .= "CONSTRAINT " . $info["CONSTRAINT"] . " "; 533 | $sql .= "FOREIGN KEY"; 534 | $sql .= " (" . implode(", ", $info[1]) . ")"; 535 | $sql .= " REFERENCES " . $this->ProcessReferenceDefinition($info[2]); 536 | 537 | break; 538 | } 539 | default: return array("success" => false, "error" => CSDB::DB_Translate("Unknown key type '%s'.", $type), "errorcode" => "unknown_key_type");; 540 | } 541 | 542 | return array("success" => true, "sql" => $sql); 543 | } 544 | } 545 | ?> -------------------------------------------------------------------------------- /support/csdb/db_sqlite.php: -------------------------------------------------------------------------------- 1 | dbprefix = ""; 24 | 25 | parent::Connect($dsn, $username, $password, $options); 26 | } 27 | 28 | public function GetVersion() 29 | { 30 | return $this->GetOne("SELECT", array("SQLITE_VERSION()")); 31 | } 32 | 33 | public function GetInsertID($name = null) 34 | { 35 | return $this->GetOne("SELECT", array("LAST_INSERT_ROWID()")); 36 | } 37 | 38 | public function TableExists($name) 39 | { 40 | return ($this->GetOne("SHOW TABLES", array("LIKE" => (strpos($name, "__") === false ? $this->dbprefix : "") . $name)) === false ? false : true); 41 | } 42 | 43 | public function QuoteIdentifier($str) 44 | { 45 | return "\"" . str_replace(array("\"", "?"), array("\"\"", ""), $str) . "\""; 46 | } 47 | 48 | protected function GenerateSQL(&$master, &$sql, &$opts, $cmd, $queryinfo, $args, $subquery) 49 | { 50 | switch ($cmd) 51 | { 52 | case "SELECT": 53 | { 54 | $supported = array( 55 | "DBPREFIX" => $this->dbprefix, 56 | "PRECOLUMN" => array("DISTINCT" => "bool", "SUBQUERIES" => true), 57 | "FROM" => array("SUBQUERIES" => true), 58 | "WHERE" => array("SUBQUERIES" => true), 59 | "GROUP BY" => true, 60 | "HAVING" => true, 61 | "ORDER BY" => true, 62 | "LIMIT" => ", " 63 | ); 64 | 65 | return $this->ProcessSELECT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 66 | } 67 | case "INSERT": 68 | { 69 | $supported = array( 70 | "DBPREFIX" => $this->dbprefix, 71 | "PREINTO" => array("LOW_PRIORITY" => "bool", "DELAYED" => "bool", "HIGH_PRIORITY" => "bool", "IGNORE" => "bool"), 72 | "SELECT" => true, 73 | "BULKINSERT" => true, 74 | "BULKINSERTLIMIT" => 900, 75 | ); 76 | 77 | return $this->ProcessINSERT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 78 | } 79 | case "UPDATE": 80 | { 81 | $supported = array( 82 | "DBPREFIX" => $this->dbprefix, 83 | "PRETABLE" => array("LOW_PRIORITY" => "bool", "IGNORE" => "bool"), 84 | "WHERE" => array("SUBQUERIES" => true), 85 | "ORDER BY" => true, 86 | "LIMIT" => ", " 87 | ); 88 | 89 | return $this->ProcessUPDATE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 90 | } 91 | case "DELETE": 92 | { 93 | $supported = array( 94 | "DBPREFIX" => $this->dbprefix, 95 | "PREFROM" => array("LOW_PRIORITY" => "bool", "QUICK" => "bool", "IGNORE" => "bool"), 96 | "WHERE" => array("SUBQUERIES" => true), 97 | "ORDER BY" => true, 98 | "LIMIT" => ", " 99 | ); 100 | 101 | return $this->ProcessDELETE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 102 | } 103 | case "SET": 104 | case "CREATE DATABASE": 105 | { 106 | return array("success" => false, "errorcode" => "skip_sql_query"); 107 | } 108 | case "USE": 109 | { 110 | $this->dbprefix = $this->GetDBPrefix($queryinfo); 111 | 112 | return array("success" => false, "errorcode" => "skip_sql_query"); 113 | } 114 | case "DROP DATABASE": 115 | { 116 | $master = true; 117 | 118 | $result = $this->Query("SHOW TABLES", array("FROM" => $queryinfo)); 119 | while ($row = $result->NextRow()) 120 | { 121 | $this->Query("DROP TABLE", array($row->name, "FROM" => $queryinfo)); 122 | } 123 | 124 | return array("success" => false, "errorcode" => "skip_sql_query"); 125 | } 126 | case "CREATE TABLE": 127 | { 128 | $supported = array( 129 | "DBPREFIX" => $this->dbprefix, 130 | "TEMPORARY" => "CREATE TEMPORARY TABLE", 131 | "AS_SELECT" => true, 132 | "PROCESSKEYS" => true, 133 | "POSTCREATE" => array() 134 | ); 135 | 136 | $result = $this->ProcessCREATE_TABLE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 137 | if (!$result["success"]) return $result; 138 | 139 | // Handle named keys. 140 | $sql = array($sql); 141 | $opts = array($opts); 142 | if (isset($queryinfo[2]) && is_array($queryinfo[2])) 143 | { 144 | foreach ($queryinfo[2] as $info) 145 | { 146 | if (strtoupper($info[0]) == "KEY" && isset($info["NAME"])) 147 | { 148 | $sql2 = ""; 149 | $opts2 = array(); 150 | $result = $this->GenerateSQL($master, $sql2, $opts2, "ADD INDEX", array($queryinfo[0], $info), array(), false); 151 | if (!$result["success"]) return $result; 152 | 153 | $sql[] = $sql2; 154 | $opts[] = $opts2; 155 | } 156 | } 157 | } 158 | 159 | return $result; 160 | } 161 | case "ADD COLUMN": 162 | { 163 | $master = true; 164 | 165 | $result = $this->ProcessColumnDefinition($queryinfo[2]); 166 | if (!$result["success"]) return $result; 167 | 168 | $sql = "ALTER TABLE " . $this->QuoteIdentifier($queryinfo[0]) . " ADD COLUMN " . $this->QuoteIdentifier($queryinfo[1]) . " " . $result["sql"]; 169 | 170 | return array("success" => true); 171 | } 172 | case "PRAGMA": 173 | { 174 | // PRAGMA support exists solely for SQLite DROP COLUMN. 175 | $master = true; 176 | 177 | $sql = "PRAGMA " . $queryinfo; 178 | 179 | return array("success" => true); 180 | } 181 | case "DROP COLUMN": 182 | { 183 | // Has to be emulated by creating a temporary table without the column. 184 | // Triggers, views, and foreign keys will probably be lost. If you care, then submit a patch. 185 | $master = true; 186 | 187 | $row = $this->GetRow("SHOW CREATE TABLE", array($queryinfo[0])); 188 | if (!$row) return array("success" => false, "error" => CSDB::DB_Translate("An error occurred while getting existing table information."), "errorcode" => "show_create_table_error"); 189 | 190 | if (!isset($row->opts[1][$queryinfo[1]])) return array("success" => false, "errorcode" => "skip_sql_query"); 191 | 192 | unset($row->opts[1][$queryinfo[1]]); 193 | 194 | if (!count($row->opts[1])) return array("success" => false, "error" => CSDB::DB_Translate("Table contains only one column."), "errorcode" => "drop_column_error"); 195 | 196 | $opts2 = $row->opts; 197 | $opts2[0] = "csdb__dropcol"; 198 | $opts2["TEMPORARY"] = true; 199 | 200 | $cols = array_keys($opts2[1]); 201 | $this->Query("CREATE TABLE", $opts2); 202 | $this->Query("INSERT", array("csdb__dropcol", $cols, "SELECT" => array(array($cols, "?"), $queryinfo[0]))); 203 | $this->Query("DROP TABLE", array($queryinfo[0])); 204 | $this->Query("CREATE TABLE", $row->opts); 205 | $this->Query("INSERT", array($queryinfo[0], $cols, "SELECT" => array(array($cols, "?"), "csdb__dropcol"))); 206 | $this->Query("DROP TABLE", array("csdb__dropcol")); 207 | 208 | return array("success" => false, "errorcode" => "skip_sql_query"); 209 | } 210 | case "ADD INDEX": 211 | { 212 | $master = true; 213 | 214 | $keyinfo = $queryinfo[1]; 215 | $type = strtoupper($keyinfo[0]); 216 | foreach ($keyinfo[1] as $num => $field) $keyinfo[1][$num] = $this->QuoteIdentifier($field); 217 | 218 | if (!isset($keyinfo["NAME"])) return array("success" => false, "errorcode" => "skip_sql_query"); 219 | 220 | if ($type == "PRIMARY" || $type == "UNIQUE") $sql = "CREATE UNIQUE INDEX "; 221 | else if ($type == "FOREIGN") return array("success" => false, "errorcode" => "skip_sql_query"); 222 | else $sql = "CREATE INDEX "; 223 | 224 | $sql .= $this->QuoteIdentifier($this->dbprefix . $queryinfo[0] . "__" . $keyinfo["NAME"]) . " ON " . $this->QuoteIdentifier($this->dbprefix . $queryinfo[0]) . " (" . implode(", ", $keyinfo[1]) . ")"; 225 | 226 | return array("success" => true); 227 | } 228 | case "DROP INDEX": 229 | { 230 | $master = true; 231 | 232 | if (!isset($queryinfo[2])) return array("success" => false, "errorcode" => "skip_sql_query"); 233 | 234 | $sql = "DROP INDEX " . $this->QuoteIdentifier($this->dbprefix . $queryinfo[2]); 235 | 236 | return array("success" => true); 237 | } 238 | case "TRUNCATE TABLE": 239 | { 240 | $supported = array( 241 | "DBPREFIX" => $this->dbprefix, 242 | "PREFROM" => array() 243 | ); 244 | 245 | $queryinfo = array($queryinfo[0]); 246 | 247 | return $this->ProcessDELETE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 248 | } 249 | case "DROP TABLE": 250 | { 251 | $master = true; 252 | 253 | $dbprefix = (isset($queryinfo["FROM"]) ? $this->GetDBPrefix($queryinfo["FROM"]) : $this->dbprefix); 254 | $sql = "DROP TABLE " . $this->QuoteIdentifier($dbprefix . $queryinfo[0]); 255 | 256 | return array("success" => true); 257 | } 258 | case "SHOW DATABASES": 259 | { 260 | // Swiped from: http://www.sqlite.org/faq.html#q7 261 | $sql = "SELECT name FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = 'table' ORDER BY name"; 262 | if (isset($queryinfo[0])) $queryinfo[0] = $this->GetDBPrefix($queryinfo[0]); 263 | 264 | return array("success" => true, "filter_opts" => array("mode" => "SHOW DATABASES", "queryinfo" => $queryinfo, "names" => array())); 265 | } 266 | case "SHOW TABLES": 267 | { 268 | // Swiped from: http://www.sqlite.org/faq.html#q7 269 | $sql = "SELECT " . (isset($queryinfo["FULL"]) && $queryinfo["FULL"] ? "*" : "name") . " FROM (SELECT *, 'normal' AS tbl_type FROM sqlite_master UNION ALL SELECT *, 'temp' AS tbl_type FROM sqlite_temp_master) WHERE type = 'table'" . (isset($queryinfo["LIKE"]) ? " AND name LIKE ?" : "") . " ORDER BY name"; 270 | if (isset($queryinfo[0]) && $queryinfo[0] == "") unset($queryinfo[0]); 271 | if (isset($queryinfo["LIKE"])) $opts[] = $queryinfo["LIKE"]; 272 | 273 | return array("success" => true, "filter_opts" => array("mode" => "SHOW TABLES", "queryinfo" => $queryinfo)); 274 | } 275 | case "SHOW CREATE DATABASE": 276 | { 277 | $sql = "SELECT name FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = 'table' ORDER BY name"; 278 | $queryinfo[0] = (string)@substr((isset($queryinfo[0]) ? $this->GetDBPrefix($queryinfo[0]) : $this->dbprefix), 0, -2); 279 | 280 | return array("success" => true, "filter_opts" => array("mode" => "SHOW CREATE DATABASE", "output" => false, "queryinfo" => $queryinfo)); 281 | } 282 | case "SHOW CREATE TABLE": 283 | { 284 | $sql = "SELECT tbl_type, sql FROM (SELECT *, 'normal' AS tbl_type FROM sqlite_master UNION ALL SELECT *, 'temp' AS tbl_type FROM sqlite_temp_master) WHERE type = 'table' AND name = ?"; 285 | 286 | $opts = array($this->dbprefix . $queryinfo[0]); 287 | 288 | return array("success" => true, "filter_opts" => array("mode" => "SHOW CREATE TABLE", "output" => false, "queryinfo" => $queryinfo, "hints" => (isset($queryinfo["EXPORT HINTS"]) ? $queryinfo["EXPORT HINTS"] : array()))); 289 | } 290 | case "BULK IMPORT MODE": 291 | { 292 | $master = true; 293 | 294 | if ($queryinfo) 295 | { 296 | $sql = array( 297 | "PRAGMA synchronous=OFF", 298 | "PRAGMA journal_mode=MEMORY", 299 | "PRAGMA temp_store=MEMORY", 300 | ); 301 | 302 | $opts = array( 303 | array(), 304 | array(), 305 | array(), 306 | ); 307 | } 308 | else 309 | { 310 | $sql = array( 311 | "PRAGMA synchronous=NORMAL", 312 | "PRAGMA journal_mode=DELETE", 313 | "PRAGMA temp_store=DEFAULT", 314 | ); 315 | 316 | $opts = array( 317 | array(), 318 | array(), 319 | array(), 320 | ); 321 | } 322 | 323 | return array("success" => true); 324 | } 325 | } 326 | 327 | return array("success" => false, "error" => CSDB::DB_Translate("Unknown query command '%s'.", $cmd), "errorcode" => "unknown_query_command"); 328 | } 329 | 330 | public function RunRowFilter(&$row, &$filteropts, &$fetchnext) 331 | { 332 | switch ($filteropts["mode"]) 333 | { 334 | case "SHOW DATABASES": 335 | { 336 | if ($row !== false) 337 | { 338 | $name = $row->name; 339 | $pos = strpos($name, "__"); 340 | if ($pos === false) $name = ""; 341 | else $name = substr($name, 0, $pos); 342 | 343 | if (isset($filteropts["names"][$name])) $fetchnext = true; 344 | else 345 | { 346 | $filteropts["names"][$name] = true; 347 | $row->name = $name; 348 | } 349 | } 350 | 351 | break; 352 | } 353 | case "SHOW TABLES": 354 | { 355 | if ($row !== false) 356 | { 357 | $dbprefix = (isset($filteropts["queryinfo"]["FROM"]) ? $this->GetDBPrefix($filteropts["queryinfo"]["FROM"]) : $this->dbprefix); 358 | $name = $row->name; 359 | 360 | if ($dbprefix == "" && strpos($name, "__") !== false) $fetchnext = true; 361 | else if ($dbprefix != "" && strtolower(substr($name, 0, strlen($dbprefix))) !== strtolower($dbprefix)) $fetchnext = true; 362 | else 363 | { 364 | $row->name = substr($name, strlen($dbprefix)); 365 | $row->tbl_name = $row->name; 366 | } 367 | } 368 | 369 | break; 370 | } 371 | case "SHOW CREATE DATABASE": 372 | { 373 | if ($row !== false) 374 | { 375 | $name = $row->name; 376 | $pos = strpos($name, "__"); 377 | if ($pos === false) $name = ""; 378 | else $name = substr($name, 0, $pos); 379 | 380 | if ($filteropts["output"] || $name == "" || $name !== $filteropts["queryinfo"][0]) $fetchnext = true; 381 | else 382 | { 383 | $row->name = $name; 384 | $row->cmd = "CREATE DATABASE"; 385 | $row->opts = array($name); 386 | 387 | $filteropts["output"] = true; 388 | } 389 | } 390 | 391 | break; 392 | } 393 | case "SHOW CREATE TABLE": 394 | { 395 | if ($row !== false) 396 | { 397 | if ($filteropts["output"]) $fetchnext = true; 398 | else 399 | { 400 | $dbprefix = ($row->tbl_type == "temp" ? "temp." : ""); 401 | 402 | // Process columns according to: http://sqlite.org/datatype3.html 403 | $cols = array(); 404 | $result2 = $this->Query("PRAGMA", $dbprefix . "table_info(" . $this->QuoteIdentifier($this->dbprefix . $filteropts["queryinfo"][0]) . ")"); 405 | while ($row2 = $result2->NextRow()) 406 | { 407 | if (isset($filteropts["hints"]) && isset($filteropts["hints"][$row2->name])) $col = $filteropts["hints"][$row2->name]; 408 | else 409 | { 410 | if (stripos($row2->type, "INT") !== false) $col = array("INTEGER", 8); 411 | else if (stripos($row2->type, "CHAR") !== false || stripos($row2->type, "CLOB") !== false || stripos($row2->type, "TEXT") !== false) 412 | { 413 | $col = array("STRING", 4); 414 | 415 | $pos = strpos($row2->type, "("); 416 | if ($pos !== false) 417 | { 418 | $pos2 = strpos($row2->type, ")", $pos); 419 | if ($pos2 !== false) 420 | { 421 | $num = (int)substr($row2->type, $pos + 1, $pos2 - $pos - 1); 422 | if ($num > 0 && $num < 256) $col = array("STRING", 1, $num); 423 | } 424 | } 425 | } 426 | else if (stripos($row2->type, "BLOB") !== false || $row2->type === "") $col = array("BINARY", 4); 427 | else if (stripos($row2->type, "REAL") !== false || stripos($row2->type, "FLOA") !== false || stripos($row2->type, "DOUB") !== false) $col = array("FLOAT", 8); 428 | else 429 | { 430 | $col = array("NUMERIC"); 431 | $pos = strpos($row2->type, "("); 432 | if ($pos !== false) 433 | { 434 | $pos2 = strpos($row2->type, ")", $pos); 435 | if ($pos2 !== false) 436 | { 437 | $nums = explode(",", substr($row2->type, $pos + 1, $pos2 - $pos - 1)); 438 | $num = (int)trim($nums[0]); 439 | if ($num > 0) $col[] = $num; 440 | if (count($nums) > 1) 441 | { 442 | $num = (int)trim($nums[1]); 443 | if ($num > 0) $col[] = $num; 444 | } 445 | } 446 | } 447 | } 448 | 449 | if ($row2->notnull > 0) $col["NOT NULL"] = true; 450 | if (isset($row2->dflt_value)) $col["DEFAULT"] = $row2->dflt_value; 451 | if ($row2->pk > 0) 452 | { 453 | $col["PRIMARY KEY"] = true; 454 | if ($col[0] == "INTEGER") $col["AUTO INCREMENT"] = true; 455 | } 456 | } 457 | 458 | $cols[$row2->name] = $col; 459 | } 460 | 461 | // Process indexes. 462 | $keys = array(); 463 | $result2 = $this->Query("PRAGMA", $dbprefix . "index_list(" . $this->QuoteIdentifier($this->dbprefix . $filteropts["queryinfo"][0]) . ")"); 464 | while ($row2 = $result2->NextRow()) 465 | { 466 | $cols2 = array(); 467 | $result3 = $this->Query("PRAGMA", $dbprefix . "index_info(" . $this->QuoteIdentifier($row2->name) . ")"); 468 | while ($row3 = $result3->NextRow()) 469 | { 470 | $cols2[] = $row3->name; 471 | } 472 | 473 | if (substr($row2->name, 0, strlen($this->dbprefix . $filteropts["queryinfo"][0]) + 2) == $this->dbprefix . $filteropts["queryinfo"][0] . "__") $row2->name = substr($row2->name, strlen($this->dbprefix . $filteropts["queryinfo"][0]) + 2); 474 | 475 | $keys[] = array(($row2->unique > 0 ? "UNIQUE" : "KEY"), $cols2, "NAME" => $row2->name); 476 | } 477 | 478 | // Process foreign keys? It would be nice to see some output of foreign_key_list(). 479 | 480 | $row->cmd = "CREATE TABLE"; 481 | $row->opts = array($filteropts["queryinfo"][0], $cols); 482 | if (count($keys)) $row->opts[] = $keys; 483 | if ($row->tbl_type == "temp") $row->opts["TEMPORARY"] = true; 484 | $row->opts["CHARACTER SET"] = "utf8"; 485 | 486 | $filteropts["output"] = true; 487 | } 488 | } 489 | 490 | break; 491 | } 492 | } 493 | 494 | if (!$fetchnext) parent::RunRowFilter($row, $filteropts, $fetchnext); 495 | } 496 | 497 | protected function ProcessColumnDefinition($info) 498 | { 499 | // SQLite is seriously limited here with just four data types. 500 | $sql = ""; 501 | $type = strtoupper($info[0]); 502 | switch ($type) 503 | { 504 | case "INTEGER": $sql .= " INTEGER"; break; 505 | case "FLOAT": $sql .= " REAL"; break; 506 | case "DECIMAL": 507 | { 508 | $sql .= " NUMERIC"; 509 | if (isset($info[1])) $sql .= "(" . $info[1] . (isset($info[2]) ? ", " . $info[2] : "") . ")"; 510 | 511 | break; 512 | } 513 | case "STRING": 514 | { 515 | $varbytes = $info[1]; 516 | if ($varbytes == 1) $sql .= " TEXT(" . $info[2] . ")"; 517 | else $sql .= " TEXT"; 518 | 519 | break; 520 | } 521 | case "BINARY": $sql .= " BLOB"; break; 522 | case "DATE": $sql .= " TEXT"; break; 523 | case "TIME": $sql .= " TEXT"; break; 524 | case "DATETIME": $sql .= " TEXT"; break; 525 | case "BOOLEAN": $sql .= " INTEGER"; break; 526 | default: return array("success" => false, "error" => CSDB::DB_Translate("Unknown column type '%s'.", $type), "errorcode" => "unknown_column_type"); 527 | } 528 | 529 | if (isset($info["NOT NULL"]) && $info["NOT NULL"]) $sql .= " NOT NULL"; 530 | if (isset($info["DEFAULT"])) $sql .= " DEFAULT " . $this->Quote($info["DEFAULT"]); 531 | if (isset($info["PRIMARY KEY"]) && $info["PRIMARY KEY"]) 532 | { 533 | $sql .= " PRIMARY KEY"; 534 | if (isset($info["AUTO INCREMENT"]) && $info["AUTO INCREMENT"]) $sql .= " AUTOINCREMENT"; 535 | } 536 | if (isset($info["UNIQUE KEY"]) && $info["UNIQUE KEY"]) $sql .= " UNIQUE"; 537 | if (isset($info["REFERENCES"])) $sql .= " REFERENCES " . $this->ProcessReferenceDefinition($info["REFERENCES"]); 538 | 539 | return array("success" => true, "sql" => $sql); 540 | } 541 | 542 | protected function ProcessKeyDefinition($info) 543 | { 544 | $sql = ""; 545 | if (isset($info["CONSTRAINT"])) $sql .= "CONSTRAINT " . $info["CONSTRAINT"] . " "; 546 | $type = strtoupper($info[0]); 547 | foreach ($info[1] as $num => $field) $info[1][$num] = $this->QuoteIdentifier($field); 548 | switch ($type) 549 | { 550 | case "PRIMARY": 551 | { 552 | $sql .= "PRIMARY KEY"; 553 | $sql .= " (" . implode(", ", $info[1]) . ")"; 554 | 555 | break; 556 | } 557 | case "KEY": 558 | { 559 | // SQLite CREATE TABLE doesn't support regular KEY indexes, but ALTER TABLE does. 560 | 561 | break; 562 | } 563 | case "UNIQUE": 564 | { 565 | $sql .= "UNIQUE"; 566 | $sql .= " (" . implode(", ", $info[1]) . ")"; 567 | 568 | break; 569 | } 570 | case "FULLTEXT": 571 | { 572 | // SQLite doesn't support FULLTEXT indexes. 573 | break; 574 | } 575 | case "FOREIGN": 576 | { 577 | $sql .= "FOREIGN KEY"; 578 | $sql .= " (" . implode(", ", $info[1]) . ")"; 579 | $sql .= " REFERENCES " . $this->ProcessReferenceDefinition($info[2]); 580 | 581 | break; 582 | } 583 | default: return array("success" => false, "error" => CSDB::DB_Translate("Unknown key type '%s'.", $type), "errorcode" => "unknown_key_type");; 584 | } 585 | 586 | return array("success" => true, "sql" => $sql); 587 | } 588 | 589 | private function GetDBPrefix($str) 590 | { 591 | $str = preg_replace('/\s+/', "_", trim(str_replace("_", " ", $str))); 592 | 593 | return ($str != "" ? $str . "__" : ""); 594 | } 595 | } 596 | ?> -------------------------------------------------------------------------------- /install.php: -------------------------------------------------------------------------------- 1 | GetBytes(64); 20 | } 21 | 22 | $ff = new FlexForms(); 23 | $ff->SetSecretKey($_SESSION["cft_install"]["secret"]); 24 | $ff->CheckSecurityToken("action"); 25 | 26 | function CFT_GetSupportedDatabases() 27 | { 28 | $result = array( 29 | "sqlite" => array("production" => true, "login" => false, "replication" => false, "default_dsn" => "@PATH@/sqlite_@RANDOM@.db"), 30 | "mysql" => array("production" => true, "login" => true, "replication" => true, "default_dsn" => "host=127.0.0.1"), 31 | "pgsql" => array("production" => true, "login" => true, "replication" => true, "default_dsn" => "host=localhost"), 32 | "oci" => array("production" => false, "login" => true, "replication" => true, "default_dsn" => "dbname=//localhost/ORCL") 33 | ); 34 | 35 | return $result; 36 | } 37 | 38 | function OutputHeader($title) 39 | { 40 | global $ff; 41 | 42 | header("Content-type: text/html; charset=UTF-8"); 43 | 44 | ?> 45 | 46 | 47 | 48 | <?=htmlspecialchars($title)?> | Cool File Transfer Installer 49 | 50 | OutputJQuery(); 52 | ?> 53 | 61 | 62 | 63 |
64 |
65 |

66 | 72 |
73 |
76 | 77 | 78 | OutputMessage("success", "The installation completed successfully."); 93 | 94 | ?> 95 |

Cool File Transfer was successfully installed. You now have a nifty browser-based live file transfer tool at your disposal.

96 | 97 |

What's next? Secure the root Cool File Transfer directory so that the web server can't write to it. Then, integrate Cool File Transfer into your web application by incorporating 'cft.php' (the CoolFileTransfer class) into your application's output.

98 | 99 |

Important configuration information is stored in the generated 'config.php' file.

100 | GenerateString(), $dsn); 115 | if (strpos($dsn, "@PATH@") !== false) 116 | { 117 | @mkdir("db", 0775); 118 | @file_put_contents("db/index.html", ""); 119 | } 120 | $dsn = str_replace("@PATH@", str_replace("\\", "/", dirname(__FILE__)) . "/db", $dsn); 121 | 122 | $_SESSION["cft_install"]["db_dsn"] = $dsn; 123 | } 124 | 125 | if (!isset($_SESSION["cft_install"]["db_user"])) $_SESSION["cft_install"]["db_user"] = ""; 126 | if (!isset($_SESSION["cft_install"]["db_pass"])) $_SESSION["cft_install"]["db_pass"] = ""; 127 | if (!isset($_SESSION["cft_install"]["db_name"])) $_SESSION["cft_install"]["db_name"] = "cft"; 128 | if (!isset($_SESSION["cft_install"]["db_table_prefix"])) $_SESSION["cft_install"]["db_table_prefix"] = "cft_"; 129 | if (!isset($_SESSION["cft_install"]["db_master_dsn"])) $_SESSION["cft_install"]["db_master_dsn"] = ""; 130 | if (!isset($_SESSION["cft_install"]["db_master_user"])) $_SESSION["cft_install"]["db_master_user"] = ""; 131 | if (!isset($_SESSION["cft_install"]["db_master_pass"])) $_SESSION["cft_install"]["db_master_pass"] = ""; 132 | 133 | $message = ""; 134 | if (isset($_REQUEST["db_dsn"])) 135 | { 136 | // Test database access. 137 | $_REQUEST["db_name"] = preg_replace('/[^a-z]/', "_", strtolower($_REQUEST["db_name"])); 138 | if (!isset($databases[$database])) $errors["msg"] = "Invalid database selected. Go back and try again."; 139 | else if ($_REQUEST["db_dsn"] == "") 140 | { 141 | $errors["msg"] = "Please correct the errors below and try again."; 142 | $errors["db_dsn"] = "Please fill in this field with a valid DSN."; 143 | } 144 | else if ($_REQUEST["db_name"] == "") 145 | { 146 | $errors["msg"] = "Please correct the errors below and try again."; 147 | $errors["db_name"] = "Please fill in this field."; 148 | } 149 | else 150 | { 151 | require_once "support/csdb/db_" . $database . ".php"; 152 | 153 | $classname = "CSDB_" . $database; 154 | 155 | try 156 | { 157 | $db = new $classname(); 158 | $db->SetDebug(true); 159 | $db->Connect($database . ":" . $_REQUEST["db_dsn"], ($databases[$database]["login"] ? $_REQUEST["db_user"] : false), ($databases[$database]["login"] ? $_REQUEST["db_pass"] : false)); 160 | $message = "Successfully connected to the server.
Running " . htmlspecialchars($db->GetDisplayName() . " " . $db->GetVersion()) . "
"; 161 | unset($db); 162 | } 163 | catch (Exception $e) 164 | { 165 | $errors["msg"] = "Database connection attempt failed.
" . htmlspecialchars($e->getMessage()); 166 | } 167 | 168 | if ($databases[$database]["replication"] && $_REQUEST["db_master_dsn"] != "") 169 | { 170 | try 171 | { 172 | $db = new $classname(); 173 | $db->SetDebug(true); 174 | $db->Connect($database . ":" . $_REQUEST["db_master_dsn"], ($databases[$type]["login"] ? $_REQUEST["db_master_user"] : false), ($databases[$type]["login"] ? $_REQUEST["db_master_pass"] : false)); 175 | $message .= "Successfully connected to the server.
Running " . htmlspecialchars($db->GetDisplayName() . " " . $db->GetVersion()) . "
"; 176 | unset($db); 177 | } 178 | catch (Exception $e) 179 | { 180 | $errors["msg"] = "Database connection attempt failed.
" . htmlspecialchars($e->getMessage()); 181 | } 182 | } 183 | } 184 | 185 | if (count($errors)) $errors["msg"] = "Please correct the errors below and try again."; 186 | else if (isset($_REQUEST["next"])) 187 | { 188 | $_SESSION["cft_install"]["db_dsn"] = $_REQUEST["db_dsn"]; 189 | if ($databases[$database]["login"]) $_SESSION["cft_install"]["db_user"] = $_REQUEST["db_user"]; 190 | if ($databases[$database]["login"]) $_SESSION["cft_install"]["db_pass"] = $_REQUEST["db_pass"]; 191 | $_SESSION["cft_install"]["db_name"] = $_REQUEST["db_name"]; 192 | $_SESSION["cft_install"]["db_table_prefix"] = $_REQUEST["db_table_prefix"]; 193 | if ($databases[$database]["replication"]) 194 | { 195 | $_SESSION["cft_install"]["db_master_dsn"] = $_REQUEST["db_master_dsn"]; 196 | if ($databases[$database]["login"]) $_SESSION["cft_install"]["db_master_user"] = $_REQUEST["db_master_user"]; 197 | if ($databases[$database]["login"]) $_SESSION["cft_install"]["db_master_pass"] = $_REQUEST["db_master_pass"]; 198 | } 199 | 200 | // Generate the configuration. 201 | if (!count($errors)) 202 | { 203 | $config = array( 204 | "rootpath" => str_replace("\\", "/", dirname(__FILE__)), 205 | "rooturl" => dirname($ff->GetFullRequestURLBase()) . "/", 206 | "db_select" => $_SESSION["cft_install"]["db_select"], 207 | "db_dsn" => $_SESSION["cft_install"]["db_dsn"], 208 | "db_login" => $databases[$database]["login"], 209 | "db_user" => $_SESSION["cft_install"]["db_user"], 210 | "db_pass" => $_SESSION["cft_install"]["db_pass"], 211 | "db_name" => $_SESSION["cft_install"]["db_name"], 212 | "db_table_prefix" => $_SESSION["cft_install"]["db_table_prefix"], 213 | "db_master_dsn" => $_SESSION["cft_install"]["db_master_dsn"], 214 | "db_master_user" => $_SESSION["cft_install"]["db_master_user"], 215 | "db_master_pass" => $_SESSION["cft_install"]["db_master_pass"], 216 | ); 217 | } 218 | 219 | // Database setup. 220 | if (!count($errors)) 221 | { 222 | require_once "support/csdb/db_" . $config["db_select"] . ".php"; 223 | 224 | $dbclassname = "CSDB_" . $config["db_select"]; 225 | 226 | try 227 | { 228 | $db = new $dbclassname($config["db_select"] . ":" . $config["db_dsn"], ($config["db_login"] ? $config["db_user"] : false), ($config["db_login"] ? $config["db_pass"] : false)); 229 | if ($config["db_master_dsn"] != "") $db->SetMaster($config["db_select"] . ":" . $config["db_master_dsn"], ($config["db_login"] ? $config["db_master_user"] : false), ($config["db_login"] ? $config["db_master_pass"] : false)); 230 | } 231 | catch (Exception $e) 232 | { 233 | $errors["msg"] = "Database connection failed. " . htmlspecialchars($e->getMessage()); 234 | } 235 | } 236 | 237 | if (!count($errors)) 238 | { 239 | try 240 | { 241 | $db->GetDisplayName(); 242 | $db->GetVersion(); 243 | } 244 | catch (Exception $e) 245 | { 246 | $errors["msg"] = "Database connection succeeded but unable to get server version. " . htmlspecialchars($e->getMessage()); 247 | } 248 | } 249 | 250 | // Create/Use the database. 251 | if (!count($errors)) 252 | { 253 | try 254 | { 255 | $db->Query("USE", $config["db_name"]); 256 | } 257 | catch (Exception $e) 258 | { 259 | try 260 | { 261 | $db->Query("CREATE DATABASE", array($config["db_name"], "CHARACTER SET" => "utf8", "COLLATE" => "utf8_general_ci")); 262 | $db->Query("USE", $config["db_name"]); 263 | } 264 | catch (Exception $e) 265 | { 266 | $errors["msg"] = "Unable to create/use database '" . htmlspecialchars($config["db_name"]) . "'. " . htmlspecialchars($e->getMessage()); 267 | } 268 | } 269 | } 270 | 271 | // Create database tables. 272 | if (!count($errors)) 273 | { 274 | $dbprefix = $config["db_table_prefix"]; 275 | $cft_db_files = $dbprefix . "files"; 276 | try 277 | { 278 | $filesfound = $db->TableExists($cft_db_files); 279 | } 280 | catch (Exception $e) 281 | { 282 | $errors["msg"] = "Unable to determine the existence of a database table. " . htmlspecialchars($e->getMessage()); 283 | } 284 | } 285 | 286 | if (!count($errors) && !$filesfound) 287 | { 288 | try 289 | { 290 | $db->Query("CREATE TABLE", array($cft_db_files, array( 291 | "id" => array("INTEGER", 8, "UNSIGNED" => true, "NOT NULL" => true, "PRIMARY KEY" => true, "AUTO INCREMENT" => true), 292 | "lastrequest" => array("INTEGER", 8, "UNSIGNED" => true, "NOT NULL" => true), 293 | "srcuser" => array("STRING", 1, 255, "NOT NULL" => true), 294 | "destuser" => array("STRING", 1, 255, "NOT NULL" => true), 295 | "sendtoken" => array("STRING", 1, 64, "NOT NULL" => true), 296 | "recvtoken" => array("STRING", 1, 64, "NOT NULL" => true), 297 | "filename" => array("STRING", 1, 255, "NOT NULL" => true), 298 | "filesize" => array("INTEGER", 8, "UNSIGNED" => true, "NOT NULL" => true), 299 | "port" => array("INTEGER", 4, "NOT NULL" => true), 300 | ), 301 | array( 302 | array("KEY", array("lastrequest"), "NAME" => "lastrequest"), 303 | array("KEY", array("destuser"), "NAME" => "destuser"), 304 | ))); 305 | } 306 | catch (Exception $e) 307 | { 308 | $errors["msg"] = "Unable to create the database table '" . htmlspecialchars($cft_db_files) . "'. " . htmlspecialchars($e->getMessage()); 309 | } 310 | } 311 | 312 | // Write the configuration to disk. 313 | if (!count($errors)) 314 | { 315 | $data = "<" . "?php\n"; 316 | $data .= "\$config = " . var_export($config, true) . ";\n"; 317 | $data .= "?" . ">"; 318 | 319 | $filename = $config["rootpath"] . "/config.php"; 320 | if (@file_put_contents($filename, $data) === false) $errors["msg"] = "Unable to write configuration to '" . htmlspecialchars($filename) . "'."; 321 | else if (function_exists("opcache_invalidate")) @opcache_invalidate($filename, true); 322 | } 323 | 324 | if (!count($errors)) 325 | { 326 | $_SESSION["cft_installed"] = true; 327 | 328 | header("Location: " . $ff->GetFullRequestURLBase() . "?action=done&sec_t=" . $ff->CreateSecurityToken("done")); 329 | 330 | exit(); 331 | } 332 | } 333 | } 334 | 335 | OutputHeader("Step 3: Configure Database"); 336 | 337 | if (isset($databases[$database])) 338 | { 339 | if (count($errors)) $ff->OutputMessage("error", $errors["msg"]); 340 | else if ($message != "") $ff->OutputMessage("info", $message); 341 | 342 | $contentopts = array( 343 | "fields" => array( 344 | array( 345 | "title" => "* DSN options", 346 | "type" => "text", 347 | "name" => "db_dsn", 348 | "default" => $_SESSION["cft_install"]["db_dsn"], 349 | "desc" => "The initial connection string to connect to the database server. Options are driver specific. Usually takes the form of: host=ipaddr_or_hostname[;port=portnum] (e.g. host=127.0.0.1;port=3306)" 350 | ), 351 | array( 352 | "use" => $databases[$database]["login"], 353 | "title" => "Username", 354 | "type" => "text", 355 | "name" => "db_user", 356 | "default" => $_SESSION["cft_install"]["db_user"], 357 | "desc" => "The username to use to log into the database server." 358 | ), 359 | array( 360 | "use" => $databases[$database]["login"], 361 | "title" => "Password", 362 | "type" => "password", 363 | "name" => "db_pass", 364 | "default" => $_SESSION["cft_install"]["db_pass"], 365 | "desc" => "The password to use to log into the database server." 366 | ), 367 | array( 368 | "title" => "* Database", 369 | "type" => "text", 370 | "name" => "db_name", 371 | "default" => $_SESSION["cft_install"]["db_name"], 372 | "desc" => "The database to select after connecting into the database server." 373 | ), 374 | array( 375 | "title" => "Table prefix", 376 | "type" => "text", 377 | "name" => "db_table_prefix", 378 | "default" => $_SESSION["cft_install"]["db_table_prefix"], 379 | "desc" => "The prefix to use for table names in the selected database." 380 | ), 381 | array( 382 | "use" => $databases[$database]["replication"], 383 | "title" => "Replication master - DSN options", 384 | "type" => "text", 385 | "name" => "db_master_dsn", 386 | "default" => $_SESSION["cft_install"]["db_master_dsn"], 387 | "desc" => "The connection string to connect to the master database server. Leave blank if you aren't using database replication! Options are driver specific. Usually takes the form of: host=ipaddr_or_hostname[;port=portnum] (e.g. host=somehost;port=3306)" 388 | ), 389 | array( 390 | "use" => $databases[$database]["replication"] && $databases[$database]["login"], 391 | "title" => "Replication master - Username", 392 | "type" => "text", 393 | "name" => "db_master_user", 394 | "default" => $_SESSION["cft_install"]["db_master_user"], 395 | "desc" => "The username to use to log into the replication master database server." 396 | ), 397 | array( 398 | "use" => $databases[$database]["replication"] && $databases[$database]["login"], 399 | "title" => "Replication master - Password", 400 | "type" => "password", 401 | "name" => "db_master_pass", 402 | "default" => $_SESSION["cft_install"]["db_master_pass"], 403 | "desc" => "The password to use to log into the replication master database server." 404 | ) 405 | ), 406 | "submit" => array("test" => "Test Connection", "next" => "Install") 407 | ); 408 | 409 | $ff->Generate($contentopts, $errors); 410 | } 411 | else 412 | { 413 | $ff->OutputMessage("error", "Invalid database selected. Go back and try again."); 414 | } 415 | 416 | OutputFooter(); 417 | } 418 | else if (isset($_REQUEST["action"]) && $_REQUEST["action"] == "step2") 419 | { 420 | $databases2 = array(); 421 | $databases = CFT_GetSupportedDatabases(); 422 | foreach ($databases as $database => $info) 423 | { 424 | require_once "support/csdb/db_" . $database . ".php"; 425 | 426 | try 427 | { 428 | $classname = "CSDB_" . $database; 429 | $db = new $classname(); 430 | if ($db->IsAvailable() !== false) $databases2[$database] = $db->GetDisplayName() . (!$info["production"] ? " [NOT for production use]" : ""); 431 | } 432 | catch (Exception $e) 433 | { 434 | } 435 | } 436 | 437 | if (!isset($_SESSION["cft_install"]["db_select"])) $_SESSION["cft_install"]["db_select"] = ""; 438 | 439 | if (isset($_REQUEST["db_select"])) 440 | { 441 | if (!isset($databases[$_REQUEST["db_select"]])) $errors["db_select"] = "Please select a database. If none are available, make sure at least one supported PDO database driver is enabled in your PHP installation."; 442 | 443 | if (!count($errors)) 444 | { 445 | $_SESSION["cft_install"]["db_select"] = $_REQUEST["db_select"]; 446 | unset($_SESSION["cft_install"]["db_dsn"]); 447 | 448 | header("Location: " . $ff->GetFullRequestURLBase() . "?action=step3&sec_t=" . $ff->CreateSecurityToken("step3")); 449 | 450 | exit(); 451 | } 452 | } 453 | 454 | OutputHeader("Step 2: Select Database"); 455 | 456 | if (count($errors)) $ff->OutputMessage("error", "Please correct the errors below to continue."); 457 | 458 | $contentopts = array( 459 | "fields" => array( 460 | array( 461 | "title" => "* Available databases", 462 | "type" => "select", 463 | "name" => "db_select", 464 | "options" => $databases2, 465 | "default" => $_SESSION["cft_install"]["db_select"], 466 | "desc" => (isset($databases2["sqlite"]) ? "SQLite should only be used for smaller installations." : "") 467 | ), 468 | ), 469 | "submit" => "Next Step" 470 | ); 471 | 472 | $ff->Generate($contentopts, $errors); 473 | 474 | OutputFooter(); 475 | } 476 | else if (isset($_REQUEST["action"]) && $_REQUEST["action"] == "step1") 477 | { 478 | if (isset($_REQUEST["submit"])) 479 | { 480 | header("Location: " . $ff->GetFullRequestURLBase() . "?action=step2&sec_t=" . $ff->CreateSecurityToken("step2")); 481 | 482 | exit(); 483 | } 484 | 485 | OutputHeader("Step 1: Environment Check"); 486 | 487 | if ((double)phpversion() < 5.6) $errors["phpversion"] = "The server is running PHP " . phpversion() . ". The installation may succeed but the software will not function. Running outdated versions of PHP poses a serious website security risk. Please contact your system administrator to upgrade your PHP installation."; 488 | 489 | if (file_put_contents("test.dat", "a") === false) $errors["createfiles"] = "Unable to create 'test.dat'. Running chmod 777 on the directory may fix the problem."; 490 | else if (!unlink("test.dat")) $errors["createfiles"] = "Unable to delete 'test.dat'. Running chmod 777 on the directory may fix the problem."; 491 | 492 | if (!isset($_SERVER["REQUEST_URI"])) $errors["requesturi"] = "The server does not appear to support this feature. The installation may fail and the site might not work."; 493 | 494 | if (!$ff->IsSSLRequest()) $errors["ssl"] = "This software should be installed over SSL. SSL/TLS certificates can be obtained for free. Proceed only if this major security risk is acceptable."; 495 | 496 | try 497 | { 498 | $rng = new CSPRNG(true); 499 | } 500 | catch (Exception $e) 501 | { 502 | $error["csprng"] = "Please ask your system administrator to install a supported PHP version (e.g. PHP 7 or later) or extension (e.g. OpenSSL)."; 503 | } 504 | 505 | // Test server creation. 506 | if (!function_exists("stream_socket_server")) $error["startserver"] = "PHP is missing a critical stream socket server functions. Application will not function."; 507 | else 508 | { 509 | $fp = @stream_socket_server("tcp://127.0.0.1:0", $errornum, $errorstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN); 510 | if ($fp === false) $error["startserver"] = "PHP is unable to bind a port on localhost. Either the OS or PHP is preventing a successful bind/listen."; 511 | else @fclose($fp); 512 | } 513 | 514 | ?> 515 |

The current PHP environment has been evaluated against the minimum system requirements. Any issues found are noted below. After correcting any issues, reload the page.

516 | array( 520 | array( 521 | "title" => "PHP 5.6.x or later", 522 | "type" => "static", 523 | "name" => "phpversion", 524 | "value" => (isset($errors["phpversion"]) ? "No. Test failed." : "Yes. Test passed.") 525 | ), 526 | array( 527 | "title" => "Able to create files in ./", 528 | "type" => "static", 529 | "name" => "createfiles", 530 | "value" => (isset($errors["createfiles"]) ? "No. Test failed." : "Yes. Test passed.") 531 | ), 532 | array( 533 | "title" => "\$_SERVER[\"REQUEST_URI\"] supported", 534 | "type" => "static", 535 | "name" => "requesturi", 536 | "value" => (isset($errors["requesturi"]) ? "No. Test failed." : "Yes. Test passed.") 537 | ), 538 | array( 539 | "title" => "Installation over SSL", 540 | "type" => "static", 541 | "name" => "ssl", 542 | "value" => (isset($errors["ssl"]) ? "No. Test failed." : "Yes. Test passed.") 543 | ), 544 | array( 545 | "title" => "Crypto-safe CSPRNG available", 546 | "type" => "static", 547 | "name" => "csprng", 548 | "value" => (isset($errors["csprng"]) ? "No. Test failed." : "Yes. Test passed.") 549 | ), 550 | array( 551 | "title" => "Able to start a TCP/IP socket server", 552 | "type" => "static", 553 | "name" => "startserver", 554 | "value" => (isset($errors["startserver"]) ? "No. Test failed." : "Yes. Test passed.") 555 | ) 556 | ), 557 | "submit" => "Next Step", 558 | "submitname" => "submit" 559 | ); 560 | 561 | $functions = array( 562 | "stream_socket_server" => "Localhost-only, dynamic port server setup", 563 | "stream_socket_client" => "Localhost connection establishment", 564 | "json_encode" => "JSON encoding/decoding (critical!)", 565 | ); 566 | 567 | foreach ($functions as $function => $info) 568 | { 569 | if (!function_exists($function)) $errors["function|" . $function] = "The software will be unable to use " . $info . ". The installation might succeed but the product may not function at all."; 570 | 571 | $contentopts["fields"][] = array( 572 | "title" => "'" . $function . "' available", 573 | "type" => "static", 574 | "name" => "function|" . $function, 575 | "value" => (isset($errors["function|" . $function]) ? "No. Test failed." : "Yes. Test passed.") 576 | ); 577 | } 578 | 579 | $classes = array( 580 | "PDO" => "PDO database classes", 581 | ); 582 | 583 | foreach ($classes as $class => $info) 584 | { 585 | if (!class_exists($class)) $errors["class|" . $function] = "The software will be unable to use " . $info . ". The installation might succeed but the product may not function at all."; 586 | 587 | $contentopts["fields"][] = array( 588 | "title" => "'" . $class . "' available", 589 | "type" => "static", 590 | "name" => "class|" . $class, 591 | "value" => (isset($errors["class|" . $class]) ? "No. Test failed." : "Yes. Test passed.") 592 | ); 593 | } 594 | 595 | $ff->Generate($contentopts, $errors); 596 | 597 | OutputFooter(); 598 | } 599 | else 600 | { 601 | OutputHeader("Introduction"); 602 | 603 | foreach ($_GET as $key => $val) 604 | { 605 | if (!isset($_SESSION["cft_install"][$key])) $_SESSION["cft_install"][$key] = (string)$val; 606 | } 607 | 608 | ?> 609 |

You are about to install Cool File Transfer: A nifty browser-based live file transfer tool.

610 | 611 |

">Start installation

612 | -------------------------------------------------------------------------------- /support/fancy-file-uploader/jquery.ui.widget.js: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.12.1+0b7246b6eeadfa9e2696e22f3230f6452f8129dc - 2020-02-20 2 | * http://jqueryui.com 3 | * Includes: widget.js 4 | * Copyright jQuery Foundation and other contributors; Licensed MIT */ 5 | 6 | /* global define, require */ 7 | /* eslint-disable no-param-reassign, new-cap, jsdoc/require-jsdoc */ 8 | 9 | (function (factory) { 10 | 'use strict'; 11 | if (typeof define === 'function' && define.amd) { 12 | // AMD. Register as an anonymous module. 13 | define(['jquery'], factory); 14 | } else if (typeof exports === 'object') { 15 | // Node/CommonJS 16 | factory(require('jquery')); 17 | } else { 18 | // Browser globals 19 | factory(window.jQuery); 20 | } 21 | })(function ($) { 22 | ('use strict'); 23 | 24 | $.ui = $.ui || {}; 25 | 26 | $.ui.version = '1.12.1'; 27 | 28 | /*! 29 | * jQuery UI Widget 1.12.1 30 | * http://jqueryui.com 31 | * 32 | * Copyright jQuery Foundation and other contributors 33 | * Released under the MIT license. 34 | * http://jquery.org/license 35 | */ 36 | 37 | //>>label: Widget 38 | //>>group: Core 39 | //>>description: Provides a factory for creating stateful widgets with a common API. 40 | //>>docs: http://api.jqueryui.com/jQuery.widget/ 41 | //>>demos: http://jqueryui.com/widget/ 42 | 43 | // Support: jQuery 1.9.x or older 44 | // $.expr[ ":" ] is deprecated. 45 | if (!$.expr.pseudos) { 46 | $.expr.pseudos = $.expr[':']; 47 | } 48 | 49 | // Support: jQuery 1.11.x or older 50 | // $.unique has been renamed to $.uniqueSort 51 | if (!$.uniqueSort) { 52 | $.uniqueSort = $.unique; 53 | } 54 | 55 | var widgetUuid = 0; 56 | var widgetHasOwnProperty = Array.prototype.hasOwnProperty; 57 | var widgetSlice = Array.prototype.slice; 58 | 59 | $.cleanData = (function (orig) { 60 | return function (elems) { 61 | var events, elem, i; 62 | // eslint-disable-next-line eqeqeq 63 | for (i = 0; (elem = elems[i]) != null; i++) { 64 | // Only trigger remove when necessary to save time 65 | events = $._data(elem, 'events'); 66 | if (events && events.remove) { 67 | $(elem).triggerHandler('remove'); 68 | } 69 | } 70 | orig(elems); 71 | }; 72 | })($.cleanData); 73 | 74 | $.widget = function (name, base, prototype) { 75 | var existingConstructor, constructor, basePrototype; 76 | 77 | // ProxiedPrototype allows the provided prototype to remain unmodified 78 | // so that it can be used as a mixin for multiple widgets (#8876) 79 | var proxiedPrototype = {}; 80 | 81 | var namespace = name.split('.')[0]; 82 | name = name.split('.')[1]; 83 | var fullName = namespace + '-' + name; 84 | 85 | if (!prototype) { 86 | prototype = base; 87 | base = $.Widget; 88 | } 89 | 90 | if ($.isArray(prototype)) { 91 | prototype = $.extend.apply(null, [{}].concat(prototype)); 92 | } 93 | 94 | // Create selector for plugin 95 | $.expr.pseudos[fullName.toLowerCase()] = function (elem) { 96 | return !!$.data(elem, fullName); 97 | }; 98 | 99 | $[namespace] = $[namespace] || {}; 100 | existingConstructor = $[namespace][name]; 101 | constructor = $[namespace][name] = function (options, element) { 102 | // Allow instantiation without "new" keyword 103 | if (!this._createWidget) { 104 | return new constructor(options, element); 105 | } 106 | 107 | // Allow instantiation without initializing for simple inheritance 108 | // must use "new" keyword (the code above always passes args) 109 | if (arguments.length) { 110 | this._createWidget(options, element); 111 | } 112 | }; 113 | 114 | // Extend with the existing constructor to carry over any static properties 115 | $.extend(constructor, existingConstructor, { 116 | version: prototype.version, 117 | 118 | // Copy the object used to create the prototype in case we need to 119 | // redefine the widget later 120 | _proto: $.extend({}, prototype), 121 | 122 | // Track widgets that inherit from this widget in case this widget is 123 | // redefined after a widget inherits from it 124 | _childConstructors: [] 125 | }); 126 | 127 | basePrototype = new base(); 128 | 129 | // We need to make the options hash a property directly on the new instance 130 | // otherwise we'll modify the options hash on the prototype that we're 131 | // inheriting from 132 | basePrototype.options = $.widget.extend({}, basePrototype.options); 133 | $.each(prototype, function (prop, value) { 134 | if (!$.isFunction(value)) { 135 | proxiedPrototype[prop] = value; 136 | return; 137 | } 138 | proxiedPrototype[prop] = (function () { 139 | function _super() { 140 | return base.prototype[prop].apply(this, arguments); 141 | } 142 | 143 | function _superApply(args) { 144 | return base.prototype[prop].apply(this, args); 145 | } 146 | 147 | return function () { 148 | var __super = this._super; 149 | var __superApply = this._superApply; 150 | var returnValue; 151 | 152 | this._super = _super; 153 | this._superApply = _superApply; 154 | 155 | returnValue = value.apply(this, arguments); 156 | 157 | this._super = __super; 158 | this._superApply = __superApply; 159 | 160 | return returnValue; 161 | }; 162 | })(); 163 | }); 164 | constructor.prototype = $.widget.extend( 165 | basePrototype, 166 | { 167 | // TODO: remove support for widgetEventPrefix 168 | // always use the name + a colon as the prefix, e.g., draggable:start 169 | // don't prefix for widgets that aren't DOM-based 170 | widgetEventPrefix: existingConstructor 171 | ? basePrototype.widgetEventPrefix || name 172 | : name 173 | }, 174 | proxiedPrototype, 175 | { 176 | constructor: constructor, 177 | namespace: namespace, 178 | widgetName: name, 179 | widgetFullName: fullName 180 | } 181 | ); 182 | 183 | // If this widget is being redefined then we need to find all widgets that 184 | // are inheriting from it and redefine all of them so that they inherit from 185 | // the new version of this widget. We're essentially trying to replace one 186 | // level in the prototype chain. 187 | if (existingConstructor) { 188 | $.each(existingConstructor._childConstructors, function (i, child) { 189 | var childPrototype = child.prototype; 190 | 191 | // Redefine the child widget using the same prototype that was 192 | // originally used, but inherit from the new version of the base 193 | $.widget( 194 | childPrototype.namespace + '.' + childPrototype.widgetName, 195 | constructor, 196 | child._proto 197 | ); 198 | }); 199 | 200 | // Remove the list of existing child constructors from the old constructor 201 | // so the old child constructors can be garbage collected 202 | delete existingConstructor._childConstructors; 203 | } else { 204 | base._childConstructors.push(constructor); 205 | } 206 | 207 | $.widget.bridge(name, constructor); 208 | 209 | return constructor; 210 | }; 211 | 212 | $.widget.extend = function (target) { 213 | var input = widgetSlice.call(arguments, 1); 214 | var inputIndex = 0; 215 | var inputLength = input.length; 216 | var key; 217 | var value; 218 | 219 | for (; inputIndex < inputLength; inputIndex++) { 220 | for (key in input[inputIndex]) { 221 | value = input[inputIndex][key]; 222 | if ( 223 | widgetHasOwnProperty.call(input[inputIndex], key) && 224 | value !== undefined 225 | ) { 226 | // Clone objects 227 | if ($.isPlainObject(value)) { 228 | target[key] = $.isPlainObject(target[key]) 229 | ? $.widget.extend({}, target[key], value) 230 | : // Don't extend strings, arrays, etc. with objects 231 | $.widget.extend({}, value); 232 | 233 | // Copy everything else by reference 234 | } else { 235 | target[key] = value; 236 | } 237 | } 238 | } 239 | } 240 | return target; 241 | }; 242 | 243 | $.widget.bridge = function (name, object) { 244 | var fullName = object.prototype.widgetFullName || name; 245 | $.fn[name] = function (options) { 246 | var isMethodCall = typeof options === 'string'; 247 | var args = widgetSlice.call(arguments, 1); 248 | var returnValue = this; 249 | 250 | if (isMethodCall) { 251 | // If this is an empty collection, we need to have the instance method 252 | // return undefined instead of the jQuery instance 253 | if (!this.length && options === 'instance') { 254 | returnValue = undefined; 255 | } else { 256 | this.each(function () { 257 | var methodValue; 258 | var instance = $.data(this, fullName); 259 | 260 | if (options === 'instance') { 261 | returnValue = instance; 262 | return false; 263 | } 264 | 265 | if (!instance) { 266 | return $.error( 267 | 'cannot call methods on ' + 268 | name + 269 | ' prior to initialization; ' + 270 | "attempted to call method '" + 271 | options + 272 | "'" 273 | ); 274 | } 275 | 276 | if (!$.isFunction(instance[options]) || options.charAt(0) === '_') { 277 | return $.error( 278 | "no such method '" + 279 | options + 280 | "' for " + 281 | name + 282 | ' widget instance' 283 | ); 284 | } 285 | 286 | methodValue = instance[options].apply(instance, args); 287 | 288 | if (methodValue !== instance && methodValue !== undefined) { 289 | returnValue = 290 | methodValue && methodValue.jquery 291 | ? returnValue.pushStack(methodValue.get()) 292 | : methodValue; 293 | return false; 294 | } 295 | }); 296 | } 297 | } else { 298 | // Allow multiple hashes to be passed on init 299 | if (args.length) { 300 | options = $.widget.extend.apply(null, [options].concat(args)); 301 | } 302 | 303 | this.each(function () { 304 | var instance = $.data(this, fullName); 305 | if (instance) { 306 | instance.option(options || {}); 307 | if (instance._init) { 308 | instance._init(); 309 | } 310 | } else { 311 | $.data(this, fullName, new object(options, this)); 312 | } 313 | }); 314 | } 315 | 316 | return returnValue; 317 | }; 318 | }; 319 | 320 | $.Widget = function (/* options, element */) {}; 321 | $.Widget._childConstructors = []; 322 | 323 | $.Widget.prototype = { 324 | widgetName: 'widget', 325 | widgetEventPrefix: '', 326 | defaultElement: '
', 327 | 328 | options: { 329 | classes: {}, 330 | disabled: false, 331 | 332 | // Callbacks 333 | create: null 334 | }, 335 | 336 | _createWidget: function (options, element) { 337 | element = $(element || this.defaultElement || this)[0]; 338 | this.element = $(element); 339 | this.uuid = widgetUuid++; 340 | this.eventNamespace = '.' + this.widgetName + this.uuid; 341 | 342 | this.bindings = $(); 343 | this.hoverable = $(); 344 | this.focusable = $(); 345 | this.classesElementLookup = {}; 346 | 347 | if (element !== this) { 348 | $.data(element, this.widgetFullName, this); 349 | this._on(true, this.element, { 350 | remove: function (event) { 351 | if (event.target === element) { 352 | this.destroy(); 353 | } 354 | } 355 | }); 356 | this.document = $( 357 | element.style 358 | ? // Element within the document 359 | element.ownerDocument 360 | : // Element is window or document 361 | element.document || element 362 | ); 363 | this.window = $( 364 | this.document[0].defaultView || this.document[0].parentWindow 365 | ); 366 | } 367 | 368 | this.options = $.widget.extend( 369 | {}, 370 | this.options, 371 | this._getCreateOptions(), 372 | options 373 | ); 374 | 375 | this._create(); 376 | 377 | if (this.options.disabled) { 378 | this._setOptionDisabled(this.options.disabled); 379 | } 380 | 381 | this._trigger('create', null, this._getCreateEventData()); 382 | this._init(); 383 | }, 384 | 385 | _getCreateOptions: function () { 386 | return {}; 387 | }, 388 | 389 | _getCreateEventData: $.noop, 390 | 391 | _create: $.noop, 392 | 393 | _init: $.noop, 394 | 395 | destroy: function () { 396 | var that = this; 397 | 398 | this._destroy(); 399 | $.each(this.classesElementLookup, function (key, value) { 400 | that._removeClass(value, key); 401 | }); 402 | 403 | // We can probably remove the unbind calls in 2.0 404 | // all event bindings should go through this._on() 405 | this.element.off(this.eventNamespace).removeData(this.widgetFullName); 406 | this.widget().off(this.eventNamespace).removeAttr('aria-disabled'); 407 | 408 | // Clean up events and states 409 | this.bindings.off(this.eventNamespace); 410 | }, 411 | 412 | _destroy: $.noop, 413 | 414 | widget: function () { 415 | return this.element; 416 | }, 417 | 418 | option: function (key, value) { 419 | var options = key; 420 | var parts; 421 | var curOption; 422 | var i; 423 | 424 | if (arguments.length === 0) { 425 | // Don't return a reference to the internal hash 426 | return $.widget.extend({}, this.options); 427 | } 428 | 429 | if (typeof key === 'string') { 430 | // Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } 431 | options = {}; 432 | parts = key.split('.'); 433 | key = parts.shift(); 434 | if (parts.length) { 435 | curOption = options[key] = $.widget.extend({}, this.options[key]); 436 | for (i = 0; i < parts.length - 1; i++) { 437 | curOption[parts[i]] = curOption[parts[i]] || {}; 438 | curOption = curOption[parts[i]]; 439 | } 440 | key = parts.pop(); 441 | if (arguments.length === 1) { 442 | return curOption[key] === undefined ? null : curOption[key]; 443 | } 444 | curOption[key] = value; 445 | } else { 446 | if (arguments.length === 1) { 447 | return this.options[key] === undefined ? null : this.options[key]; 448 | } 449 | options[key] = value; 450 | } 451 | } 452 | 453 | this._setOptions(options); 454 | 455 | return this; 456 | }, 457 | 458 | _setOptions: function (options) { 459 | var key; 460 | 461 | for (key in options) { 462 | this._setOption(key, options[key]); 463 | } 464 | 465 | return this; 466 | }, 467 | 468 | _setOption: function (key, value) { 469 | if (key === 'classes') { 470 | this._setOptionClasses(value); 471 | } 472 | 473 | this.options[key] = value; 474 | 475 | if (key === 'disabled') { 476 | this._setOptionDisabled(value); 477 | } 478 | 479 | return this; 480 | }, 481 | 482 | _setOptionClasses: function (value) { 483 | var classKey, elements, currentElements; 484 | 485 | for (classKey in value) { 486 | currentElements = this.classesElementLookup[classKey]; 487 | if ( 488 | value[classKey] === this.options.classes[classKey] || 489 | !currentElements || 490 | !currentElements.length 491 | ) { 492 | continue; 493 | } 494 | 495 | // We are doing this to create a new jQuery object because the _removeClass() call 496 | // on the next line is going to destroy the reference to the current elements being 497 | // tracked. We need to save a copy of this collection so that we can add the new classes 498 | // below. 499 | elements = $(currentElements.get()); 500 | this._removeClass(currentElements, classKey); 501 | 502 | // We don't use _addClass() here, because that uses this.options.classes 503 | // for generating the string of classes. We want to use the value passed in from 504 | // _setOption(), this is the new value of the classes option which was passed to 505 | // _setOption(). We pass this value directly to _classes(). 506 | elements.addClass( 507 | this._classes({ 508 | element: elements, 509 | keys: classKey, 510 | classes: value, 511 | add: true 512 | }) 513 | ); 514 | } 515 | }, 516 | 517 | _setOptionDisabled: function (value) { 518 | this._toggleClass( 519 | this.widget(), 520 | this.widgetFullName + '-disabled', 521 | null, 522 | !!value 523 | ); 524 | 525 | // If the widget is becoming disabled, then nothing is interactive 526 | if (value) { 527 | this._removeClass(this.hoverable, null, 'ui-state-hover'); 528 | this._removeClass(this.focusable, null, 'ui-state-focus'); 529 | } 530 | }, 531 | 532 | enable: function () { 533 | return this._setOptions({ disabled: false }); 534 | }, 535 | 536 | disable: function () { 537 | return this._setOptions({ disabled: true }); 538 | }, 539 | 540 | _classes: function (options) { 541 | var full = []; 542 | var that = this; 543 | 544 | options = $.extend( 545 | { 546 | element: this.element, 547 | classes: this.options.classes || {} 548 | }, 549 | options 550 | ); 551 | 552 | function bindRemoveEvent() { 553 | options.element.each(function (_, element) { 554 | var isTracked = $.map(that.classesElementLookup, function (elements) { 555 | return elements; 556 | }).some(function (elements) { 557 | return elements.is(element); 558 | }); 559 | 560 | if (!isTracked) { 561 | that._on($(element), { 562 | remove: '_untrackClassesElement' 563 | }); 564 | } 565 | }); 566 | } 567 | 568 | function processClassString(classes, checkOption) { 569 | var current, i; 570 | for (i = 0; i < classes.length; i++) { 571 | current = that.classesElementLookup[classes[i]] || $(); 572 | if (options.add) { 573 | bindRemoveEvent(); 574 | current = $( 575 | $.uniqueSort(current.get().concat(options.element.get())) 576 | ); 577 | } else { 578 | current = $(current.not(options.element).get()); 579 | } 580 | that.classesElementLookup[classes[i]] = current; 581 | full.push(classes[i]); 582 | if (checkOption && options.classes[classes[i]]) { 583 | full.push(options.classes[classes[i]]); 584 | } 585 | } 586 | } 587 | 588 | if (options.keys) { 589 | processClassString(options.keys.match(/\S+/g) || [], true); 590 | } 591 | if (options.extra) { 592 | processClassString(options.extra.match(/\S+/g) || []); 593 | } 594 | 595 | return full.join(' '); 596 | }, 597 | 598 | _untrackClassesElement: function (event) { 599 | var that = this; 600 | $.each(that.classesElementLookup, function (key, value) { 601 | if ($.inArray(event.target, value) !== -1) { 602 | that.classesElementLookup[key] = $(value.not(event.target).get()); 603 | } 604 | }); 605 | 606 | this._off($(event.target)); 607 | }, 608 | 609 | _removeClass: function (element, keys, extra) { 610 | return this._toggleClass(element, keys, extra, false); 611 | }, 612 | 613 | _addClass: function (element, keys, extra) { 614 | return this._toggleClass(element, keys, extra, true); 615 | }, 616 | 617 | _toggleClass: function (element, keys, extra, add) { 618 | add = typeof add === 'boolean' ? add : extra; 619 | var shift = typeof element === 'string' || element === null, 620 | options = { 621 | extra: shift ? keys : extra, 622 | keys: shift ? element : keys, 623 | element: shift ? this.element : element, 624 | add: add 625 | }; 626 | options.element.toggleClass(this._classes(options), add); 627 | return this; 628 | }, 629 | 630 | _on: function (suppressDisabledCheck, element, handlers) { 631 | var delegateElement; 632 | var instance = this; 633 | 634 | // No suppressDisabledCheck flag, shuffle arguments 635 | if (typeof suppressDisabledCheck !== 'boolean') { 636 | handlers = element; 637 | element = suppressDisabledCheck; 638 | suppressDisabledCheck = false; 639 | } 640 | 641 | // No element argument, shuffle and use this.element 642 | if (!handlers) { 643 | handlers = element; 644 | element = this.element; 645 | delegateElement = this.widget(); 646 | } else { 647 | element = delegateElement = $(element); 648 | this.bindings = this.bindings.add(element); 649 | } 650 | 651 | $.each(handlers, function (event, handler) { 652 | function handlerProxy() { 653 | // Allow widgets to customize the disabled handling 654 | // - disabled as an array instead of boolean 655 | // - disabled class as method for disabling individual parts 656 | if ( 657 | !suppressDisabledCheck && 658 | (instance.options.disabled === true || 659 | $(this).hasClass('ui-state-disabled')) 660 | ) { 661 | return; 662 | } 663 | return (typeof handler === 'string' 664 | ? instance[handler] 665 | : handler 666 | ).apply(instance, arguments); 667 | } 668 | 669 | // Copy the guid so direct unbinding works 670 | if (typeof handler !== 'string') { 671 | handlerProxy.guid = handler.guid = 672 | handler.guid || handlerProxy.guid || $.guid++; 673 | } 674 | 675 | var match = event.match(/^([\w:-]*)\s*(.*)$/); 676 | var eventName = match[1] + instance.eventNamespace; 677 | var selector = match[2]; 678 | 679 | if (selector) { 680 | delegateElement.on(eventName, selector, handlerProxy); 681 | } else { 682 | element.on(eventName, handlerProxy); 683 | } 684 | }); 685 | }, 686 | 687 | _off: function (element, eventName) { 688 | eventName = 689 | (eventName || '').split(' ').join(this.eventNamespace + ' ') + 690 | this.eventNamespace; 691 | element.off(eventName); 692 | 693 | // Clear the stack to avoid memory leaks (#10056) 694 | this.bindings = $(this.bindings.not(element).get()); 695 | this.focusable = $(this.focusable.not(element).get()); 696 | this.hoverable = $(this.hoverable.not(element).get()); 697 | }, 698 | 699 | _delay: function (handler, delay) { 700 | var instance = this; 701 | function handlerProxy() { 702 | return (typeof handler === 'string' 703 | ? instance[handler] 704 | : handler 705 | ).apply(instance, arguments); 706 | } 707 | return setTimeout(handlerProxy, delay || 0); 708 | }, 709 | 710 | _hoverable: function (element) { 711 | this.hoverable = this.hoverable.add(element); 712 | this._on(element, { 713 | mouseenter: function (event) { 714 | this._addClass($(event.currentTarget), null, 'ui-state-hover'); 715 | }, 716 | mouseleave: function (event) { 717 | this._removeClass($(event.currentTarget), null, 'ui-state-hover'); 718 | } 719 | }); 720 | }, 721 | 722 | _focusable: function (element) { 723 | this.focusable = this.focusable.add(element); 724 | this._on(element, { 725 | focusin: function (event) { 726 | this._addClass($(event.currentTarget), null, 'ui-state-focus'); 727 | }, 728 | focusout: function (event) { 729 | this._removeClass($(event.currentTarget), null, 'ui-state-focus'); 730 | } 731 | }); 732 | }, 733 | 734 | _trigger: function (type, event, data) { 735 | var prop, orig; 736 | var callback = this.options[type]; 737 | 738 | data = data || {}; 739 | event = $.Event(event); 740 | event.type = (type === this.widgetEventPrefix 741 | ? type 742 | : this.widgetEventPrefix + type 743 | ).toLowerCase(); 744 | 745 | // The original event may come from any element 746 | // so we need to reset the target on the new event 747 | event.target = this.element[0]; 748 | 749 | // Copy original event properties over to the new event 750 | orig = event.originalEvent; 751 | if (orig) { 752 | for (prop in orig) { 753 | if (!(prop in event)) { 754 | event[prop] = orig[prop]; 755 | } 756 | } 757 | } 758 | 759 | this.element.trigger(event, data); 760 | return !( 761 | ($.isFunction(callback) && 762 | callback.apply(this.element[0], [event].concat(data)) === false) || 763 | event.isDefaultPrevented() 764 | ); 765 | } 766 | }; 767 | 768 | $.each({ show: 'fadeIn', hide: 'fadeOut' }, function (method, defaultEffect) { 769 | $.Widget.prototype['_' + method] = function (element, options, callback) { 770 | if (typeof options === 'string') { 771 | options = { effect: options }; 772 | } 773 | 774 | var hasOptions; 775 | var effectName = !options 776 | ? method 777 | : options === true || typeof options === 'number' 778 | ? defaultEffect 779 | : options.effect || defaultEffect; 780 | 781 | options = options || {}; 782 | if (typeof options === 'number') { 783 | options = { duration: options }; 784 | } 785 | 786 | hasOptions = !$.isEmptyObject(options); 787 | options.complete = callback; 788 | 789 | if (options.delay) { 790 | element.delay(options.delay); 791 | } 792 | 793 | if (hasOptions && $.effects && $.effects.effect[effectName]) { 794 | element[method](options); 795 | } else if (effectName !== method && element[effectName]) { 796 | element[effectName](options.duration, options.easing, callback); 797 | } else { 798 | element.queue(function (next) { 799 | $(this)[method](); 800 | if (callback) { 801 | callback.call(element[0]); 802 | } 803 | next(); 804 | }); 805 | } 806 | }; 807 | }); 808 | }); 809 | --------------------------------------------------------------------------------