From ee99952d90578065c427e823b8c44dbb61f0d90c Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Mon, 17 Jan 2022 20:06:26 -0500 Subject: [PATCH 01/39] Support OCI (Read/Write) --- lib/Data/Database.php | 127 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 104 insertions(+), 23 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 0c66d330..a2b1fe1a 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -199,12 +199,30 @@ class Database extends AbstractData $burnafterreading = $paste['adata'][3]; } try { + $big_string = $isVersion1 ? $paste['data'] : Json::encode($paste); + if (self::$_type === 'oci') { + # It is not possible to execute in the normal way if strlen($big_string) >= 4000 + $stmt = self::$_db->prepare( + 'INSERT INTO ' . self::_sanitizeIdentifier('paste') . + ' VALUES(?,?,?,?,?,?,?,?,?)' + ); + $stmt->bindParam(1, $pasteid); + $stmt->bindParam(2, $big_string, PDO::PARAM_STR, strlen($big_string)); + $stmt->bindParam(3, $created, PDO::PARAM_INT); + $stmt->bindParam(4, $expire_date, PDO::PARAM_INT); + $stmt->bindParam(5, $opendiscussion, PDO::PARAM_INT); + $stmt->bindParam(6, $burnafterreading, PDO::PARAM_INT); + $stmt->bindParam(7, Json::encode($meta)); + $stmt->bindParam(8, $attachment, PDO::PARAM_STR, strlen($attachment)); + $stmt->bindParam(9, $attachmentname); + return $stmt->execute(); + } return self::_exec( 'INSERT INTO ' . self::_sanitizeIdentifier('paste') . ' VALUES(?,?,?,?,?,?,?,?,?)', array( $pasteid, - $isVersion1 ? $paste['data'] : Json::encode($paste), + $big_string, $created, $expire_date, (int) $opendiscussion, @@ -233,11 +251,15 @@ class Database extends AbstractData } self::$_cache[$pasteid] = false; + $rawData = ""; try { $paste = self::_select( 'SELECT * FROM ' . self::_sanitizeIdentifier('paste') . ' WHERE dataid = ?', array($pasteid), true ); + if ($paste !== false) { + $rawData = self::$_type === 'oci' ? self::_clob($paste['DATA']) : $paste['data']; + } } catch (Exception $e) { $paste = false; } @@ -245,25 +267,25 @@ class Database extends AbstractData return false; } // create array - $data = Json::decode($paste['data']); + $data = Json::decode($rawData); $isVersion2 = array_key_exists('v', $data) && $data['v'] >= 2; if ($isVersion2) { self::$_cache[$pasteid] = $data; list($createdKey) = self::_getVersionedKeys(2); } else { - self::$_cache[$pasteid] = array('data' => $paste['data']); + self::$_cache[$pasteid] = array('data' => $paste[self::_sanitizeColumn('data')]); list($createdKey) = self::_getVersionedKeys(1); } try { - $paste['meta'] = Json::decode($paste['meta']); + $paste['meta'] = Json::decode($paste[self::_sanitizeColumn('meta')]); } catch (Exception $e) { $paste['meta'] = array(); } $paste = self::upgradePreV1Format($paste); self::$_cache[$pasteid]['meta'] = $paste['meta']; - self::$_cache[$pasteid]['meta'][$createdKey] = (int) $paste['postdate']; - $expire_date = (int) $paste['expiredate']; + self::$_cache[$pasteid]['meta'][$createdKey] = (int) $paste[self::_sanitizeColumn('postdate')]; + $expire_date = (int) $paste[self::_sanitizeColumn('expiredate')]; if ($expire_date > 0) { self::$_cache[$pasteid]['meta']['expire_date'] = $expire_date; } @@ -272,16 +294,16 @@ class Database extends AbstractData } // support v1 attachments - if (array_key_exists('attachment', $paste) && strlen($paste['attachment'])) { - self::$_cache[$pasteid]['attachment'] = $paste['attachment']; - if (array_key_exists('attachmentname', $paste) && strlen($paste['attachmentname'])) { - self::$_cache[$pasteid]['attachmentname'] = $paste['attachmentname']; + if (array_key_exists(self::_sanitizeColumn('attachment'), $paste) && strlen($paste[self::_sanitizeColumn('attachment')])) { + self::$_cache[$pasteid]['attachment'] = $paste[self::_sanitizeColumn('attachment')]; + if (array_key_exists(self::_sanitizeColumn('attachmentname'), $paste) && strlen($paste[self::_sanitizeColumn('attachmentname')])) { + self::$_cache[$pasteid]['attachmentname'] = $paste[self::_sanitizeColumn('attachmentname')]; } } - if ($paste['opendiscussion']) { + if ($paste[self::_sanitizeColumn('opendiscussion')]) { self::$_cache[$pasteid]['meta']['opendiscussion'] = true; } - if ($paste['burnafterreading']) { + if ($paste[self::_sanitizeColumn('burnafterreading')]) { self::$_cache[$pasteid]['meta']['burnafterreading'] = true; } @@ -356,6 +378,21 @@ class Database extends AbstractData } } try { + if (self::$_type === 'oci') { + # It is not possible to execute in the normal way if strlen($big_string) >= 4000 + $stmt = self::$_db->prepare( + 'INSERT INTO ' . self::_sanitizeIdentifier('comment') . + ' VALUES(?,?,?,?,?,?,?)' + ); + $stmt->bindParam(1, $commentid); + $stmt->bindParam(2, $pasteid); + $stmt->bindParam(3, $parentid); + $stmt->bindParam(4, $data, PDO::PARAM_STR, strlen($data)); + $stmt->bindParam(5, $meta['nickname']); + $stmt->bindParam(6, $meta[$iconKey]); + $stmt->bindParam(7, $meta[$createdKey], PDO::PARAM_INT); + return $stmt->execute(); + } return self::_exec( 'INSERT INTO ' . self::_sanitizeIdentifier('comment') . ' VALUES(?,?,?,?,?,?,?)', @@ -392,22 +429,33 @@ class Database extends AbstractData $comments = array(); if (count($rows)) { foreach ($rows as $row) { - $i = $this->getOpenSlot($comments, (int) $row['postdate']); - $data = Json::decode($row['data']); + $i = $this->getOpenSlot($comments, (int) $row[self::_sanitizeColumn('postdate')]); + $id = $row[self::_sanitizeColumn('dataid')]; + if (self::$_type === 'oci') { + $newrow = self::_select( + 'SELECT data FROM ' . self::_sanitizeIdentifier('comment') . + ' WHERE dataid = ?', array($id), true + ); + $rawData = self::_clob($newrow['DATA']); + } + else { + $rawData = $row['data']; + } + $data = Json::decode($rawData); if (array_key_exists('v', $data) && $data['v'] >= 2) { $version = 2; $comments[$i] = $data; } else { $version = 1; - $comments[$i] = array('data' => $row['data']); + $comments[$i] = array('data' => $rawData); } list($createdKey, $iconKey) = self::_getVersionedKeys($version); - $comments[$i]['id'] = $row['dataid']; - $comments[$i]['parentid'] = $row['parentid']; - $comments[$i]['meta'] = array($createdKey => (int) $row['postdate']); + $comments[$i]['id'] = $id; + $comments[$i]['parentid'] = $row[self::_sanitizeColumn('parentid')]; + $comments[$i]['meta'] = array($createdKey => (int) $row[self::_sanitizeColumn('postdate')]); foreach (array('nickname' => 'nickname', 'vizhash' => $iconKey) as $rowKey => $commentKey) { - if (array_key_exists($rowKey, $row) && !empty($row[$rowKey])) { - $comments[$i]['meta'][$commentKey] = $row[$rowKey]; + if (array_key_exists(self::_sanitizeColumn($rowKey), $row) && !empty($row[self::_sanitizeColumn($rowKey)])) { + $comments[$i]['meta'][$commentKey] = $row[self::_sanitizeColumn($rowKey)]; } } } @@ -518,7 +566,8 @@ class Database extends AbstractData $pastes = array(); $rows = self::_select( 'SELECT dataid FROM ' . self::_sanitizeIdentifier('paste') . - ' WHERE expiredate < ? AND expiredate != ? LIMIT ?', + ' WHERE expiredate < ? AND expiredate != ? ' . + (self::$_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?'), array(time(), 0, $batchsize) ); if (count($rows)) { @@ -658,7 +707,7 @@ class Database extends AbstractData } catch (PDOException $e) { return ''; } - return $row ? $row['value'] : ''; + return $row ? $row[self::_sanitizeColumn('value')] : ''; } /** @@ -789,7 +838,21 @@ class Database extends AbstractData */ private static function _sanitizeIdentifier($identifier) { - return preg_replace('/[^A-Za-z0-9_]+/', '', self::$_prefix . $identifier); + $id = preg_replace('/[^A-Za-z0-9_]+/', '', self::$_prefix . $identifier); + return self::_sanitizeColumn($id); + } + + /** + * sanitizes column name because OCI + * + * @access private + * @static + * @param string $name + * @return string + */ + private static function _sanitizeColumn($name) + { + return self::$_type === 'oci' ? strtoupper($name) : $name; } /** @@ -865,4 +928,22 @@ class Database extends AbstractData ); } } + + /** + * read CLOB for OCI + * https://stackoverflow.com/questions/36200534/pdo-oci-into-a-clob-field + * + * @access private + * @static + * @param object $column + * @return string + */ + private static function _clob($column) + { + if ($column == null) return null; + $str = ""; + while ($column !== null and $tmp = fread($column, 1024)) + $str .= $tmp; + return $str; + } } From 6a489d35ab39041b3dd793d91beaf8681489bf4f Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Tue, 18 Jan 2022 11:21:25 -0500 Subject: [PATCH 02/39] Support OCI (Create table) --- lib/Data/Database.php | 71 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index a2b1fe1a..617676c3 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -572,7 +572,7 @@ class Database extends AbstractData ); if (count($rows)) { foreach ($rows as $row) { - $pastes[] = $row['dataid']; + $pastes[] = $row[self::_sanitizeIdentifier('dataid')]; } } return $pastes; @@ -710,6 +710,18 @@ class Database extends AbstractData return $row ? $row[self::_sanitizeColumn('value')] : ''; } + /** + * OCI cannot accept semicolons + * + * @access private + * @static + * @return string + */ + private static function _getSemicolon() + { + return self::$_type === 'oci' ? "" : ";"; + } + /** * get the primary key clauses, depending on the database driver * @@ -721,7 +733,7 @@ class Database extends AbstractData private static function _getPrimaryKeyClauses($key = 'dataid') { $main_key = $after_key = ''; - if (self::$_type === 'mysql') { + if (self::$_type === 'mysql' || self::$_type === 'oci') { $after_key = ", PRIMARY KEY ($key)"; } else { $main_key = ' PRIMARY KEY'; @@ -740,13 +752,13 @@ class Database extends AbstractData */ private static function _getDataType() { - return self::$_type === 'pgsql' ? 'TEXT' : 'BLOB'; + return self::$_type === 'pgsql' ? 'TEXT' : (self::$_type === 'oci' ? 'VARCHAR2(4000)' : 'BLOB'); } /** * get the attachment type, depending on the database driver * - * PostgreSQL uses a different API for BLOBs then SQL, hence we use TEXT + * PostgreSQL and OCI use different APIs for BLOBs then SQL, hence we use TEXT and CLOB * * @access private * @static @@ -754,7 +766,21 @@ class Database extends AbstractData */ private static function _getAttachmentType() { - return self::$_type === 'pgsql' ? 'TEXT' : 'MEDIUMBLOB'; + return self::$_type === 'pgsql' ? 'TEXT' : (self::$_type === 'oci' ? 'CLOB' : 'MEDIUMBLOB'); + } + + /** + * get the meta type, depending on the database driver + * + * OCI can't even accept TEXT so it has to be VARCHAR2(200) + * + * @access private + * @static + * @return string + */ + private static function _getMetaType() + { + return self::$_type === 'oci' ? 'VARCHAR2(4000)' : 'TEXT'; } /** @@ -768,6 +794,7 @@ class Database extends AbstractData list($main_key, $after_key) = self::_getPrimaryKeyClauses(); $dataType = self::_getDataType(); $attachmentType = self::_getAttachmentType(); + $metaType = self::_getMetaType(); self::$_db->exec( 'CREATE TABLE ' . self::_sanitizeIdentifier('paste') . ' ( ' . "dataid CHAR(16) NOT NULL$main_key, " . @@ -776,12 +803,26 @@ class Database extends AbstractData 'expiredate INT, ' . 'opendiscussion INT, ' . 'burnafterreading INT, ' . - 'meta TEXT, ' . + "meta $metaType, " . "attachment $attachmentType, " . - "attachmentname $dataType$after_key );" + "attachmentname $dataType$after_key )" . self::_getSemicolon() ); } + /** + * get the nullable text type, depending on the database driver + * + * OCI will pad CHAR columns with spaces, hence VARCHAR2 + * + * @access private + * @static + * @return string + */ + private static function _getParentType() + { + return self::$_type === 'oci' ? 'VARCHAR2(16)' : 'CHAR(16)'; + } + /** * create the paste table * @@ -792,19 +833,21 @@ class Database extends AbstractData { list($main_key, $after_key) = self::_getPrimaryKeyClauses(); $dataType = self::_getDataType(); + $parentType = self::_getParentType(); + $attachmentType = self::_getAttachmentType(); self::$_db->exec( 'CREATE TABLE ' . self::_sanitizeIdentifier('comment') . ' ( ' . "dataid CHAR(16) NOT NULL$main_key, " . 'pasteid CHAR(16), ' . - 'parentid CHAR(16), ' . - "data $dataType, " . + "parentid $parentType, " . + "data $attachmentType, " . "nickname $dataType, " . "vizhash $dataType, " . - "postdate INT$after_key );" + "postdate INT$after_key )" . self::_getSemicolon() ); self::$_db->exec( - 'CREATE INDEX IF NOT EXISTS comment_parent ON ' . - self::_sanitizeIdentifier('comment') . '(pasteid);' + 'CREATE INDEX comment_parent ON ' . + self::_sanitizeIdentifier('comment') . '(pasteid)' . self::_getSemicolon() ); } @@ -817,9 +860,11 @@ class Database extends AbstractData private static function _createConfigTable() { list($main_key, $after_key) = self::_getPrimaryKeyClauses('id'); + $charType = self::$_type === 'oci' ? 'VARCHAR2(16)' : 'CHAR(16)'; + $textType = self::$_type === 'oci' ? 'VARCHAR2(4000)' : 'TEXT'; self::$_db->exec( 'CREATE TABLE ' . self::_sanitizeIdentifier('config') . - " ( id CHAR(16) NOT NULL$main_key, value TEXT$after_key );" + " ( id $charType NOT NULL$main_key, value $textType$after_key )" . self::_getSemicolon() ); self::_exec( 'INSERT INTO ' . self::_sanitizeIdentifier('config') . From 041ef7f7a5865c6bf8163e9f8219a2029d05a2e1 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Thu, 20 Jan 2022 13:33:23 -0500 Subject: [PATCH 03/39] Support OCI (Satisfy the CI) --- lib/Data/Database.php | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 617676c3..a89da4b9 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -199,9 +199,10 @@ class Database extends AbstractData $burnafterreading = $paste['adata'][3]; } try { - $big_string = $isVersion1 ? $paste['data'] : Json::encode($paste); + $big_string = $isVersion1 ? $paste['data'] : Json::encode($paste); + $metajson = Json::encode($meta); if (self::$_type === 'oci') { - # It is not possible to execute in the normal way if strlen($big_string) >= 4000 + // It is not possible to execute in the normal way if strlen($big_string) >= 4000 $stmt = self::$_db->prepare( 'INSERT INTO ' . self::_sanitizeIdentifier('paste') . ' VALUES(?,?,?,?,?,?,?,?,?)' @@ -212,7 +213,7 @@ class Database extends AbstractData $stmt->bindParam(4, $expire_date, PDO::PARAM_INT); $stmt->bindParam(5, $opendiscussion, PDO::PARAM_INT); $stmt->bindParam(6, $burnafterreading, PDO::PARAM_INT); - $stmt->bindParam(7, Json::encode($meta)); + $stmt->bindParam(7, $metajson); $stmt->bindParam(8, $attachment, PDO::PARAM_STR, strlen($attachment)); $stmt->bindParam(9, $attachmentname); return $stmt->execute(); @@ -251,7 +252,7 @@ class Database extends AbstractData } self::$_cache[$pasteid] = false; - $rawData = ""; + $rawData = ''; try { $paste = self::_select( 'SELECT * FROM ' . self::_sanitizeIdentifier('paste') . @@ -379,7 +380,7 @@ class Database extends AbstractData } try { if (self::$_type === 'oci') { - # It is not possible to execute in the normal way if strlen($big_string) >= 4000 + // It is not possible to execute in the normal way if strlen($big_string) >= 4000 $stmt = self::$_db->prepare( 'INSERT INTO ' . self::_sanitizeIdentifier('comment') . ' VALUES(?,?,?,?,?,?,?)' @@ -437,8 +438,7 @@ class Database extends AbstractData ' WHERE dataid = ?', array($id), true ); $rawData = self::_clob($newrow['DATA']); - } - else { + } else { $rawData = $row['data']; } $data = Json::decode($rawData); @@ -719,7 +719,7 @@ class Database extends AbstractData */ private static function _getSemicolon() { - return self::$_type === 'oci' ? "" : ";"; + return self::$_type === 'oci' ? '' : ';'; } /** @@ -980,15 +980,18 @@ class Database extends AbstractData * * @access private * @static - * @param object $column + * @param resource $column * @return string */ private static function _clob($column) { - if ($column == null) return null; - $str = ""; - while ($column !== null and $tmp = fread($column, 1024)) + if ($column == null) { + return null; + } + $str = ''; + while ($tmp = fread($column, 1024)) { $str .= $tmp; + } return $str; } } From 2182cdd44f092277ce829cb9effd861ceb415c06 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 22 Jan 2022 08:45:12 +0100 Subject: [PATCH 04/39] generalize OCI handling of queries and results --- CHANGELOG.md | 11 +- CREDITS.md | 1 + lib/Data/Database.php | 242 ++++++++++++++------------------------ tst/Data/DatabaseTest.php | 12 ++ 4 files changed, 105 insertions(+), 161 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d964a57a..428a60ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * ADDED: Opt-out of federated learning of cohorts (FLoC) (#776) * ADDED: Configuration option to exempt IPs from the rate-limiter (#787) * ADDED: Google Cloud Storage backend support (#795) + * ADDED: Oracle database support (#868) * CHANGED: Language selection cookie only transmitted over HTTPS (#472) * CHANGED: Upgrading libraries to: random_compat 2.0.20 * CHANGED: Removed automatic `.ini` configuration file migration (#808) @@ -65,7 +66,7 @@ * CHANGED: Upgrading libraries to: DOMpurify 2.0.1 * FIXED: Enabling browsers without WASM to create pastes and read uncompressed ones (#454) * FIXED: Cloning related issues (#489, #491, #493, #494) - * FIXED: Enable file operation only when editing (#497) + * FIXED: Enable file operation only when editing (#497) * FIXED: Clicking 'New' on a previously submitted paste does not blank address bar (#354) * FIXED: Clear address bar when create new paste from existing paste (#479) * FIXED: Discussion section not hiding when new/clone paste is clicked on (#484) @@ -228,7 +229,7 @@ encryption), i18n (translation, counterpart of i18n.php) and helper (stateless u * FIXED: 2 minor corrections to avoid notices in php log. * FIXED: Sources converted to UTF-8. * **Alpha 0.14 (2012-04-20):** - * ADDED: GD presence is checked. + * ADDED: GD presence is checked. * CHANGED: Traffic limiter data files moved to data/ (→easier rights management) * ADDED: "Burn after reading" implemented. Opening the URL will display the paste and immediately destroy it on server. * **Alpha 0.13 (2012-04-18):** @@ -236,16 +237,16 @@ encryption), i18n (translation, counterpart of i18n.php) and helper (stateless u * FIXED: $error not properly initialized in index.php * **Alpha 0.12 (2012-04-18):** * **DISCUSSIONS !** Now you can enable discussions on your pastes. Of course, posted comments and nickname are also encrypted and the server cannot see them. - * This feature implies a change in storage format. You will have to delete all previous pastes in your ZeroBin. + * This feature implies a change in storage format. You will have to delete all previous pastes in your ZeroBin. * Added [[php:vizhash_gd|Vizhash]] as avatars, so you can match posters IP addresses without revealing them. (Same image = same IP). Of course the IP address cannot be deduced from the Vizhash. * Remaining time before expiration is now displayed. - * Explicit tags were added to CSS and jQuery selectors (eg. div#aaa instead of #aaa) to speed up browser. + * Explicit tags were added to CSS and jQuery selectors (eg. div#aaa instead of #aaa) to speed up browser. * Better cleaning of the URL (to make sure the key is not broken by some stupid redirection service) * **Alpha 0.11 (2012-04-12):** * Automatically ignore parameters (such as &utm_source=...) added //after// the anchor by some stupid Web 2.0 services. * First public release. * **Alpha 0.10 (2012-04-12):** - * IE9 does not seem to correctly support ''pre-wrap'' either. Special handling mode activated for all version of IE<10. (Note: **ALL other browsers** correctly support this feature.) + * IE9 does not seem to correctly support ''pre-wrap'' either. Special handling mode activated for all version of IE<10. (Note: **ALL other browsers** correctly support this feature.) * **Alpha 0.9 (2012-04-11):** * Oh bummer... IE 8 is as shitty as IE6/7: Its does not seem to support ''white-space:pre-wrap'' correctly. I had to activate the special handling mode. I still have to test IE 9. * **Alpha 0.8 (2012-04-11):** diff --git a/CREDITS.md b/CREDITS.md index 612749c0..6c2f647c 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -29,6 +29,7 @@ Sébastien Sauvage - original idea and main developer * Lucas Savva - configurable config file location, NixOS packaging * rodehoed - option to exempt ips from the rate-limiter * Mark van Holsteijn - Google Cloud Storage backend +* Austin Huang - Oracle database support ## Translations * Hexalyse - French diff --git a/lib/Data/Database.php b/lib/Data/Database.php index a89da4b9..f616a96a 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -199,31 +199,12 @@ class Database extends AbstractData $burnafterreading = $paste['adata'][3]; } try { - $big_string = $isVersion1 ? $paste['data'] : Json::encode($paste); - $metajson = Json::encode($meta); - if (self::$_type === 'oci') { - // It is not possible to execute in the normal way if strlen($big_string) >= 4000 - $stmt = self::$_db->prepare( - 'INSERT INTO ' . self::_sanitizeIdentifier('paste') . - ' VALUES(?,?,?,?,?,?,?,?,?)' - ); - $stmt->bindParam(1, $pasteid); - $stmt->bindParam(2, $big_string, PDO::PARAM_STR, strlen($big_string)); - $stmt->bindParam(3, $created, PDO::PARAM_INT); - $stmt->bindParam(4, $expire_date, PDO::PARAM_INT); - $stmt->bindParam(5, $opendiscussion, PDO::PARAM_INT); - $stmt->bindParam(6, $burnafterreading, PDO::PARAM_INT); - $stmt->bindParam(7, $metajson); - $stmt->bindParam(8, $attachment, PDO::PARAM_STR, strlen($attachment)); - $stmt->bindParam(9, $attachmentname); - return $stmt->execute(); - } return self::_exec( 'INSERT INTO ' . self::_sanitizeIdentifier('paste') . ' VALUES(?,?,?,?,?,?,?,?,?)', array( $pasteid, - $big_string, + $isVersion1 ? $paste['data'] : Json::encode($paste), $created, $expire_date, (int) $opendiscussion, @@ -252,15 +233,11 @@ class Database extends AbstractData } self::$_cache[$pasteid] = false; - $rawData = ''; try { $paste = self::_select( 'SELECT * FROM ' . self::_sanitizeIdentifier('paste') . ' WHERE dataid = ?', array($pasteid), true ); - if ($paste !== false) { - $rawData = self::$_type === 'oci' ? self::_clob($paste['DATA']) : $paste['data']; - } } catch (Exception $e) { $paste = false; } @@ -268,25 +245,25 @@ class Database extends AbstractData return false; } // create array - $data = Json::decode($rawData); + $data = Json::decode($paste['data']); $isVersion2 = array_key_exists('v', $data) && $data['v'] >= 2; if ($isVersion2) { self::$_cache[$pasteid] = $data; list($createdKey) = self::_getVersionedKeys(2); } else { - self::$_cache[$pasteid] = array('data' => $paste[self::_sanitizeColumn('data')]); + self::$_cache[$pasteid] = array('data' => $paste['data']); list($createdKey) = self::_getVersionedKeys(1); } try { - $paste['meta'] = Json::decode($paste[self::_sanitizeColumn('meta')]); + $paste['meta'] = Json::decode($paste['meta']); } catch (Exception $e) { $paste['meta'] = array(); } $paste = self::upgradePreV1Format($paste); self::$_cache[$pasteid]['meta'] = $paste['meta']; - self::$_cache[$pasteid]['meta'][$createdKey] = (int) $paste[self::_sanitizeColumn('postdate')]; - $expire_date = (int) $paste[self::_sanitizeColumn('expiredate')]; + self::$_cache[$pasteid]['meta'][$createdKey] = (int) $paste['postdate']; + $expire_date = (int) $paste['expiredate']; if ($expire_date > 0) { self::$_cache[$pasteid]['meta']['expire_date'] = $expire_date; } @@ -295,16 +272,16 @@ class Database extends AbstractData } // support v1 attachments - if (array_key_exists(self::_sanitizeColumn('attachment'), $paste) && strlen($paste[self::_sanitizeColumn('attachment')])) { - self::$_cache[$pasteid]['attachment'] = $paste[self::_sanitizeColumn('attachment')]; - if (array_key_exists(self::_sanitizeColumn('attachmentname'), $paste) && strlen($paste[self::_sanitizeColumn('attachmentname')])) { - self::$_cache[$pasteid]['attachmentname'] = $paste[self::_sanitizeColumn('attachmentname')]; + if (array_key_exists('attachment', $paste) && strlen($paste['attachment'])) { + self::$_cache[$pasteid]['attachment'] = $paste['attachment']; + if (array_key_exists('attachmentname', $paste) && strlen($paste['attachmentname'])) { + self::$_cache[$pasteid]['attachmentname'] = $paste['attachmentname']; } } - if ($paste[self::_sanitizeColumn('opendiscussion')]) { + if ($paste['opendiscussion']) { self::$_cache[$pasteid]['meta']['opendiscussion'] = true; } - if ($paste[self::_sanitizeColumn('burnafterreading')]) { + if ($paste['burnafterreading']) { self::$_cache[$pasteid]['meta']['burnafterreading'] = true; } @@ -379,21 +356,6 @@ class Database extends AbstractData } } try { - if (self::$_type === 'oci') { - // It is not possible to execute in the normal way if strlen($big_string) >= 4000 - $stmt = self::$_db->prepare( - 'INSERT INTO ' . self::_sanitizeIdentifier('comment') . - ' VALUES(?,?,?,?,?,?,?)' - ); - $stmt->bindParam(1, $commentid); - $stmt->bindParam(2, $pasteid); - $stmt->bindParam(3, $parentid); - $stmt->bindParam(4, $data, PDO::PARAM_STR, strlen($data)); - $stmt->bindParam(5, $meta['nickname']); - $stmt->bindParam(6, $meta[$iconKey]); - $stmt->bindParam(7, $meta[$createdKey], PDO::PARAM_INT); - return $stmt->execute(); - } return self::_exec( 'INSERT INTO ' . self::_sanitizeIdentifier('comment') . ' VALUES(?,?,?,?,?,?,?)', @@ -430,32 +392,22 @@ class Database extends AbstractData $comments = array(); if (count($rows)) { foreach ($rows as $row) { - $i = $this->getOpenSlot($comments, (int) $row[self::_sanitizeColumn('postdate')]); - $id = $row[self::_sanitizeColumn('dataid')]; - if (self::$_type === 'oci') { - $newrow = self::_select( - 'SELECT data FROM ' . self::_sanitizeIdentifier('comment') . - ' WHERE dataid = ?', array($id), true - ); - $rawData = self::_clob($newrow['DATA']); - } else { - $rawData = $row['data']; - } - $data = Json::decode($rawData); + $i = $this->getOpenSlot($comments, (int) $row['postdate']); + $data = Json::decode($row['data']); if (array_key_exists('v', $data) && $data['v'] >= 2) { $version = 2; $comments[$i] = $data; } else { $version = 1; - $comments[$i] = array('data' => $rawData); + $comments[$i] = array('data' => $row['data']); } list($createdKey, $iconKey) = self::_getVersionedKeys($version); - $comments[$i]['id'] = $id; - $comments[$i]['parentid'] = $row[self::_sanitizeColumn('parentid')]; - $comments[$i]['meta'] = array($createdKey => (int) $row[self::_sanitizeColumn('postdate')]); + $comments[$i]['id'] = $row['dataid']; + $comments[$i]['parentid'] = $row['parentid']; + $comments[$i]['meta'] = array($createdKey => (int) $row['postdate']); foreach (array('nickname' => 'nickname', 'vizhash' => $iconKey) as $rowKey => $commentKey) { - if (array_key_exists(self::_sanitizeColumn($rowKey), $row) && !empty($row[self::_sanitizeColumn($rowKey)])) { - $comments[$i]['meta'][$commentKey] = $row[self::_sanitizeColumn($rowKey)]; + if (array_key_exists($rowKey, $row) && !empty($row[$rowKey])) { + $comments[$i]['meta'][$commentKey] = $row[$rowKey]; } } } @@ -572,7 +524,7 @@ class Database extends AbstractData ); if (count($rows)) { foreach ($rows as $row) { - $pastes[] = $row[self::_sanitizeIdentifier('dataid')]; + $pastes[] = $row['dataid']; } } return $pastes; @@ -591,7 +543,22 @@ class Database extends AbstractData private static function _exec($sql, array $params) { $statement = self::$_db->prepare($sql); - $result = $statement->execute($params); + if (self::$_type === 'oci') { + // It is not possible to execute in the normal way if strlen($param) >= 4000 + foreach ($params as $key => $parameter) { + $position = $key + 1; + if (is_int($parameter)) { + $statement->bindParam($position, $parameter, PDO::PARAM_INT); + } elseif ($length = strlen($parameter) >= 4000) { + $statement->bindParam($position, $parameter, PDO::PARAM_STR, $length); + } else { + $statement->bindParam($position, $parameter); + } + } + $result = $statement->execute(); + } else { + $result = $statement->execute($params); + } $statement->closeCursor(); return $result; } @@ -615,6 +582,14 @@ class Database extends AbstractData $statement->fetch(PDO::FETCH_ASSOC) : $statement->fetchAll(PDO::FETCH_ASSOC); $statement->closeCursor(); + if (self::$_type === 'oci') { + // returned column names are all upper case, convert these back + // returned CLOB values are streams, convert these into strings + $result = array_combine( + array_map('strtolower', array_keys($result)), + array_map('self::_sanitizeClob', array_values($result)) + ); + } return $result; } @@ -707,19 +682,7 @@ class Database extends AbstractData } catch (PDOException $e) { return ''; } - return $row ? $row[self::_sanitizeColumn('value')] : ''; - } - - /** - * OCI cannot accept semicolons - * - * @access private - * @static - * @return string - */ - private static function _getSemicolon() - { - return self::$_type === 'oci' ? '' : ';'; + return $row ? $row['value'] : ''; } /** @@ -744,7 +707,7 @@ class Database extends AbstractData /** * get the data type, depending on the database driver * - * PostgreSQL uses a different API for BLOBs then SQL, hence we use TEXT + * PostgreSQL and OCI uses a different API for BLOBs then SQL, hence we use TEXT and CLOB * * @access private * @static @@ -752,7 +715,7 @@ class Database extends AbstractData */ private static function _getDataType() { - return self::$_type === 'pgsql' ? 'TEXT' : (self::$_type === 'oci' ? 'VARCHAR2(4000)' : 'BLOB'); + return self::$_type === 'pgsql' ? 'TEXT' : (self::$_type === 'oci' ? 'CLOB' : 'BLOB'); } /** @@ -772,7 +735,7 @@ class Database extends AbstractData /** * get the meta type, depending on the database driver * - * OCI can't even accept TEXT so it has to be VARCHAR2(200) + * OCI doesn't accept TEXT so it has to be VARCHAR2(4000) * * @access private * @static @@ -798,31 +761,17 @@ class Database extends AbstractData self::$_db->exec( 'CREATE TABLE ' . self::_sanitizeIdentifier('paste') . ' ( ' . "dataid CHAR(16) NOT NULL$main_key, " . - "data $attachmentType, " . + "data $dataType, " . 'postdate INT, ' . 'expiredate INT, ' . 'opendiscussion INT, ' . 'burnafterreading INT, ' . "meta $metaType, " . "attachment $attachmentType, " . - "attachmentname $dataType$after_key )" . self::_getSemicolon() + "attachmentname $dataType$after_key )" ); } - /** - * get the nullable text type, depending on the database driver - * - * OCI will pad CHAR columns with spaces, hence VARCHAR2 - * - * @access private - * @static - * @return string - */ - private static function _getParentType() - { - return self::$_type === 'oci' ? 'VARCHAR2(16)' : 'CHAR(16)'; - } - /** * create the paste table * @@ -833,21 +782,19 @@ class Database extends AbstractData { list($main_key, $after_key) = self::_getPrimaryKeyClauses(); $dataType = self::_getDataType(); - $parentType = self::_getParentType(); - $attachmentType = self::_getAttachmentType(); self::$_db->exec( 'CREATE TABLE ' . self::_sanitizeIdentifier('comment') . ' ( ' . "dataid CHAR(16) NOT NULL$main_key, " . 'pasteid CHAR(16), ' . - "parentid $parentType, " . - "data $attachmentType, " . + 'parentid CHAR(16), ' . + "data $dataType, " . "nickname $dataType, " . "vizhash $dataType, " . - "postdate INT$after_key )" . self::_getSemicolon() + "postdate INT$after_key )" ); self::$_db->exec( - 'CREATE INDEX comment_parent ON ' . - self::_sanitizeIdentifier('comment') . '(pasteid)' . self::_getSemicolon() + 'CREATE INDEX IF NOT EXISTS comment_parent ON ' . + self::_sanitizeIdentifier('comment') . '(pasteid)' ); } @@ -864,7 +811,7 @@ class Database extends AbstractData $textType = self::$_type === 'oci' ? 'VARCHAR2(4000)' : 'TEXT'; self::$_db->exec( 'CREATE TABLE ' . self::_sanitizeIdentifier('config') . - " ( id $charType NOT NULL$main_key, value $textType$after_key )" . self::_getSemicolon() + " ( id $charType NOT NULL$main_key, value $textType$after_key )" ); self::_exec( 'INSERT INTO ' . self::_sanitizeIdentifier('config') . @@ -873,6 +820,24 @@ class Database extends AbstractData ); } + /** + * sanitizes CLOB values used with OCI + * + * From: https://stackoverflow.com/questions/36200534/pdo-oci-into-a-clob-field + * + * @access private + * @static + * @param int|string|resource $value + * @return int|string + */ + public static function _sanitizeClob($value) + { + if (is_resource($value)) { + $value = stream_get_contents($value); + } + return $value; + } + /** * sanitizes identifiers * @@ -883,21 +848,7 @@ class Database extends AbstractData */ private static function _sanitizeIdentifier($identifier) { - $id = preg_replace('/[^A-Za-z0-9_]+/', '', self::$_prefix . $identifier); - return self::_sanitizeColumn($id); - } - - /** - * sanitizes column name because OCI - * - * @access private - * @static - * @param string $name - * @return string - */ - private static function _sanitizeColumn($name) - { - return self::$_type === 'oci' ? strtoupper($name) : $name; + return preg_replace('/[^A-Za-z0-9_]+/', '', self::$_prefix . $identifier); } /** @@ -915,43 +866,43 @@ class Database extends AbstractData case '0.21': // create the meta column if necessary (pre 0.21 change) try { - self::$_db->exec('SELECT meta FROM ' . self::_sanitizeIdentifier('paste') . ' LIMIT 1;'); + self::$_db->exec('SELECT meta FROM ' . self::_sanitizeIdentifier('paste') . ' LIMIT 1'); } catch (PDOException $e) { - self::$_db->exec('ALTER TABLE ' . self::_sanitizeIdentifier('paste') . ' ADD COLUMN meta TEXT;'); + self::$_db->exec('ALTER TABLE ' . self::_sanitizeIdentifier('paste') . ' ADD COLUMN meta TEXT'); } // SQLite only allows one ALTER statement at a time... self::$_db->exec( 'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . - " ADD COLUMN attachment $attachmentType;" + " ADD COLUMN attachment $attachmentType" ); self::$_db->exec( - 'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . " ADD COLUMN attachmentname $dataType;" + 'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . " ADD COLUMN attachmentname $dataType" ); // SQLite doesn't support MODIFY, but it allows TEXT of similar // size as BLOB, so there is no need to change it there if (self::$_type !== 'sqlite') { self::$_db->exec( 'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . - " ADD PRIMARY KEY (dataid), MODIFY COLUMN data $dataType;" + " ADD PRIMARY KEY (dataid), MODIFY COLUMN data $dataType" ); self::$_db->exec( 'ALTER TABLE ' . self::_sanitizeIdentifier('comment') . " ADD PRIMARY KEY (dataid), MODIFY COLUMN data $dataType, " . - "MODIFY COLUMN nickname $dataType, MODIFY COLUMN vizhash $dataType;" + "MODIFY COLUMN nickname $dataType, MODIFY COLUMN vizhash $dataType" ); } else { self::$_db->exec( 'CREATE UNIQUE INDEX IF NOT EXISTS paste_dataid ON ' . - self::_sanitizeIdentifier('paste') . '(dataid);' + self::_sanitizeIdentifier('paste') . '(dataid)' ); self::$_db->exec( 'CREATE UNIQUE INDEX IF NOT EXISTS comment_dataid ON ' . - self::_sanitizeIdentifier('comment') . '(dataid);' + self::_sanitizeIdentifier('comment') . '(dataid)' ); } self::$_db->exec( 'CREATE INDEX IF NOT EXISTS comment_parent ON ' . - self::_sanitizeIdentifier('comment') . '(pasteid);' + self::_sanitizeIdentifier('comment') . '(pasteid)' ); // no break, continue with updates for 0.22 and later case '1.3': @@ -961,7 +912,7 @@ class Database extends AbstractData if (self::$_type !== 'sqlite' && self::$_type !== 'pgsql') { self::$_db->exec( 'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . - " MODIFY COLUMN data $attachmentType;" + " MODIFY COLUMN data $attachmentType" ); } // no break, continue with updates for all newer versions @@ -973,25 +924,4 @@ class Database extends AbstractData ); } } - - /** - * read CLOB for OCI - * https://stackoverflow.com/questions/36200534/pdo-oci-into-a-clob-field - * - * @access private - * @static - * @param resource $column - * @return string - */ - private static function _clob($column) - { - if ($column == null) { - return null; - } - $str = ''; - while ($tmp = fread($column, 1024)) { - $str .= $tmp; - } - return $str; - } } diff --git a/tst/Data/DatabaseTest.php b/tst/Data/DatabaseTest.php index 1cfc0bea..c433b5a1 100644 --- a/tst/Data/DatabaseTest.php +++ b/tst/Data/DatabaseTest.php @@ -388,4 +388,16 @@ class DatabaseTest extends PHPUnit_Framework_TestCase $this->assertEquals(Controller::VERSION, $result['value']); Helper::rmDir($this->_path); } + + public function testOciClob() + { + $int = (int) random_bytes(1); + $string = random_bytes(10); + $clob = fopen('php://memory', 'r+'); + fwrite($clob, $string); + rewind($clob); + $this->assertEquals($int, Database::_sanitizeClob($int)); + $this->assertEquals($string, Database::_sanitizeClob($string)); + $this->assertEquals($string, Database::_sanitizeClob($clob)); + } } From 585d5db983d3a2e25197c548c2e77d0a0fbb3061 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 22 Jan 2022 08:47:34 +0100 Subject: [PATCH 05/39] apply StyleCI recommendation --- tst/Data/DatabaseTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tst/Data/DatabaseTest.php b/tst/Data/DatabaseTest.php index c433b5a1..16e6fcb8 100644 --- a/tst/Data/DatabaseTest.php +++ b/tst/Data/DatabaseTest.php @@ -391,9 +391,9 @@ class DatabaseTest extends PHPUnit_Framework_TestCase public function testOciClob() { - $int = (int) random_bytes(1); + $int = (int) random_bytes(1); $string = random_bytes(10); - $clob = fopen('php://memory', 'r+'); + $clob = fopen('php://memory', 'r+'); fwrite($clob, $string); rewind($clob); $this->assertEquals($int, Database::_sanitizeClob($int)); From c725b4f0feb2f44cc46cc67b3236d29078fe8716 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 22 Jan 2022 21:29:39 +0100 Subject: [PATCH 06/39] handle 'IF NOT EXISTS' differently in OCI --- lib/Data/Database.php | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index f616a96a..59c7017b 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -792,10 +792,24 @@ class Database extends AbstractData "vizhash $dataType, " . "postdate INT$after_key )" ); - self::$_db->exec( - 'CREATE INDEX IF NOT EXISTS comment_parent ON ' . - self::_sanitizeIdentifier('comment') . '(pasteid)' - ); + if (self::$_type === 'oci') { + self::$_db->exec( + 'declare + already_exists exception; + columns_indexed exception; + pragma exception_init( already_exists, -955 ); + pragma exception_init(columns_indexed, -1408); + begin + execute immediate \'create index comment_parent on ' . self::_sanitizeIdentifier('comment') . ' (pasteid)\'; + exception + end' + ); + } else { + self::$_db->exec( + 'CREATE INDEX IF NOT EXISTS comment_parent ON ' . + self::_sanitizeIdentifier('comment') . '(pasteid)' + ); + } } /** From 35ef64ff7989839ebe4e3083f82d2317ce07c001 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 22 Jan 2022 22:11:49 +0100 Subject: [PATCH 07/39] remove duplication, kudos @rugk --- lib/Data/Database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 59c7017b..7dc56747 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -822,7 +822,7 @@ class Database extends AbstractData { list($main_key, $after_key) = self::_getPrimaryKeyClauses('id'); $charType = self::$_type === 'oci' ? 'VARCHAR2(16)' : 'CHAR(16)'; - $textType = self::$_type === 'oci' ? 'VARCHAR2(4000)' : 'TEXT'; + $textType = self::_getMetaType(); self::$_db->exec( 'CREATE TABLE ' . self::_sanitizeIdentifier('config') . " ( id $charType NOT NULL$main_key, value $textType$after_key )" From 83336b294904ff808b351ee449396482c47ce4a4 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 23 Jan 2022 07:11:06 +0100 Subject: [PATCH 08/39] documented changes for Postgres and Oracle --- INSTALL.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 0e728237..da6812e0 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -190,8 +190,14 @@ CREATE TABLE prefix_config ( INSERT INTO prefix_config VALUES('VERSION', '1.3.5'); ``` -In **PostgreSQL**, the data, attachment, nickname and vizhash columns needs to -be TEXT and not BLOB or MEDIUMBLOB. +In **PostgreSQL**, the `data`, `attachment`, `nickname` and `vizhash` columns +need to be `TEXT` and not `BLOB` or `MEDIUMBLOB`. The key names in brackets, +after `PRIMARY KEY`, need to be removed. + +In **Oracle**, the `data`, `attachment`, `nickname` and `vizhash` columns need +to be `CLOB` and not `BLOB` or `MEDIUMBLOB`, the `id` column in the `config` +table needs to be `VARCHAR2(16)` and the `meta` column in the `paste` table +and the `value` column in the `config` table need to be `VARCHAR2(4000)`. ### Using Google Cloud Storage If you want to deploy PrivateBin in a serverless manner in the Google Cloud, you From 47deaeb7ca960def189f232f3a79db53eb33494e Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 23 Jan 2022 07:11:36 +0100 Subject: [PATCH 09/39] use the correct function --- lib/Data/Database.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 7dc56747..d725bb3b 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -548,11 +548,11 @@ class Database extends AbstractData foreach ($params as $key => $parameter) { $position = $key + 1; if (is_int($parameter)) { - $statement->bindParam($position, $parameter, PDO::PARAM_INT); + $statement->bindValue($position, $parameter, PDO::PARAM_INT); } elseif ($length = strlen($parameter) >= 4000) { - $statement->bindParam($position, $parameter, PDO::PARAM_STR, $length); + $statement->bindValue($position, $parameter, PDO::PARAM_STR, $length); } else { - $statement->bindParam($position, $parameter); + $statement->bindValue($position, $parameter); } } $result = $statement->execute(); From b54308a77eb7baed177030e54d4d4379dac88c53 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 23 Jan 2022 07:19:35 +0100 Subject: [PATCH 10/39] don't mangle non-arrays --- lib/Data/Database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index d725bb3b..a0694b06 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -582,7 +582,7 @@ class Database extends AbstractData $statement->fetch(PDO::FETCH_ASSOC) : $statement->fetchAll(PDO::FETCH_ASSOC); $statement->closeCursor(); - if (self::$_type === 'oci') { + if (self::$_type === 'oci' && is_array($result)) { // returned column names are all upper case, convert these back // returned CLOB values are streams, convert these into strings $result = array_combine( From b133c2e2330927312206c3566072eadf25ee807e Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 23 Jan 2022 07:32:28 +0100 Subject: [PATCH 11/39] sanitize both single rows and multiple ones --- lib/Data/Database.php | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index a0694b06..8c73a206 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -585,10 +585,9 @@ class Database extends AbstractData if (self::$_type === 'oci' && is_array($result)) { // returned column names are all upper case, convert these back // returned CLOB values are streams, convert these into strings - $result = array_combine( - array_map('strtolower', array_keys($result)), - array_map('self::_sanitizeClob', array_values($result)) - ); + $result = $firstOnly ? + self::_sanitizeOciRow($result) : + array_map('self::_sanitizeOciRow', $result); } return $result; } @@ -839,7 +838,7 @@ class Database extends AbstractData * * From: https://stackoverflow.com/questions/36200534/pdo-oci-into-a-clob-field * - * @access private + * @access public * @static * @param int|string|resource $value * @return int|string @@ -865,6 +864,22 @@ class Database extends AbstractData return preg_replace('/[^A-Za-z0-9_]+/', '', self::$_prefix . $identifier); } + /** + * sanitizes row returned by OCI + * + * @access private + * @static + * @param array $row + * @return array + */ + private static function _sanitizeOciRow($row) + { + return array_combine( + array_map('strtolower', array_keys($row)), + array_map('self::_sanitizeClob', array_values($row)) + ); + } + /** * upgrade the database schema from an old version * From 0be55e05bf45c5761360e8f6bd1c224ba1b9fbac Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 23 Jan 2022 20:59:02 +0100 Subject: [PATCH 12/39] use quoted identifiers, tell MySQL to expect ANSI SQL --- lib/Data/Database.php | 203 ++++++++++++++++++++---------------------- 1 file changed, 98 insertions(+), 105 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 8c73a206..d0616a7d 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -97,6 +97,11 @@ class Database extends AbstractData self::$_type = strtolower( substr($options['dsn'], 0, strpos($options['dsn'], ':')) ); + // MySQL uses backticks to quote identifiers by default, + // tell it to expect ANSI SQL double quotes + if (self::$_type === 'mysql' && defined('PDO::MYSQL_ATTR_INIT_COMMAND')) { + $options['opt'][PDO::MYSQL_ATTR_INIT_COMMAND] = "SET sql_mode='ANSI_QUOTES'"; + } $tableQuery = self::_getTableQuery(self::$_type); self::$_db = new PDO( $options['dsn'], @@ -200,8 +205,8 @@ class Database extends AbstractData } try { return self::_exec( - 'INSERT INTO ' . self::_sanitizeIdentifier('paste') . - ' VALUES(?,?,?,?,?,?,?,?,?)', + 'INSERT INTO "' . self::_sanitizeIdentifier('paste') . + '" VALUES(?,?,?,?,?,?,?,?,?)', array( $pasteid, $isVersion1 ? $paste['data'] : Json::encode($paste), @@ -235,8 +240,8 @@ class Database extends AbstractData self::$_cache[$pasteid] = false; try { $paste = self::_select( - 'SELECT * FROM ' . self::_sanitizeIdentifier('paste') . - ' WHERE dataid = ?', array($pasteid), true + 'SELECT * FROM "' . self::_sanitizeIdentifier('paste') . + '" WHERE "dataid" = ?', array($pasteid), true ); } catch (Exception $e) { $paste = false; @@ -297,12 +302,12 @@ class Database extends AbstractData public function delete($pasteid) { self::_exec( - 'DELETE FROM ' . self::_sanitizeIdentifier('paste') . - ' WHERE dataid = ?', array($pasteid) + 'DELETE FROM "' . self::_sanitizeIdentifier('paste') . + '" WHERE "dataid" = ?', array($pasteid) ); self::_exec( - 'DELETE FROM ' . self::_sanitizeIdentifier('comment') . - ' WHERE pasteid = ?', array($pasteid) + 'DELETE FROM "' . self::_sanitizeIdentifier('comment') . + '" WHERE "pasteid" = ?', array($pasteid) ); if ( array_key_exists($pasteid, self::$_cache) @@ -357,8 +362,8 @@ class Database extends AbstractData } try { return self::_exec( - 'INSERT INTO ' . self::_sanitizeIdentifier('comment') . - ' VALUES(?,?,?,?,?,?,?)', + 'INSERT INTO "' . self::_sanitizeIdentifier('comment') . + '" VALUES(?,?,?,?,?,?,?)', array( $commentid, $pasteid, @@ -384,8 +389,8 @@ class Database extends AbstractData public function readComments($pasteid) { $rows = self::_select( - 'SELECT * FROM ' . self::_sanitizeIdentifier('comment') . - ' WHERE pasteid = ?', array($pasteid) + 'SELECT * FROM "' . self::_sanitizeIdentifier('comment') . + '" WHERE "pasteid" = ?', array($pasteid) ); // create comment list @@ -429,8 +434,8 @@ class Database extends AbstractData { try { return (bool) self::_select( - 'SELECT dataid FROM ' . self::_sanitizeIdentifier('comment') . - ' WHERE pasteid = ? AND parentid = ? AND dataid = ?', + 'SELECT "dataid" FROM "' . self::_sanitizeIdentifier('comment') . + '" WHERE "pasteid" = ? AND "parentid" = ? AND "dataid" = ?', array($pasteid, $parentid, $commentid), true ); } catch (Exception $e) { @@ -458,8 +463,8 @@ class Database extends AbstractData } } return self::_exec( - 'UPDATE ' . self::_sanitizeIdentifier('config') . - ' SET value = ? WHERE id = ?', + 'UPDATE "' . self::_sanitizeIdentifier('config') . + '" SET "value" = ? WHERE "id" = ?', array($value, strtoupper($namespace)) ); } @@ -479,8 +484,8 @@ class Database extends AbstractData if ($value === '') { // initialize the row, so that setValue can rely on UPDATE queries self::_exec( - 'INSERT INTO ' . self::_sanitizeIdentifier('config') . - ' VALUES(?,?)', + 'INSERT INTO "' . self::_sanitizeIdentifier('config') . + '" VALUES(?,?)', array($configKey, '') ); @@ -517,8 +522,8 @@ class Database extends AbstractData { $pastes = array(); $rows = self::_select( - 'SELECT dataid FROM ' . self::_sanitizeIdentifier('paste') . - ' WHERE expiredate < ? AND expiredate != ? ' . + 'SELECT "dataid" FROM "' . self::_sanitizeIdentifier('paste') . + '" WHERE "expiredate" < ? AND "expiredate" != ? ' . (self::$_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?'), array(time(), 0, $batchsize) ); @@ -586,8 +591,12 @@ class Database extends AbstractData // returned column names are all upper case, convert these back // returned CLOB values are streams, convert these into strings $result = $firstOnly ? - self::_sanitizeOciRow($result) : - array_map('self::_sanitizeOciRow', $result); + array_map('self::_sanitizeClob', $result) : + array_map( + function ($row) { + return array_map('self::_sanitizeClob', $row); + }, $result + ); } return $result; } @@ -621,39 +630,39 @@ class Database extends AbstractData { switch ($type) { case 'ibm': - $sql = 'SELECT tabname FROM SYSCAT.TABLES '; + $sql = 'SELECT "tabname" FROM "SYSCAT"."TABLES"'; break; case 'informix': - $sql = 'SELECT tabname FROM systables '; + $sql = 'SELECT "tabname" FROM "systables"'; break; case 'mssql': - $sql = 'SELECT name FROM sysobjects ' - . "WHERE type = 'U' ORDER BY name"; + $sql = 'SELECT "name" FROM "sysobjects" ' + . 'WHERE "type" = \'U\' ORDER BY "name"'; break; case 'mysql': $sql = 'SHOW TABLES'; break; case 'oci': - $sql = 'SELECT table_name FROM all_tables'; + $sql = 'SELECT "table_name" FROM "all_tables"'; break; case 'pgsql': - $sql = 'SELECT c.relname AS table_name ' - . 'FROM pg_class c, pg_user u ' - . "WHERE c.relowner = u.usesysid AND c.relkind = 'r' " - . 'AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) ' - . "AND c.relname !~ '^(pg_|sql_)' " + $sql = 'SELECT c."relname" AS "table_name" ' + . 'FROM "pg_class" c, "pg_user" u ' + . "WHERE c.\"relowner\" = u.\"usesysid\" AND c.\"relkind\" = 'r' " + . 'AND NOT EXISTS (SELECT 1 FROM "pg_views" WHERE "viewname" = c."relname") ' + . "AND c.\"relname\" !~ '^(pg_|sql_)' " . 'UNION ' - . 'SELECT c.relname AS table_name ' - . 'FROM pg_class c ' - . "WHERE c.relkind = 'r' " - . 'AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) ' - . 'AND NOT EXISTS (SELECT 1 FROM pg_user WHERE usesysid = c.relowner) ' - . "AND c.relname !~ '^pg_'"; + . 'SELECT c."relname" AS "table_name" ' + . 'FROM "pg_class" c ' + . "WHERE c.\"relkind\" = 'r' " + . 'AND NOT EXISTS (SELECT 1 FROM "pg_views" WHERE "viewname" = c."relname") ' + . 'AND NOT EXISTS (SELECT 1 FROM "pg_user" WHERE "usesysid" = c."relowner") ' + . "AND c.\"relname\" !~ '^pg_'"; break; case 'sqlite': - $sql = "SELECT name FROM sqlite_master WHERE type='table' " - . 'UNION ALL SELECT name FROM sqlite_temp_master ' - . "WHERE type='table' ORDER BY name"; + $sql = 'SELECT "name" FROM "sqlite_master" WHERE "type"=\'table\' ' + . 'UNION ALL SELECT "name" FROM "sqlite_temp_master" ' + . 'WHERE "type"=\'table\' ORDER BY "name"'; break; default: throw new Exception( @@ -675,8 +684,8 @@ class Database extends AbstractData { try { $row = self::_select( - 'SELECT value FROM ' . self::_sanitizeIdentifier('config') . - ' WHERE id = ?', array($key), true + 'SELECT "value" FROM "' . self::_sanitizeIdentifier('config') . + '" WHERE "id" = ?', array($key), true ); } catch (PDOException $e) { return ''; @@ -696,7 +705,7 @@ class Database extends AbstractData { $main_key = $after_key = ''; if (self::$_type === 'mysql' || self::$_type === 'oci') { - $after_key = ", PRIMARY KEY ($key)"; + $after_key = ", PRIMARY KEY (\"$key\")"; } else { $main_key = ' PRIMARY KEY'; } @@ -758,16 +767,16 @@ class Database extends AbstractData $attachmentType = self::_getAttachmentType(); $metaType = self::_getMetaType(); self::$_db->exec( - 'CREATE TABLE ' . self::_sanitizeIdentifier('paste') . ' ( ' . - "dataid CHAR(16) NOT NULL$main_key, " . - "data $dataType, " . - 'postdate INT, ' . - 'expiredate INT, ' . - 'opendiscussion INT, ' . - 'burnafterreading INT, ' . - "meta $metaType, " . - "attachment $attachmentType, " . - "attachmentname $dataType$after_key )" + 'CREATE TABLE "' . self::_sanitizeIdentifier('paste') . '" ( ' . + "\"dataid\" CHAR(16) NOT NULL$main_key, " . + "\"data\" $dataType, " . + '"postdate" INT, ' . + '"expiredate" INT, ' . + '"opendiscussion" INT, ' . + '"burnafterreading" INT, ' . + "\"meta\" $metaType, " . + "\"attachment\" $attachmentType, " . + "\"attachmentname\" $dataType$after_key )" ); } @@ -782,14 +791,14 @@ class Database extends AbstractData list($main_key, $after_key) = self::_getPrimaryKeyClauses(); $dataType = self::_getDataType(); self::$_db->exec( - 'CREATE TABLE ' . self::_sanitizeIdentifier('comment') . ' ( ' . - "dataid CHAR(16) NOT NULL$main_key, " . - 'pasteid CHAR(16), ' . - 'parentid CHAR(16), ' . - "data $dataType, " . - "nickname $dataType, " . - "vizhash $dataType, " . - "postdate INT$after_key )" + 'CREATE TABLE "' . self::_sanitizeIdentifier('comment') . '" ( ' . + "\"dataid\" CHAR(16) NOT NULL$main_key, " . + '"pasteid" CHAR(16), ' . + '"parentid" CHAR(16), ' . + "\"data\" $dataType, " . + "\"nickname\" $dataType, " . + "\"vizhash\" $dataType, " . + "\"postdate\" INT$after_key )" ); if (self::$_type === 'oci') { self::$_db->exec( @@ -799,14 +808,14 @@ class Database extends AbstractData pragma exception_init( already_exists, -955 ); pragma exception_init(columns_indexed, -1408); begin - execute immediate \'create index comment_parent on ' . self::_sanitizeIdentifier('comment') . ' (pasteid)\'; + execute immediate \'create index "comment_parent" on "' . self::_sanitizeIdentifier('comment') . '" ("pasteid")\'; exception end' ); } else { self::$_db->exec( - 'CREATE INDEX IF NOT EXISTS comment_parent ON ' . - self::_sanitizeIdentifier('comment') . '(pasteid)' + 'CREATE INDEX IF NOT EXISTS "comment_parent" ON "' . + self::_sanitizeIdentifier('comment') . '" ("pasteid")' ); } } @@ -823,12 +832,12 @@ class Database extends AbstractData $charType = self::$_type === 'oci' ? 'VARCHAR2(16)' : 'CHAR(16)'; $textType = self::_getMetaType(); self::$_db->exec( - 'CREATE TABLE ' . self::_sanitizeIdentifier('config') . - " ( id $charType NOT NULL$main_key, value $textType$after_key )" + 'CREATE TABLE "' . self::_sanitizeIdentifier('config') . + "\" ( \"id\" $charType NOT NULL$main_key, \"value\" $textType$after_key )" ); self::_exec( - 'INSERT INTO ' . self::_sanitizeIdentifier('config') . - ' VALUES(?,?)', + 'INSERT INTO "' . self::_sanitizeIdentifier('config') . + '" VALUES(?,?)', array('VERSION', Controller::VERSION) ); } @@ -864,22 +873,6 @@ class Database extends AbstractData return preg_replace('/[^A-Za-z0-9_]+/', '', self::$_prefix . $identifier); } - /** - * sanitizes row returned by OCI - * - * @access private - * @static - * @param array $row - * @return array - */ - private static function _sanitizeOciRow($row) - { - return array_combine( - array_map('strtolower', array_keys($row)), - array_map('self::_sanitizeClob', array_values($row)) - ); - } - /** * upgrade the database schema from an old version * @@ -895,43 +888,43 @@ class Database extends AbstractData case '0.21': // create the meta column if necessary (pre 0.21 change) try { - self::$_db->exec('SELECT meta FROM ' . self::_sanitizeIdentifier('paste') . ' LIMIT 1'); + self::$_db->exec('SELECT "meta" FROM "' . self::_sanitizeIdentifier('paste') . '" LIMIT 1'); } catch (PDOException $e) { - self::$_db->exec('ALTER TABLE ' . self::_sanitizeIdentifier('paste') . ' ADD COLUMN meta TEXT'); + self::$_db->exec('ALTER TABLE "' . self::_sanitizeIdentifier('paste') . '" ADD COLUMN "meta" TEXT'); } // SQLite only allows one ALTER statement at a time... self::$_db->exec( - 'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . - " ADD COLUMN attachment $attachmentType" + 'ALTER TABLE "' . self::_sanitizeIdentifier('paste') . + "\" ADD COLUMN \"attachment\" $attachmentType" ); self::$_db->exec( - 'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . " ADD COLUMN attachmentname $dataType" + 'ALTER TABLE "' . self::_sanitizeIdentifier('paste') . "\" ADD COLUMN \"attachmentname\" $dataType" ); // SQLite doesn't support MODIFY, but it allows TEXT of similar // size as BLOB, so there is no need to change it there if (self::$_type !== 'sqlite') { self::$_db->exec( - 'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . - " ADD PRIMARY KEY (dataid), MODIFY COLUMN data $dataType" + 'ALTER TABLE "' . self::_sanitizeIdentifier('paste') . + "\" ADD PRIMARY KEY (\"dataid\"), MODIFY COLUMN \"data\" $dataType" ); self::$_db->exec( - 'ALTER TABLE ' . self::_sanitizeIdentifier('comment') . - " ADD PRIMARY KEY (dataid), MODIFY COLUMN data $dataType, " . - "MODIFY COLUMN nickname $dataType, MODIFY COLUMN vizhash $dataType" + 'ALTER TABLE "' . self::_sanitizeIdentifier('comment') . + "\" ADD PRIMARY KEY (\"dataid\"), MODIFY COLUMN \"data\" $dataType, " . + "MODIFY COLUMN \"nickname\" $dataType, MODIFY COLUMN \"vizhash\" $dataType" ); } else { self::$_db->exec( - 'CREATE UNIQUE INDEX IF NOT EXISTS paste_dataid ON ' . - self::_sanitizeIdentifier('paste') . '(dataid)' + 'CREATE UNIQUE INDEX IF NOT EXISTS "paste_dataid" ON "' . + self::_sanitizeIdentifier('paste') . '" ("dataid")' ); self::$_db->exec( - 'CREATE UNIQUE INDEX IF NOT EXISTS comment_dataid ON ' . - self::_sanitizeIdentifier('comment') . '(dataid)' + 'CREATE UNIQUE INDEX IF NOT EXISTS "comment_dataid" ON "' . + self::_sanitizeIdentifier('comment') . '" ("dataid")' ); } self::$_db->exec( - 'CREATE INDEX IF NOT EXISTS comment_parent ON ' . - self::_sanitizeIdentifier('comment') . '(pasteid)' + 'CREATE INDEX IF NOT EXISTS "comment_parent" ON "' . + self::_sanitizeIdentifier('comment') . '" ("pasteid")' ); // no break, continue with updates for 0.22 and later case '1.3': @@ -940,15 +933,15 @@ class Database extends AbstractData // to change it there if (self::$_type !== 'sqlite' && self::$_type !== 'pgsql') { self::$_db->exec( - 'ALTER TABLE ' . self::_sanitizeIdentifier('paste') . - " MODIFY COLUMN data $attachmentType" + 'ALTER TABLE "' . self::_sanitizeIdentifier('paste') . + "\" MODIFY COLUMN \"data\" $attachmentType" ); } // no break, continue with updates for all newer versions default: self::_exec( - 'UPDATE ' . self::_sanitizeIdentifier('config') . - ' SET value = ? WHERE id = ?', + 'UPDATE "' . self::_sanitizeIdentifier('config') . + '" SET "value" = ? WHERE "id" = ?', array(Controller::VERSION, 'VERSION') ); } From 8d6392192428d6a0099fa0cc29ec8c52d7c99deb Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 23 Jan 2022 21:24:28 +0100 Subject: [PATCH 13/39] workaround bug in OCI PDO driver --- lib/Data/Database.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index d0616a7d..1e9adea4 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -583,9 +583,17 @@ class Database extends AbstractData { $statement = self::$_db->prepare($sql); $statement->execute($params); - $result = $firstOnly ? - $statement->fetch(PDO::FETCH_ASSOC) : - $statement->fetchAll(PDO::FETCH_ASSOC); + if ($firstOnly) { + $result = $statement->fetch(PDO::FETCH_ASSOC); + } elseif (self::$_type === 'oci') { + // workaround for https://bugs.php.net/bug.php?id=46728 + $result = array(); + while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { + $result[] = $row; + } + } else { + $result = $statement->fetchAll(PDO::FETCH_ASSOC); + } $statement->closeCursor(); if (self::$_type === 'oci' && is_array($result)) { // returned column names are all upper case, convert these back From 4f051fe5a5d7891e0d3d2f69ea9787ea637114f8 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 23 Jan 2022 21:31:40 +0100 Subject: [PATCH 14/39] revert regression --- lib/Data/Database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 1e9adea4..1441648c 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -777,7 +777,7 @@ class Database extends AbstractData self::$_db->exec( 'CREATE TABLE "' . self::_sanitizeIdentifier('paste') . '" ( ' . "\"dataid\" CHAR(16) NOT NULL$main_key, " . - "\"data\" $dataType, " . + "\"data\" $attachmentType, " . '"postdate" INT, ' . '"expiredate" INT, ' . '"opendiscussion" INT, ' . From 0cc2b677538ac948132c1ee674c433ccdf104640 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 23 Jan 2022 21:45:22 +0100 Subject: [PATCH 15/39] bindValue doesn't need the length --- lib/Data/Database.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 1441648c..583c4008 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -395,7 +395,7 @@ class Database extends AbstractData // create comment list $comments = array(); - if (count($rows)) { + if (is_array($rows) && count($rows)) { foreach ($rows as $row) { $i = $this->getOpenSlot($comments, (int) $row['postdate']); $data = Json::decode($row['data']); @@ -527,7 +527,7 @@ class Database extends AbstractData (self::$_type === 'oci' ? 'FETCH NEXT ? ROWS ONLY' : 'LIMIT ?'), array(time(), 0, $batchsize) ); - if (count($rows)) { + if (is_array($rows) && count($rows)) { foreach ($rows as $row) { $pastes[] = $row['dataid']; } @@ -554,8 +554,6 @@ class Database extends AbstractData $position = $key + 1; if (is_int($parameter)) { $statement->bindValue($position, $parameter, PDO::PARAM_INT); - } elseif ($length = strlen($parameter) >= 4000) { - $statement->bindValue($position, $parameter, PDO::PARAM_STR, $length); } else { $statement->bindValue($position, $parameter); } From a8e1c33b54ed612fd7fdbdb00a2663ee1ebdb9f3 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 24 Jan 2022 17:26:09 +0100 Subject: [PATCH 16/39] stick to single convention of binding parameters --- lib/Data/Database.php | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 583c4008..92aeec75 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -548,20 +548,15 @@ class Database extends AbstractData private static function _exec($sql, array $params) { $statement = self::$_db->prepare($sql); - if (self::$_type === 'oci') { - // It is not possible to execute in the normal way if strlen($param) >= 4000 - foreach ($params as $key => $parameter) { - $position = $key + 1; - if (is_int($parameter)) { - $statement->bindValue($position, $parameter, PDO::PARAM_INT); - } else { - $statement->bindValue($position, $parameter); - } + foreach ($params as $key => $parameter) { + $position = $key + 1; + if (is_int($parameter)) { + $statement->bindValue($position, $parameter, PDO::PARAM_INT); + } else { + $statement->bindValue($position, $parameter); } - $result = $statement->execute(); - } else { - $result = $statement->execute($params); } + $result = $statement->execute(); $statement->closeCursor(); return $result; } From 56c54dd8800399b16403ff9901e8a3048811a7aa Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 24 Jan 2022 17:48:27 +0100 Subject: [PATCH 17/39] prefer switch statements for complex logic, all comparing the same variable --- lib/Data/Database.php | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 92aeec75..27e71c9a 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -705,10 +705,14 @@ class Database extends AbstractData private static function _getPrimaryKeyClauses($key = 'dataid') { $main_key = $after_key = ''; - if (self::$_type === 'mysql' || self::$_type === 'oci') { - $after_key = ", PRIMARY KEY (\"$key\")"; - } else { - $main_key = ' PRIMARY KEY'; + switch (self::$_type) { + case 'mysql': + case 'oci': + $after_key = ", PRIMARY KEY (\"$key\")"; + break; + default: + $main_key = ' PRIMARY KEY'; + break; } return array($main_key, $after_key); } @@ -724,7 +728,14 @@ class Database extends AbstractData */ private static function _getDataType() { - return self::$_type === 'pgsql' ? 'TEXT' : (self::$_type === 'oci' ? 'CLOB' : 'BLOB'); + switch (self::$_type) { + case 'oci': + return 'CLOB'; + case 'pgsql': + return 'TEXT'; + default: + return 'BLOB'; + } } /** @@ -738,7 +749,14 @@ class Database extends AbstractData */ private static function _getAttachmentType() { - return self::$_type === 'pgsql' ? 'TEXT' : (self::$_type === 'oci' ? 'CLOB' : 'MEDIUMBLOB'); + switch (self::$_type) { + case 'oci': + return 'CLOB'; + case 'pgsql': + return 'TEXT'; + default: + return 'MEDIUMBLOB'; + } } /** @@ -752,7 +770,12 @@ class Database extends AbstractData */ private static function _getMetaType() { - return self::$_type === 'oci' ? 'VARCHAR2(4000)' : 'TEXT'; + switch (self::$_type) { + case 'oci': + return 'VARCHAR2(4000)'; + default: + return 'TEXT'; + } } /** From 0b6af67b99151905541e5dad65246051192f3bc8 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 24 Jan 2022 17:50:24 +0100 Subject: [PATCH 18/39] removed obsolete comment --- lib/Data/Database.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 27e71c9a..080da533 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -589,7 +589,6 @@ class Database extends AbstractData } $statement->closeCursor(); if (self::$_type === 'oci' && is_array($result)) { - // returned column names are all upper case, convert these back // returned CLOB values are streams, convert these into strings $result = $firstOnly ? array_map('self::_sanitizeClob', $result) : From b8e8755fb1f70e5259323e2dcb16de49fefb1dba Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 24 Jan 2022 21:36:18 +0100 Subject: [PATCH 19/39] Basically it wants a non-empty catch statement Co-authored-by: Austin Huang --- lib/Data/Database.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 080da533..c9901dc4 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -833,7 +833,9 @@ class Database extends AbstractData begin execute immediate \'create index "comment_parent" on "' . self::_sanitizeIdentifier('comment') . '" ("pasteid")\'; exception - end' + when already_exists or columns_indexed then + NULL; + end;' ); } else { self::$_db->exec( From 0c4852c099cd1eb9015cdfac31cc7613b10c5b82 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 24 Jan 2022 21:40:10 +0100 Subject: [PATCH 20/39] this fixes the comment display issue Co-authored-by: Austin Huang --- lib/Data/Database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index c9901dc4..8dcbe0c5 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -582,7 +582,7 @@ class Database extends AbstractData // workaround for https://bugs.php.net/bug.php?id=46728 $result = array(); while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { - $result[] = $row; + $result[] = array_map('self::_sanitizeClob', $row); } } else { $result = $statement->fetchAll(PDO::FETCH_ASSOC); From 535f038daaa1b558e3ae8f3093481c499d190635 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 24 Jan 2022 21:43:31 +0100 Subject: [PATCH 21/39] handle `LIMIT` in oci Co-authored-by: Austin Huang --- lib/Data/Database.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 8dcbe0c5..aed8db69 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -913,7 +913,10 @@ class Database extends AbstractData case '0.21': // create the meta column if necessary (pre 0.21 change) try { - self::$_db->exec('SELECT "meta" FROM "' . self::_sanitizeIdentifier('paste') . '" LIMIT 1'); + self::$_db->exec( + 'SELECT "meta" FROM "' . self::_sanitizeIdentifier('paste') . '" ' . + (self::$_type === 'oci' ? 'FETCH NEXT 1 ROWS ONLY' : 'LIMIT 1') + ); } catch (PDOException $e) { self::$_db->exec('ALTER TABLE "' . self::_sanitizeIdentifier('paste') . '" ADD COLUMN "meta" TEXT'); } From 55db9426b99bd7514fa2bb12b29c15a9c379d7bf Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 24 Jan 2022 21:43:48 +0100 Subject: [PATCH 22/39] Throws `ORA-00942: table or view does not exist` otherwise Co-authored-by: Austin Huang --- lib/Data/Database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index aed8db69..657107bf 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -643,7 +643,7 @@ class Database extends AbstractData $sql = 'SHOW TABLES'; break; case 'oci': - $sql = 'SELECT "table_name" FROM "all_tables"'; + $sql = 'SELECT table_name FROM all_tables'; break; case 'pgsql': $sql = 'SELECT c."relname" AS "table_name" ' From f4438a01036bb53e42b879a6da6e6f63052453d6 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Mon, 24 Jan 2022 21:44:20 +0100 Subject: [PATCH 23/39] inserting CLOB absolutely requires a length argument Co-authored-by: Austin Huang --- lib/Data/Database.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 657107bf..0e605987 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -548,12 +548,13 @@ class Database extends AbstractData private static function _exec($sql, array $params) { $statement = self::$_db->prepare($sql); - foreach ($params as $key => $parameter) { - $position = $key + 1; + foreach ($params as $key => &$parameter) { if (is_int($parameter)) { - $statement->bindValue($position, $parameter, PDO::PARAM_INT); + $statement->bindParam($key + 1, $parameter, PDO::PARAM_INT); + } elseif (strlen($parameter) >= 4000) { + $statement->bindParam($key + 1, $parameter, PDO::PARAM_STR, strlen($parameter)); } else { - $statement->bindValue($position, $parameter); + $statement->bindParam($key + 1, $parameter); } } $result = $statement->execute(); From 0333777a37c77cd9540962d231306b0a09a6df12 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Tue, 25 Jan 2022 05:59:22 +0100 Subject: [PATCH 24/39] remove duplicate CLOB sanitation --- lib/Data/Database.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 0e605987..3a9a8060 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -593,11 +593,7 @@ class Database extends AbstractData // returned CLOB values are streams, convert these into strings $result = $firstOnly ? array_map('self::_sanitizeClob', $result) : - array_map( - function ($row) { - return array_map('self::_sanitizeClob', $row); - }, $result - ); + $result; } return $result; } From 53c0e4976bf2321e4da0205e95a068c1652f98d5 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Wed, 26 Jan 2022 05:26:47 +0100 Subject: [PATCH 25/39] document what the U type stands for --- lib/Data/Database.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index 3a9a8060..babcd254 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -633,6 +633,7 @@ class Database extends AbstractData $sql = 'SELECT "tabname" FROM "systables"'; break; case 'mssql': + // U: tables created by the user $sql = 'SELECT "name" FROM "sysobjects" ' . 'WHERE "type" = \'U\' ORDER BY "name"'; break; From 1d20eee1693a67a6ce71d50458122e0f6ebbe915 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Wed, 26 Jan 2022 05:28:29 +0100 Subject: [PATCH 26/39] readability --- lib/Data/Database.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index babcd254..a35726cb 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -549,12 +549,13 @@ class Database extends AbstractData { $statement = self::$_db->prepare($sql); foreach ($params as $key => &$parameter) { + $position = $key + 1; if (is_int($parameter)) { - $statement->bindParam($key + 1, $parameter, PDO::PARAM_INT); + $statement->bindParam($position, $parameter, PDO::PARAM_INT); } elseif (strlen($parameter) >= 4000) { - $statement->bindParam($key + 1, $parameter, PDO::PARAM_STR, strlen($parameter)); + $statement->bindParam($position, $parameter, PDO::PARAM_STR, strlen($parameter)); } else { - $statement->bindParam($key + 1, $parameter); + $statement->bindParam($position, $parameter); } } $result = $statement->execute(); From 29ffd25c181352a327e0dd99d512f394b88dc3b9 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 30 Jan 2022 21:42:24 +0100 Subject: [PATCH 27/39] apply suggestion of @r4sas --- lib/Data/Database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Data/Database.php b/lib/Data/Database.php index a35726cb..03e60612 100644 --- a/lib/Data/Database.php +++ b/lib/Data/Database.php @@ -647,7 +647,7 @@ class Database extends AbstractData case 'pgsql': $sql = 'SELECT c."relname" AS "table_name" ' . 'FROM "pg_class" c, "pg_user" u ' - . "WHERE c.\"relowner\" = u.\"usesysid\" AND c.\"relkind\" = 'r' " + . 'WHERE c."relowner" = u."usesysid" AND c."relkind" = \'r\' ' . 'AND NOT EXISTS (SELECT 1 FROM "pg_views" WHERE "viewname" = c."relname") ' . "AND c.\"relname\" !~ '^(pg_|sql_)' " . 'UNION ' From 401cd32d07ce41f2ea71d7fe19298c864896239f Mon Sep 17 00:00:00 2001 From: "foxsouns - SEE ME @ GITLAB" Date: Wed, 9 Feb 2022 22:30:53 -0800 Subject: [PATCH 28/39] add jb (lojban) into supported languages list --- js/privatebin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/privatebin.js b/js/privatebin.js index 6218700a..860a2bd7 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -601,7 +601,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @prop {string[]} * @readonly */ - const supportedLanguages = ['bg', 'ca', 'cs', 'de', 'es', 'et', 'fr', 'he', 'hu', 'id', 'it', 'lt', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sl', 'uk', 'zh']; + const supportedLanguages = ['bg', 'ca', 'cs', 'de', 'es', 'et', 'fr', 'he', 'hu', 'id', 'it', 'jb', 'lt', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sl', 'uk', 'zh']; /** * built in language From cadfb0919339a1dc9de6210e15a256217d1d88f3 Mon Sep 17 00:00:00 2001 From: "foxsouns - SEE ME @ GITLAB" Date: Wed, 9 Feb 2022 22:39:09 -0800 Subject: [PATCH 29/39] copy en.json into jb.json --- i18n/jb.json | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 i18n/jb.json diff --git a/i18n/jb.json b/i18n/jb.json new file mode 100644 index 00000000..a96bab5d --- /dev/null +++ b/i18n/jb.json @@ -0,0 +1,189 @@ +{ + "PrivateBin": "PrivateBin", + "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.", + "More information on the project page.": "More information on the project page.", + "Because ignorance is bliss": "Because ignorance is bliss", + "en": "en", + "Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.", + "%s requires php %s or above to work. Sorry.": "%s requires php %s or above to work. Sorry.", + "%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.", + "Please wait %d seconds between each post.": [ + "Please wait %d second between each post. (singular)", + "Please wait %d seconds between each post. (1st plural)", + "Please wait %d seconds between each post. (2nd plural)", + "Please wait %d seconds between each post. (3rd plural)" + ], + "Paste is limited to %s of encrypted data.": "Paste is limited to %s of encrypted data.", + "Invalid data.": "Invalid data.", + "You are unlucky. Try again.": "You are unlucky. Try again.", + "Error saving comment. Sorry.": "Error saving comment. Sorry.", + "Error saving paste. Sorry.": "Error saving paste. Sorry.", + "Invalid paste ID.": "Invalid paste ID.", + "Paste is not of burn-after-reading type.": "Paste is not of burn-after-reading type.", + "Wrong deletion token. Paste was not deleted.": "Wrong deletion token. Paste was not deleted.", + "Paste was properly deleted.": "Paste was properly deleted.", + "JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.", + "%s requires a modern browser to work.": "%s requires a modern browser to work.", + "New": "New", + "Send": "Send", + "Clone": "Clone", + "Raw text": "Raw text", + "Expires": "Expires", + "Burn after reading": "Burn after reading", + "Open discussion": "Open discussion", + "Password (recommended)": "Password (recommended)", + "Discussion": "Discussion", + "Toggle navigation": "Toggle navigation", + "%d seconds": [ + "%d second (singular)", + "%d seconds (1st plural)", + "%d seconds (2nd plural)", + "%d seconds (3rd plural)" + ], + "%d minutes": [ + "%d minute (singular)", + "%d minutes (1st plural)", + "%d minutes (2nd plural)", + "%d minutes (3rd plural)" + ], + "%d hours": [ + "%d hour (singular)", + "%d hours (1st plural)", + "%d hours (2nd plural)", + "%d hours (3rd plural)" + ], + "%d days": [ + "%d day (singular)", + "%d days (1st plural)", + "%d days (2nd plural)", + "%d days (3rd plural)" + ], + "%d weeks": [ + "%d week (singular)", + "%d weeks (1st plural)", + "%d weeks (2nd plural)", + "%d weeks (3rd plural)" + ], + "%d months": [ + "%d month (singular)", + "%d months (1st plural)", + "%d months (2nd plural)", + "%d months (3rd plural)" + ], + "%d years": [ + "%d year (singular)", + "%d years (1st plural)", + "%d years (2nd plural)", + "%d years (3rd plural)" + ], + "Never": "Never", + "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.": "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.", + "This document will expire in %d seconds.": [ + "This document will expire in %d second. (singular)", + "This document will expire in %d seconds. (1st plural)", + "This document will expire in %d seconds. (2nd plural)", + "This document will expire in %d seconds. (3rd plural)" + ], + "This document will expire in %d minutes.": [ + "This document will expire in %d minute. (singular)", + "This document will expire in %d minutes. (1st plural)", + "This document will expire in %d minutes. (2nd plural)", + "This document will expire in %d minutes. (3rd plural)" + ], + "This document will expire in %d hours.": [ + "This document will expire in %d hour. (singular)", + "This document will expire in %d hours. (1st plural)", + "This document will expire in %d hours. (2nd plural)", + "This document will expire in %d hours. (3rd plural)" + ], + "This document will expire in %d days.": [ + "This document will expire in %d day. (singular)", + "This document will expire in %d days. (1st plural)", + "This document will expire in %d days. (2nd plural)", + "This document will expire in %d days. (3rd plural)" + ], + "This document will expire in %d months.": [ + "This document will expire in %d month. (singular)", + "This document will expire in %d months. (1st plural)", + "This document will expire in %d months. (2nd plural)", + "This document will expire in %d months. (3rd plural)" + ], + "Please enter the password for this paste:": "Please enter the password for this paste:", + "Could not decrypt data (Wrong key?)": "Could not decrypt data (Wrong key?)", + "Could not delete the paste, it was not stored in burn after reading mode.": "Could not delete the paste, it was not stored in burn after reading mode.", + "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.": "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.", + "Could not decrypt comment; Wrong key?": "Could not decrypt comment; Wrong key?", + "Reply": "Reply", + "Anonymous": "Anonymous", + "Avatar generated from IP address": "Avatar generated from IP address", + "Add comment": "Add comment", + "Optional nickname…": "Optional nickname…", + "Post comment": "Post comment", + "Sending comment…": "Sending comment…", + "Comment posted.": "Comment posted.", + "Could not refresh display: %s": "Could not refresh display: %s", + "unknown status": "unknown status", + "server error or not responding": "server error or not responding", + "Could not post comment: %s": "Could not post comment: %s", + "Sending paste…": "Sending paste…", + "Your paste is %s (Hit [Ctrl]+[c] to copy)": "Your paste is %s (Hit [Ctrl]+[c] to copy)", + "Delete data": "Delete data", + "Could not create paste: %s": "Could not create paste: %s", + "Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)": "Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)", + "B": "B", + "KiB": "KiB", + "MiB": "MiB", + "GiB": "GiB", + "TiB": "TiB", + "PiB": "PiB", + "EiB": "EiB", + "ZiB": "ZiB", + "YiB": "YiB", + "Format": "Format", + "Plain Text": "Plain Text", + "Source Code": "Source Code", + "Markdown": "Markdown", + "Download attachment": "Download attachment", + "Cloned: '%s'": "Cloned: '%s'", + "The cloned file '%s' was attached to this paste.": "The cloned file '%s' was attached to this paste.", + "Attach a file": "Attach a file", + "alternatively drag & drop a file or paste an image from the clipboard": "alternatively drag & drop a file or paste an image from the clipboard", + "File too large, to display a preview. Please download the attachment.": "File too large, to display a preview. Please download the attachment.", + "Remove attachment": "Remove attachment", + "Your browser does not support uploading encrypted files. Please use a newer browser.": "Your browser does not support uploading encrypted files. Please use a newer browser.", + "Invalid attachment.": "Invalid attachment.", + "Options": "Options", + "Shorten URL": "Shorten URL", + "Editor": "Editor", + "Preview": "Preview", + "%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.": "%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.", + "Decrypt": "Decrypt", + "Enter password": "Enter password", + "Loading…": "Loading…", + "Decrypting paste…": "Decrypting paste…", + "Preparing new paste…": "Preparing new paste…", + "In case this message never disappears please have a look at this FAQ for information to troubleshoot.": "In case this message never disappears please have a look at this FAQ for information to troubleshoot.", + "+++ no paste text +++": "+++ no paste text +++", + "Could not get paste data: %s": "Could not get paste data: %s", + "QR code": "QR code", + "This website is using an insecure HTTP connection! Please use it only for testing.": "This website is using an insecure HTTP connection! Please use it only for testing.", + "For more information see this FAQ entry.": "For more information see this FAQ entry.", + "Your browser may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.": "Your browser may require an HTTPS connection to support the WebCrypto API. Try switching to HTTPS.", + "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.": "Your browser doesn't support WebAssembly, used for zlib compression. You can create uncompressed documents, but can't read compressed ones.", + "waiting on user to provide a password": "waiting on user to provide a password", + "Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.": "Could not decrypt data. Did you enter a wrong password? Retry with the button at the top.", + "Retry": "Retry", + "Showing raw text…": "Showing raw text…", + "Notice:": "Notice:", + "This link will expire after %s.": "This link will expire after %s.", + "This link can only be accessed once, do not use back or refresh button in your browser.": "This link can only be accessed once, do not use back or refresh button in your browser.", + "Link:": "Link:", + "Recipient may become aware of your timezone, convert time to UTC?": "Recipient may become aware of your timezone, convert time to UTC?", + "Use Current Timezone": "Use Current Timezone", + "Convert To UTC": "Convert To UTC", + "Close": "Close", + "Encrypted note on PrivateBin": "Encrypted note on PrivateBin", + "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.": "Visit this link to see the note. Giving the URL to anyone allows them to access the note, too.", + "URL shortener may expose your decrypt key in URL.": "URL shortener may expose your decrypt key in URL.", + "Save paste": "Save paste" +} From 79abba5124f8e52a46ddd9cfc049a7419cb6948c Mon Sep 17 00:00:00 2001 From: "foxsouns - SEE ME @ GITLAB" Date: Wed, 9 Feb 2022 22:44:22 -0800 Subject: [PATCH 30/39] Update jb.json baby steps --- i18n/jb.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/jb.json b/i18n/jb.json index a96bab5d..23c6c2d4 100644 --- a/i18n/jb.json +++ b/i18n/jb.json @@ -1,9 +1,9 @@ { - "PrivateBin": "PrivateBin", + "PrivateBin": "sivnibot", "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.", "More information on the project page.": "More information on the project page.", "Because ignorance is bliss": "Because ignorance is bliss", - "en": "en", + "en": "jb", "Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.", "%s requires php %s or above to work. Sorry.": "%s requires php %s or above to work. Sorry.", "%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.", From 08d8922aaded9a6d039d4631f9997b03e765e9cf Mon Sep 17 00:00:00 2001 From: "foxsouns - SEE ME @ GITLAB" Date: Wed, 9 Feb 2022 22:50:19 -0800 Subject: [PATCH 31/39] change name to the more sensical "patxu" --- i18n/jb.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/jb.json b/i18n/jb.json index 23c6c2d4..42069d87 100644 --- a/i18n/jb.json +++ b/i18n/jb.json @@ -1,5 +1,5 @@ { - "PrivateBin": "sivnibot", + "PrivateBin": "sivnipax", "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.", "More information on the project page.": "More information on the project page.", "Because ignorance is bliss": "Because ignorance is bliss", From 902e7cf480f5a3123b2875a35999c8a24510b77f Mon Sep 17 00:00:00 2001 From: "foxsouns - SEE ME @ GITLAB" Date: Wed, 9 Feb 2022 23:27:54 -0800 Subject: [PATCH 32/39] add lojban --- i18n/languages.json | 1 + 1 file changed, 1 insertion(+) diff --git a/i18n/languages.json b/i18n/languages.json index a2aff4a9..671b3f75 100644 --- a/i18n/languages.json +++ b/i18n/languages.json @@ -89,6 +89,7 @@ "ku": ["Kurdî", "Kurdish"], "kj": ["Kuanyama", "Kwanyama"], "la": ["lingua latina", "Latin"], + "jb": ["jbobau", "Lojban"], "lb": ["Lëtzebuergesch", "Luxembourgish"], "lg": ["Luganda", "Ganda"], "li": ["Limburgs", "Limburgish"], From 77dd0b027f154b36a81c20dd0f62d010fd5bc4fe Mon Sep 17 00:00:00 2001 From: "foxsouns - SEE ME @ GITLAB" Date: Thu, 10 Feb 2022 00:30:14 -0800 Subject: [PATCH 33/39] roughly 2% of random lojban stuff --- i18n/jb.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/i18n/jb.json b/i18n/jb.json index 42069d87..6e8e08f5 100644 --- a/i18n/jb.json +++ b/i18n/jb.json @@ -2,7 +2,7 @@ "PrivateBin": "sivnipax", "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.", "More information on the project page.": "More information on the project page.", - "Because ignorance is bliss": "Because ignorance is bliss", + "Because ignorance is bliss": ".i ki'u le ka na djuno cu ka saxfri", "en": "jb", "Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.", "%s requires php %s or above to work. Sorry.": "%s requires php %s or above to work. Sorry.", @@ -14,7 +14,7 @@ "Please wait %d seconds between each post. (3rd plural)" ], "Paste is limited to %s of encrypted data.": "Paste is limited to %s of encrypted data.", - "Invalid data.": "Invalid data.", + "Invalid data.": ".i le selru'e cu na drani", "You are unlucky. Try again.": "You are unlucky. Try again.", "Error saving comment. Sorry.": "Error saving comment. Sorry.", "Error saving paste. Sorry.": "Error saving paste. Sorry.", @@ -24,10 +24,10 @@ "Paste was properly deleted.": "Paste was properly deleted.", "JavaScript is required for %s to work. Sorry for the inconvenience.": "JavaScript is required for %s to work. Sorry for the inconvenience.", "%s requires a modern browser to work.": "%s requires a modern browser to work.", - "New": "New", - "Send": "Send", - "Clone": "Clone", - "Raw text": "Raw text", + "New": "cnino", + "Send": "benji", + "Clone": "fukpi", + "Raw text": "vlapoi nalselrucyzu'e", "Expires": "Expires", "Burn after reading": "Burn after reading", "Open discussion": "Open discussion", From 1d6bcb1f5746d6d669a16496db293735f9ffddca Mon Sep 17 00:00:00 2001 From: "foxsouns - SEE ME @ GITLAB" Date: Thu, 10 Feb 2022 09:48:16 -0800 Subject: [PATCH 34/39] Update jb.json --- i18n/jb.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/jb.json b/i18n/jb.json index 6e8e08f5..ff392b87 100644 --- a/i18n/jb.json +++ b/i18n/jb.json @@ -1,6 +1,6 @@ { "PrivateBin": "sivnipax", - "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.", + "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "la %s lo la fukpipax kibro ku .i ji'a zo'e se zancari gi'e fingubni .i lo samse'u na djuno lo datni selru'e cu .i Data is encrypted/decrypted %sin the browser%s using 256 bits AES.", "More information on the project page.": "More information on the project page.", "Because ignorance is bliss": ".i ki'u le ka na djuno cu ka saxfri", "en": "jb", From 15374f99aeef48ec67c57fad65ca65c5817ed7c7 Mon Sep 17 00:00:00 2001 From: "foxsouns - SEE ME @ GITLAB" Date: Thu, 10 Feb 2022 10:33:11 -0800 Subject: [PATCH 35/39] name change, little better --- i18n/jb.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/jb.json b/i18n/jb.json index ff392b87..cf04d978 100644 --- a/i18n/jb.json +++ b/i18n/jb.json @@ -1,6 +1,6 @@ { - "PrivateBin": "sivnipax", - "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "la %s lo la fukpipax kibro ku .i ji'a zo'e se zancari gi'e fingubni .i lo samse'u na djuno lo datni selru'e cu .i Data is encrypted/decrypted %sin the browser%s using 256 bits AES.", + "PrivateBin": "sivbaktu", + "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "la %s basti lo sorcu lo'e se setca kibro .i ji'a zo'e se zancari gi'e fingubni .i lo samse'u na djuno lo datni selru'e cu .i Data is encrypted/decrypted %sin the browser%s using 256 bits AES.", "More information on the project page.": "More information on the project page.", "Because ignorance is bliss": ".i ki'u le ka na djuno cu ka saxfri", "en": "jb", From 49d0b35dc85f0a8a4ce17768f4b7348ec60cb0ae Mon Sep 17 00:00:00 2001 From: AckKid <81313252+AckKid@users.noreply.github.com> Date: Thu, 10 Feb 2022 18:53:39 -0500 Subject: [PATCH 36/39] Create Procfile --- Procfile | 1 + 1 file changed, 1 insertion(+) create mode 100644 Procfile diff --git a/Procfile b/Procfile new file mode 100644 index 00000000..a88c9761 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: vendor/bin/heroku-php-apache2 From 832f0005762ea7d256e398de86b814ae7d87579e Mon Sep 17 00:00:00 2001 From: Bjoern Becker Date: Fri, 11 Feb 2022 12:22:16 +0100 Subject: [PATCH 37/39] update jquery --- js/common.js | 2 +- js/jquery-3.4.1.js | 2 -- js/jquery-3.6.0.js | 2 ++ tpl/bootstrap.php | 2 +- tpl/page.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) delete mode 100644 js/jquery-3.4.1.js create mode 100644 js/jquery-3.6.0.js diff --git a/js/common.js b/js/common.js index b23ed221..4acbbfca 100644 --- a/js/common.js +++ b/js/common.js @@ -10,7 +10,7 @@ global.fs = require('fs'); global.WebCrypto = require('@peculiar/webcrypto').Crypto; // application libraries to test -global.$ = global.jQuery = require('./jquery-3.4.1'); +global.$ = global.jQuery = require('./jquery-3.6.0'); global.RawDeflate = require('./rawinflate-0.3').RawDeflate; global.zlib = require('./zlib-1.2.11').zlib; require('./prettify'); diff --git a/js/jquery-3.4.1.js b/js/jquery-3.4.1.js deleted file mode 100644 index a1c07fd8..00000000 --- a/js/jquery-3.4.1.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.4.1 | (c) JS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.1",k=function(e,t){return new k.fn.init(e,t)},p=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function d(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!A[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){A(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!A[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;nx",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Q.set(this,i,{value:k.event.trigger(k.extend(r[0],k.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,i)&&k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/\s*$/g;function Oe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Me(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Q.hasData(e)&&(o=Q.access(e),a=Q.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Vt,Gt=[],Yt=/(=)\?(?=&|$)|\?\?/;k.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Gt.pop()||k.expando+"_"+kt++;return this[e]=!0,e}}),k.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Yt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Yt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Yt,"$1"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||k.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?k(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Gt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Vt=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=we([e],t,o),o&&o.length&&k(o).remove(),k.merge([],i.childNodes)));var r,i,o},k.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(k.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},k.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){k.fn[t]=function(e){return this.on(t,e)}}),k.expr.pseudos.animated=function(t){return k.grep(k.timers,function(e){return t===e.elem}).length},k.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=k.css(e,"position"),c=k(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=k.css(e,"top"),u=k.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,k.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},k.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){k.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===k.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===k.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=k(e).offset()).top+=k.css(e,"borderTopWidth",!0),i.left+=k.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-k.css(r,"marginTop",!0),left:t.left-i.left-k.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===k.css(e,"position"))e=e.offsetParent;return e||ie})}}),k.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;k.fn[t]=function(e){return _(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),k.each(["top","left"],function(e,n){k.cssHooks[n]=ze(y.pixelPosition,function(e,t){if(t)return t=_e(e,n),$e.test(t)?k(e).position()[n]+"px":t})}),k.each({Height:"height",Width:"width"},function(a,s){k.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){k.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return _(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?k.css(e,t,i):k.style(e,t,n,i)},s,n?e:void 0,n)}})}),k.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){k.fn[n]=function(e,t){return 0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 - + diff --git a/tpl/page.php b/tpl/page.php index 28f37b90..bc9641ec 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -20,7 +20,7 @@ if ($SYNTAXHIGHLIGHTING): endif; endif; ?> - + From 8faf0501f4d39039220070d59935b1ecac4d4086 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 12 Feb 2022 16:17:09 +0100 Subject: [PATCH 38/39] improve Lojban support - Crowdin has to use the 3 letter language code, since Lojban has no 2 letter code. Added support for this in the PHP backend and renamed the translation file. - Lojban has no plural cases, updated the plural-formulas accordingly. - Credited the change and documented it. - Updated the SRI hashes. --- CHANGELOG.md | 4 ++-- CREDITS.md | 1 + i18n/{jb.json => jbo.json} | 0 i18n/languages.json | 2 +- js/privatebin.js | 7 ++++--- lib/I18n.php | 3 ++- tpl/bootstrap.php | 2 +- tpl/page.php | 2 +- 8 files changed, 12 insertions(+), 9 deletions(-) rename i18n/{jb.json => jbo.json} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 428a60ae..7c3f8045 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # PrivateBin version history * **1.4 (not yet released)** - * ADDED: Translation for Estonian + * ADDED: Translations for Estonian and Lojban * ADDED: new HTTP headers improving security (#765) * ADDED: Download button for paste text (#774) * ADDED: Opt-out of federated learning of cohorts (FLoC) (#776) @@ -14,7 +14,7 @@ * CHANGED: Removed configurable `dir` for `traffic` & `purge` limiters (#419) * CHANGED: Server salt, traffic and purge limiter now stored in the storage backend (#419) * **1.3.5 (2021-04-05)** - * ADDED: Translation for Hebrew, Lithuanian, Indonesian and Catalan + * ADDED: Translations for Hebrew, Lithuanian, Indonesian and Catalan * ADDED: Make the project info configurable (#681) * CHANGED: Upgrading libraries to: DOMpurify 2.2.7, kjua 0.9.0 & random_compat 2.0.18 * CHANGED: Open all links in new window (#630) diff --git a/CREDITS.md b/CREDITS.md index 6c2f647c..de0ebe81 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -54,3 +54,4 @@ Sébastien Sauvage - original idea and main developer * whenwesober - Indonesian * retiolus - Catalan * sarnane - Estonian +* foxsouns - Lojban diff --git a/i18n/jb.json b/i18n/jbo.json similarity index 100% rename from i18n/jb.json rename to i18n/jbo.json diff --git a/i18n/languages.json b/i18n/languages.json index 671b3f75..2d7341dc 100644 --- a/i18n/languages.json +++ b/i18n/languages.json @@ -89,7 +89,7 @@ "ku": ["Kurdî", "Kurdish"], "kj": ["Kuanyama", "Kwanyama"], "la": ["lingua latina", "Latin"], - "jb": ["jbobau", "Lojban"], + "jbo":["jbobau", "Lojban"], "lb": ["Lëtzebuergesch", "Luxembourgish"], "lg": ["Luganda", "Ganda"], "li": ["Limburgs", "Limburgish"], diff --git a/js/privatebin.js b/js/privatebin.js index 860a2bd7..ef030fb3 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -601,7 +601,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { * @prop {string[]} * @readonly */ - const supportedLanguages = ['bg', 'ca', 'cs', 'de', 'es', 'et', 'fr', 'he', 'hu', 'id', 'it', 'jb', 'lt', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sl', 'uk', 'zh']; + const supportedLanguages = ['bg', 'ca', 'cs', 'de', 'es', 'et', 'fr', 'he', 'hu', 'id', 'it', 'jbo', 'lt', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sl', 'uk', 'zh']; /** * built in language @@ -785,6 +785,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { case 'he': return n === 1 ? 0 : (n === 2 ? 1 : ((n < 0 || n > 10) && (n % 10 === 0) ? 2 : 3)); case 'id': + case 'jbo': return 0; case 'lt': return n % 10 === 1 && n % 100 !== 11 ? 0 : ((n % 10 >= 2 && n % 100 < 10 || n % 100 >= 20) ? 1 : 2); @@ -5404,8 +5405,8 @@ jQuery.PrivateBin = (function($, RawDeflate) { node.setAttribute('target', '_blank'); } // set non-HTML/MathML links to xlink:show=new - if (!node.hasAttribute('target') - && (node.hasAttribute('xlink:href') + if (!node.hasAttribute('target') + && (node.hasAttribute('xlink:href') || node.hasAttribute('href'))) { node.setAttribute('xlink:show', 'new'); } diff --git a/lib/I18n.php b/lib/I18n.php index 50bf0ccf..bc8b765c 100644 --- a/lib/I18n.php +++ b/lib/I18n.php @@ -195,7 +195,7 @@ class I18n if (count(self::$_availableLanguages) == 0) { $i18n = dir(self::_getPath()); while (false !== ($file = $i18n->read())) { - if (preg_match('/^([a-z]{2}).json$/', $file, $match) === 1) { + if (preg_match('/^([a-z]{2,3}).json$/', $file, $match) === 1) { self::$_availableLanguages[] = $match[1]; } } @@ -324,6 +324,7 @@ class I18n case 'he': return $n === 1 ? 0 : ($n === 2 ? 1 : (($n < 0 || $n > 10) && ($n % 10 === 0) ? 2 : 3)); case 'id': + case 'jbo': return 0; case 'lt': return $n % 10 === 1 && $n % 100 !== 11 ? 0 : (($n % 10 >= 2 && $n % 100 < 10 || $n % 100 >= 20) ? 1 : 2); diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index 1e4eae00..ee89ea9d 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -72,7 +72,7 @@ endif; ?> - + diff --git a/tpl/page.php b/tpl/page.php index 28f37b90..98e32099 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -50,7 +50,7 @@ endif; ?> - + From 186dd82653f65b67d005036972465717e9a6fb5e Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 12 Feb 2022 16:41:09 +0100 Subject: [PATCH 39/39] Apply StyleCI fix that class name we used was not quite correct, but PHP tolerated the typo --- tst/Bootstrap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tst/Bootstrap.php b/tst/Bootstrap.php index 70aafddd..5b6012f0 100644 --- a/tst/Bootstrap.php +++ b/tst/Bootstrap.php @@ -304,7 +304,7 @@ class StorageObjectStub extends StorageObject $this->_generation = $generation; $this->_info = $info; $this->_connection = $connection; - $timeCreated = new Datetime(); + $timeCreated = new DateTime(); $this->_info['metadata']['timeCreated'] = $timeCreated->format('Y-m-d\TH:i:s.u\Z'); }