Przeglądaj źródła

Merge branch 'master' into crowdin-translation

El RIDO 4 lat temu
rodzic
commit
e822673e90
15 zmienionych plików z 253 dodań i 120 usunięć
  1. 8 7
      CHANGELOG.md
  2. 2 0
      CREDITS.md
  3. 8 2
      INSTALL.md
  4. 1 0
      Procfile
  5. 1 0
      i18n/languages.json
  6. 1 1
      js/common.js
  7. 0 1
      js/jquery-3.4.1.js
  8. 1 0
      js/jquery-3.6.0.js
  9. 4 3
      js/privatebin.js
  10. 208 100
      lib/Data/Database.php
  11. 2 1
      lib/I18n.php
  12. 2 2
      tpl/bootstrap.php
  13. 2 2
      tpl/page.php
  14. 1 1
      tst/Bootstrap.php
  15. 12 0
      tst/Data/DatabaseTest.php

+ 8 - 7
CHANGELOG.md

@@ -1,19 +1,20 @@
 # 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)
     * 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)
     * 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)
@@ -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):**

+ 2 - 0
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
@@ -53,3 +54,4 @@ Sébastien Sauvage - original idea and main developer
 * whenwesober - Indonesian
 * retiolus - Catalan
 * sarnane - Estonian
+* foxsouns - Lojban

+ 8 - 2
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

+ 1 - 0
Procfile

@@ -0,0 +1 @@
+web: vendor/bin/heroku-php-apache2

+ 1 - 0
i18n/languages.json

@@ -89,6 +89,7 @@
     "ku": ["Kurdî", "Kurdish"],
     "kj": ["Kuanyama", "Kwanyama"],
     "la": ["lingua latina", "Latin"],
+    "jbo":["jbobau", "Lojban"],
     "lb": ["Lëtzebuergesch", "Luxembourgish"],
     "lg": ["Luganda", "Ganda"],
     "li": ["Limburgs", "Limburgish"],

+ 1 - 1
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');

Plik diff jest za duży
+ 0 - 1
js/jquery-3.4.1.js


Plik diff jest za duży
+ 1 - 0
js/jquery-3.6.0.js


+ 4 - 3
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', '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');
                 }

+ 208 - 100
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,13 +389,13 @@ 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
         $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']);
@@ -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,11 +522,12 @@ class Database extends AbstractData
     {
         $pastes = array();
         $rows   = self::_select(
-            'SELECT dataid FROM ' . self::_sanitizeIdentifier('paste') .
-            ' WHERE expiredate < ? AND expiredate != ? LIMIT ?',
+            'SELECT "dataid" FROM "' . self::_sanitizeIdentifier('paste') .
+            '" WHERE "expiredate" < ? AND "expiredate" != ? ' .
+            (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'];
             }
@@ -542,7 +548,17 @@ class Database extends AbstractData
     private static function _exec($sql, array $params)
     {
         $statement = self::$_db->prepare($sql);
-        $result    = $statement->execute($params);
+        foreach ($params as $key => &$parameter) {
+            $position = $key + 1;
+            if (is_int($parameter)) {
+                $statement->bindParam($position, $parameter, PDO::PARAM_INT);
+            } elseif (strlen($parameter) >= 4000) {
+                $statement->bindParam($position, $parameter, PDO::PARAM_STR, strlen($parameter));
+            } else {
+                $statement->bindParam($position, $parameter);
+            }
+        }
+        $result = $statement->execute();
         $statement->closeCursor();
         return $result;
     }
@@ -562,10 +578,24 @@ 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[] = array_map('self::_sanitizeClob', $row);
+            }
+        } else {
+            $result = $statement->fetchAll(PDO::FETCH_ASSOC);
+        }
         $statement->closeCursor();
+        if (self::$_type === 'oci' && is_array($result)) {
+            // returned CLOB values are streams, convert these into strings
+            $result = $firstOnly ?
+                array_map('self::_sanitizeClob', $result) :
+                $result;
+        }
         return $result;
     }
 
@@ -598,14 +628,15 @@ 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";
+                // U: tables created by the user
+                $sql = 'SELECT "name" FROM "sysobjects" '
+                     . 'WHERE "type" = \'U\' ORDER BY "name"';
                 break;
             case 'mysql':
                 $sql = 'SHOW TABLES';
@@ -614,23 +645,23 @@ class Database extends AbstractData
                 $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(
@@ -652,8 +683,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 '';
@@ -672,10 +703,14 @@ class Database extends AbstractData
     private static function _getPrimaryKeyClauses($key = 'dataid')
     {
         $main_key = $after_key = '';
-        if (self::$_type === 'mysql') {
-            $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);
     }
@@ -683,7 +718,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
@@ -691,13 +726,20 @@ class Database extends AbstractData
      */
     private static function _getDataType()
     {
-        return self::$_type === 'pgsql' ? 'TEXT' : 'BLOB';
+        switch (self::$_type) {
+            case 'oci':
+                return 'CLOB';
+            case 'pgsql':
+                return 'TEXT';
+            default:
+                return '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
@@ -705,7 +747,33 @@ class Database extends AbstractData
      */
     private static function _getAttachmentType()
     {
-        return self::$_type === 'pgsql' ? 'TEXT' : 'MEDIUMBLOB';
+        switch (self::$_type) {
+            case 'oci':
+                return 'CLOB';
+            case 'pgsql':
+                return 'TEXT';
+            default:
+                return 'MEDIUMBLOB';
+        }
+    }
+
+    /**
+     * get the meta type, depending on the database driver
+     *
+     * OCI doesn't accept TEXT so it has to be VARCHAR2(4000)
+     *
+     * @access private
+     * @static
+     * @return string
+     */
+    private static function _getMetaType()
+    {
+        switch (self::$_type) {
+            case 'oci':
+                return 'VARCHAR2(4000)';
+            default:
+                return 'TEXT';
+        }
     }
 
     /**
@@ -719,17 +787,18 @@ 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, " .
-            "data $attachmentType, " .
-            'postdate INT, ' .
-            'expiredate INT, ' .
-            'opendiscussion INT, ' .
-            'burnafterreading INT, ' .
-            'meta TEXT, ' .
-            "attachment $attachmentType, " .
-            "attachmentname $dataType$after_key );"
+            'CREATE TABLE "' . self::_sanitizeIdentifier('paste') . '" ( ' .
+            "\"dataid\" CHAR(16) NOT NULL$main_key, " .
+            "\"data\" $attachmentType, " .
+            '"postdate" INT, ' .
+            '"expiredate" INT, ' .
+            '"opendiscussion" INT, ' .
+            '"burnafterreading" INT, ' .
+            "\"meta\" $metaType, " .
+            "\"attachment\" $attachmentType, " .
+            "\"attachmentname\" $dataType$after_key )"
         );
     }
 
@@ -744,19 +813,35 @@ 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 );"
-        );
-        self::$_db->exec(
-            'CREATE INDEX IF NOT EXISTS comment_parent ON ' .
-            self::_sanitizeIdentifier('comment') . '(pasteid);'
+            '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(
+                '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
+                    when already_exists or columns_indexed then
+                    NULL;
+                end;'
+            );
+        } else {
+            self::$_db->exec(
+                'CREATE INDEX IF NOT EXISTS "comment_parent" ON "' .
+                self::_sanitizeIdentifier('comment') . '" ("pasteid")'
+            );
+        }
     }
 
     /**
@@ -768,17 +853,37 @@ 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::_getMetaType();
         self::$_db->exec(
-            'CREATE TABLE ' . self::_sanitizeIdentifier('config') .
-            " ( id CHAR(16) NOT NULL$main_key, value TEXT$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)
         );
     }
 
+    /**
+     * sanitizes CLOB values used with OCI
+     *
+     * From: https://stackoverflow.com/questions/36200534/pdo-oci-into-a-clob-field
+     *
+     * @access public
+     * @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
      *
@@ -807,43 +912,46 @@ 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;');
+                    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':
@@ -852,15 +960,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')
                 );
         }

+ 2 - 1
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);

+ 2 - 2
tpl/bootstrap.php

@@ -41,7 +41,7 @@ if ($SYNTAXHIGHLIGHTING) :
 endif;
 ?>
 		<noscript><link type="text/css" rel="stylesheet" href="css/noscript.css" /></noscript>
-		<script type="text/javascript" data-cfasync="false" src="js/jquery-3.4.1.js" integrity="sha512-bnIvzh6FU75ZKxp0GXLH9bewza/OIw6dLVh9ICg0gogclmYGguQJWl8U30WpbsGTqbIiAwxTsbe76DErLq5EDQ==" crossorigin="anonymous"></script>
+		<script type="text/javascript" data-cfasync="false" src="js/jquery-3.6.0.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
 <?php
 if ($QRCODE) :
 ?>
@@ -72,7 +72,7 @@ endif;
 ?>
 		<script type="text/javascript" data-cfasync="false" src="js/purify-2.2.7.js" integrity="sha512-7Ka1I/nJuR2CL8wzIS5PJS4HgEMd0HJ6kfAl6fFhwFBB27rhztFbe0tS+Ex+Qg+5n4nZIT4lty4k4Di3+X9T4A==" crossorigin="anonymous"></script>
 		<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
-		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-lJwDAY69TQuYQZ7FjUFPfhgYeZ2L6y5bmGt1hR+d3kMm2sddivGr7ZDdLLSe/CBgn1JrsKMj3th9dPyXN3dLHw==" crossorigin="anonymous"></script>
+		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-PTOcxIWIPWCnb5vC4fmQDMqYGerwsu3AndVyPxn9NlQffIWYMPf/p28Z9SIygXsmcYjmTRmUiW5y7df63mNTfg==" crossorigin="anonymous"></script>
 		<!-- icon -->
 		<link rel="apple-touch-icon" href="<?php echo I18n::encode($BASEPATH); ?>img/apple-touch-icon.png" sizes="180x180" />
 		<link rel="icon" type="image/png" href="img/favicon-32x32.png" sizes="32x32" />

+ 2 - 2
tpl/page.php

@@ -20,7 +20,7 @@ if ($SYNTAXHIGHLIGHTING):
     endif;
 endif;
 ?>
-		<script type="text/javascript" data-cfasync="false" src="js/jquery-3.4.1.js" integrity="sha512-bnIvzh6FU75ZKxp0GXLH9bewza/OIw6dLVh9ICg0gogclmYGguQJWl8U30WpbsGTqbIiAwxTsbe76DErLq5EDQ==" crossorigin="anonymous"></script>
+		<script type="text/javascript" data-cfasync="false" src="js/jquery-3.6.0.js" integrity="sha512-bnIvzh6FU75ZKxp0GXLH9bewza/OIw6dLVh9ICg0gogclmYGguQJWl8U30WpbsGTqbIiAwxTsbe76DErLq5EDQ==" crossorigin="anonymous"></script>
 <?php
 if ($QRCODE):
 ?>
@@ -50,7 +50,7 @@ endif;
 ?>
 		<script type="text/javascript" data-cfasync="false" src="js/purify-2.2.7.js" integrity="sha512-7Ka1I/nJuR2CL8wzIS5PJS4HgEMd0HJ6kfAl6fFhwFBB27rhztFbe0tS+Ex+Qg+5n4nZIT4lty4k4Di3+X9T4A==" crossorigin="anonymous"></script>
 		<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
-		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-lJwDAY69TQuYQZ7FjUFPfhgYeZ2L6y5bmGt1hR+d3kMm2sddivGr7ZDdLLSe/CBgn1JrsKMj3th9dPyXN3dLHw==" crossorigin="anonymous"></script>
+		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-PTOcxIWIPWCnb5vC4fmQDMqYGerwsu3AndVyPxn9NlQffIWYMPf/p28Z9SIygXsmcYjmTRmUiW5y7df63mNTfg==" crossorigin="anonymous"></script>
 		<!-- icon -->
 		<link rel="apple-touch-icon" href="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" sizes="180x180" />
 		<link rel="icon" type="image/png" href="img/favicon-32x32.png?<?php echo rawurlencode($VERSION); ?>" sizes="32x32" />

+ 1 - 1
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');
     }
 

+ 12 - 0
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));
+    }
 }

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików