From 7e7f9d556efa11939542f3704fefa3f4039b6fb7 Mon Sep 17 00:00:00 2001
From: Carsten  Rose <carsten.rose@math.uzh.ch>
Date: Sat, 26 Jan 2019 00:56:06 +0100
Subject: [PATCH] Implements and fixes #7747. Import Excel: restrict reading to
 explicit named worksheets

---
 extension/Documentation/Manual.rst  | 110 ++++++++++++++++------------
 extension/Source/core/Constants.php |   8 ++
 extension/Source/core/Save.php      |  49 +++++++++++--
 extension/Source/core/sample.xlsx   | Bin 4797 -> 0 bytes
 4 files changed, 113 insertions(+), 54 deletions(-)
 delete mode 100644 extension/Source/core/sample.xlsx

diff --git a/extension/Documentation/Manual.rst b/extension/Documentation/Manual.rst
index 31ac84e52..d7bed9b16 100644
--- a/extension/Documentation/Manual.rst
+++ b/extension/Documentation/Manual.rst
@@ -2790,31 +2790,39 @@ See also at specific *FormElement* definitions.
 +------------------------+--------+----------------------------------------------------------------------------------------------------------+
 | autofocus              | string | See `input-option-autofocus`_                                                                            |
 +------------------------+--------+----------------------------------------------------------------------------------------------------------+
-| capture                | string | See `input-upload`_                                                                                      |
-| accept                 | string |                                                                                                          |
-| maxFileSize            | string |                                                                                                          |
-| fileDestination        | string |                                                                                                          |
-| fileReplace            | string |                                                                                                          |
-| autoOrient             | string |                                                                                                          |
-| autoOrientCmd          | string |                                                                                                          |
-| autoOrientMimeType     | string |                                                                                                          |
-| chmodFile / chmodDir   | string |                                                                                                          |
-| slaveId                | string |                                                                                                          |
-| sqlBefore              | string |                                                                                                          |
-| sqlInsert              | string |                                                                                                          |
-| sqlUpdate              | string |                                                                                                          |
-| sqlDelete              | string |                                                                                                          |
-| sqlAfter               | string |                                                                                                          |
+| capture,               | string | See `input-upload`_                                                                                      |
+| accept,                |        |                                                                                                          |
+| maxFileSize,           |        |                                                                                                          |
+| fileDestination,       |        |                                                                                                          |
+| fileReplace,           |        |                                                                                                          |
+| autoOrient,            |        |                                                                                                          |
+| autoOrientCmd,         |        |                                                                                                          |
+| autoOrientMimeType,    |        |                                                                                                          |
+| chmodFile, chmodDir,   |        |                                                                                                          |
+| slaveId,               |        |                                                                                                          |
+| sqlBefore,             |        |                                                                                                          |
+| sqlInsert,             |        |                                                                                                          |
+| sqlUpdate,             |        |                                                                                                          |
+| sqlDelete,             |        |                                                                                                          |
+| sqlAfter,              |        |                                                                                                          |
+| importToTable,         |        |                                                                                                          |
+| importToColumns,       |        |                                                                                                          |
+| importRegion,          |        |                                                                                                          |
+| importMode,            |        |                                                                                                          |
+| importType,            |        |                                                                                                          |
+| importNamedSheetsOnly, |        |                                                                                                          |
+| importSetReadDataOnly, |        |                                                                                                          |
+| importListSheetNames,  |        |                                                                                                          |
 +------------------------+--------+----------------------------------------------------------------------------------------------------------+
 | checkBoxMode           | string | See `input-checkbox`_, `input-radio`_, `input-select`_                                                   |
-| checked                | string |                                                                                                          |
-| unchecked              | string |                                                                                                          |
-| label2                 | string |                                                                                                          |
-| itemList               | string |                                                                                                          |
-| emptyHide              | -      |                                                                                                          |
-| emptyItemAtStart       | -      |                                                                                                          |
-| emptyItemAtEnd         | -      |                                                                                                          |
-| buttonClass            | string |                                                                                                          |
+| checked                |        |                                                                                                          |
+| unchecked              |        |                                                                                                          |
+| label2                 |        |                                                                                                          |
+| itemList               |        |                                                                                                          |
+| emptyHide              |        |                                                                                                          |
+| emptyItemAtStart       |        |                                                                                                          |
+| emptyItemAtEnd         |        |                                                                                                          |
+| buttonClass            |        |                                                                                                          |
 +------------------------+--------+----------------------------------------------------------------------------------------------------------+
 | dateFormat             | string | yyyy-mm-dd / dd.mm.yyyy                                                                                  |
 +------------------------+--------+----------------------------------------------------------------------------------------------------------+
@@ -2841,20 +2849,20 @@ See also at specific *FormElement* definitions.
 +------------------------+--------+----------------------------------------------------------------------------------------------------------+
 | extraButtonInfoClass   | string | By default empty. Specify any class to be assigned to wrap extraButtonInfo                               |
 +------------------------+--------+----------------------------------------------------------------------------------------------------------+
-| editor-plugins         | string | See `input-editor`_                                                                                      |
-| editor-toolbar         | string |                                                                                                          |
-| editor-statusbar       | string |                                                                                                          |
+| editor-plugins,        | string | See `input-editor`_                                                                                      |
+| editor-toolbar,        |        |                                                                                                          |
+| editor-statusbar,      |        |                                                                                                          |
 +------------------------+--------+----------------------------------------------------------------------------------------------------------+
 | fileButtonText         | string | Overwrite default 'Choose File'                                                                          |
 +------------------------+--------+----------------------------------------------------------------------------------------------------------+
 | fillStoreVar           | string | Fill the STORE_VAR with custom values. See `STORE_VARS`_.                                                |
 +------------------------+--------+----------------------------------------------------------------------------------------------------------+
-| form                   | string | See `subrecord-option`_                                                                                  |
-| page                   | string |                                                                                                          |
-| title                  | string |                                                                                                          |
-| extraDeleteForm        | string |                                                                                                          |
-| detail                 | string |                                                                                                          |
-| subrecordTableClass    | string |                                                                                                          |
+| form,                  | string | See `subrecord-option`_                                                                                  |
+| page,                  |        |                                                                                                          |
+| title,                 |        |                                                                                                          |
+| extraDeleteForm,       |        |                                                                                                          |
+| detail,                |        |                                                                                                          |
+| subrecordTableClass,   |        |                                                                                                          |
 +------------------------+--------+----------------------------------------------------------------------------------------------------------+
 | min                    | s/d/n  | Minimum and/or maximum allowed values for input field. Can be used for numbers, dates, or strings.       |
 +------------------------+--------+                                                                                                          |
@@ -2863,12 +2871,12 @@ See also at specific *FormElement* definitions.
 | processReadOnly        | [n]    | [0|1] By default FE's with type='readonly' are not processed during 'save'.                              |
 |                        |        | This option forces to process them during 'save' as well.                                                |
 +------------------------+--------+----------------------------------------------------------------------------------------------------------+
-| retype                 | string | See `input-text`_                                                                                        |
-| retypeLabel            | string |                                                                                                          |
-| retypeNote             | string |                                                                                                          |
-| characterCountWrap     | string |                                                                                                          |
-| hideZero               | string |                                                                                                          |
-| emptyMeansNull         | string |                                                                                                          |
+| retype,                | string | See `input-text`_                                                                                        |
+| retypeLabel,           |        |                                                                                                          |
+| retypeNote,            |        |                                                                                                          |
+| characterCountWrap,    |        |                                                                                                          |
+| hideZero,              |        |                                                                                                          |
+| emptyMeansNull,        |        |                                                                                                          |
 +------------------------+--------+----------------------------------------------------------------------------------------------------------+
 | showSeconds            | string | 0|1 - Shows the seconds on form load. Default: 0                                                         |
 +------------------------+--------+----------------------------------------------------------------------------------------------------------+
@@ -2877,10 +2885,10 @@ See also at specific *FormElement* definitions.
 | timeIsOptional         | string | 0|1 - Used for datetime input. 0 (default): Time is required - 1: Entering a time is optional            |
 |                        |        | (defaults to 00:00:00 if none entered).                                                                  |
 +------------------------+--------+----------------------------------------------------------------------------------------------------------+
-| typeAheadLimit         | string | See `input-typeahead`_                                                                                   |
-| typeAheadMinLength     | string |                                                                                                          |
-| typeAheadSql           | string |                                                                                                          |
-| typeAheadSqlPrefetch   | string |                                                                                                          |
+| typeAheadLimit,        | string | See `input-typeahead`_                                                                                   |
+| typeAheadMinLength,    |        |                                                                                                          |
+| typeAheadSql,          |        |                                                                                                          |
+| typeAheadSqlPrefetch,  |        |                                                                                                          |
 +------------------------+--------+----------------------------------------------------------------------------------------------------------+
 | wrapRow                | string | If specified, skip default wrapping (`<div class='col-md-?'>`). Instead the given string is used.        |
 +------------------------+--------+                                                                                                          |
@@ -3606,16 +3614,22 @@ See also `downloadButton`_ to offer a download of an uploaded file.
     (e.g. 43214), which is the serial value date in Excel. To convert such a number to a MariaDb date, use:
     `DATE_ADD('1899-12-30', INTERVAL serialValue DAY)`.
 
-    * *importToTable*: <mariadb.tablename> - **Required**. Providing this parameter activates the import. If the table
+    * *importToTable* = <[db.]tablename> - **Required**. Providing this parameter activates the import. If the table
       doesn't exist, it will be created.
-    * *importToColumns*: <col1>,<col2>,... - If none provided, the Excel column names A, B, ... are used. Note: These
+    * *importToColumns* = <col1>,<col2>,... - If none provided, the Excel column names A, B, ... are used. Note: These
       have to match the table's column names if the table already exists.
-    * *importRegion*: [tab],[startColumn],[startRow],[endColumn],[endRow]|... - All parts are optional (default:
+    * *importRegion* = [tab],[startColumn],[startRow],[endColumn],[endRow]|... - All parts are optional (default:
       entire 1st sheet). Tab can either be given as an index (1-based) or a name. start/endColumn can be given either
       numerically (1, 2, ...) or by column name (A, B, ...). Note that you can specify several regions to import.
-    * *importMode*: `append` (default) | `replace` - The data is either appended or replace in the specified table.
-    * *importType*: `auto` (default) | `xls` | `xlsx` | `ods` | `csv` - Define what kind of data should be expected by the
-      Spreadsheet Reader. `auto` should work fine in most cases.
+    * *importMode* = `append` (default) | `replace` - The data is either appended or replace in the specified table.
+    * *importType* = `auto` (default) | `xls` | `xlsx` | `ods` | `csv` - Define what kind of data should be expected by the
+      Spreadsheet Reader.
+    * *importNamedSheetsOnly* = <comma separated list of sheet names>. Use this option if specific sheets cause problems
+      during import and should be skipped, by naming only those sheets, who will be read. This will also reduce the memory
+      usage.
+    * *importSetReadDataOnly* = 0|1. Read only cell data, not the cell formatting. Warning: cell types other than numerical
+      will be misinterpreted.
+    * *importListSheetNames* = 0|1. For debug use only. Will open a dialog and report all found worksheet names.
 
 
 Immediately after the upload finished (before the user press save), the file will be checked on the server for it's
diff --git a/extension/Source/core/Constants.php b/extension/Source/core/Constants.php
index 870bb398f..cacf6905e 100644
--- a/extension/Source/core/Constants.php
+++ b/extension/Source/core/Constants.php
@@ -337,6 +337,10 @@ const ERROR_DND_EMPTY_REORDER_SQL = 2700;
 // Form
 const ERROR_FORM_RESERVED_NAME = 2800;
 
+// Import (Excel, ODS, ...)
+const ERROR_IMPORT_MISSING_EXPLICIT_TYPE = 2900;
+const ERROR_IMPORT_LIST_SHEET_NAMES = 2901;
+
 //
 // Store Names: Identifier
 //
@@ -1040,6 +1044,10 @@ const FE_IMPORT_TYPE_XLS = 'xls';
 const FE_IMPORT_TYPE_XLSX = 'xlsx';
 const FE_IMPORT_TYPE_ODS = 'ods';
 const FE_IMPORT_TYPE_CSV = 'csv';
+const FE_IMPORT_NAMED_SHEETS_ONLY = 'importNamedSheetsOnly';
+const FE_IMPORT_READ_DATA_ONLY = 'importSetReadDataOnly';
+const FE_IMPORT_LIST_SHEET_NAMES = 'importListSheetNames';
+
 
 const FE_IMAGE_SOURCE = 'imageSource'; // Image source for a fabric element
 const FE_DEFAULT_PEN_COLOR = 'defaultPenColor'; // Default pen color for a fabric element
diff --git a/extension/Source/core/Save.php b/extension/Source/core/Save.php
index bb9fba4c9..a578d4a75 100644
--- a/extension/Source/core/Save.php
+++ b/extension/Source/core/Save.php
@@ -369,7 +369,7 @@ class Save {
             }
 
             $column = $formElement[FE_NAME];
-            $pathFileName = $this->doUpload($formElement, ($formValues[$column]??''), $sip, $modeUpload);
+            $pathFileName = $this->doUpload($formElement, ($formValues[$column] ?? ''), $sip, $modeUpload);
 
             if ($modeUpload == UPLOAD_MODE_DELETEOLD && $pathFileName == '') {
                 $pathFileNameTmp = '';  // see '4'
@@ -661,9 +661,25 @@ class Save {
      * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
      */
     private function doImport($formElement, $fileName) {
+        $importNamedSheetsOnly = array();
 
         Support::setIfNotSet($formElement, FE_IMPORT_TYPE, FE_IMPORT_TYPE_AUTO);
 
+        if (!empty($formElement[FE_IMPORT_NAMED_SHEETS_ONLY])) {
+            $importNamedSheetsOnly = explode(',', $formElement[FE_IMPORT_NAMED_SHEETS_ONLY]);
+        }
+
+        if ($formElement[FE_IMPORT_TYPE] === FE_IMPORT_TYPE_AUTO) {
+
+            $list = [FE_IMPORT_LIST_SHEET_NAMES, FE_IMPORT_READ_DATA_ONLY, FE_IMPORT_LIST_SHEET_NAMES];
+            foreach ($list as $token) {
+                if (isset($formElement[$token])) {
+                    throw new UserFormException('If ' . $token .
+                        ' is given, an explicit document type (like ' . FE_IMPORT_TYPE . '=xlsx) should be set.', ERROR_IMPORT_MISSING_EXPLICIT_TYPE);
+                }
+            }
+        }
+
         switch ($formElement[FE_IMPORT_TYPE]) {
             case FE_IMPORT_TYPE_AUTO:
                 $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($fileName);
@@ -675,6 +691,23 @@ class Save {
             case FE_IMPORT_TYPE_ODS:
                 $inputFileType = ucfirst($formElement[FE_IMPORT_TYPE]);
                 $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType);
+
+                // setReadDataOnly
+                if (($formElement[FE_IMPORT_READ_DATA_ONLY] ?? '0') != '0') {
+                    $reader->setReadDataOnly(true);
+                }
+
+                // setLoadSheetsOnly
+                if (!empty ($importNamedSheetsOnly)) {
+                    $reader->setLoadSheetsOnly($importNamedSheetsOnly);
+                }
+
+                if (($formElement[FE_IMPORT_LIST_SHEET_NAMES] ?? '0') != '0') {
+                    $sheetNames = $reader->listWorksheetNames($fileName);
+                    throw new UserFormException("Worksheets: " . implode(', ', $sheetNames),
+                        ERROR_IMPORT_LIST_SHEET_NAMES);
+                }
+
                 $spreadsheet = $reader->load($fileName);
                 break;
 
@@ -685,7 +718,7 @@ class Save {
 
         $tableName = $formElement[FE_IMPORT_TO_TABLE];
         $regions = OnArray::trimArray(explode('|', $formElement[FE_IMPORT_REGION] ?? ''));
-        $columnNames = OnArray::trimArray(explode(',', $formElement[FE_IMPORT_TO_COLUMNS]));
+        $columnNames = OnArray::trimArray(explode(',', $formElement[FE_IMPORT_TO_COLUMNS] ?? ''));
         $importMode = $formElement[FE_IMPORT_MODE] ?? FE_IMPORT_MODE_APPEND;
 
         foreach ($regions as $region) {
@@ -789,7 +822,8 @@ class Save {
      * @throws UserFormException
      * @throws UserReportException
      */
-    private function copyUploadFile(array $formElement, array $statusUpload) {
+    private
+    function copyUploadFile(array $formElement, array $statusUpload) {
         $pathFileName = '';
 
         if (!isset($statusUpload[FILES_TMP_NAME]) || $statusUpload[FILES_TMP_NAME] === '') {
@@ -852,7 +886,8 @@ class Save {
      * @throws UserFormException
      * @throws UserReportException
      */
-    private function autoOrient(array $formElement, $pathFileName) {
+    private
+    function autoOrient(array $formElement, $pathFileName) {
 
         // 'autoOrient' wished?
         if (!isset($formElement[FE_FILE_AUTO_ORIENT]) || $formElement[FE_FILE_AUTO_ORIENT] == '0') {
@@ -893,7 +928,8 @@ class Save {
      * @throws UserFormException
      * @throws UserReportException
      */
-    private function splitUpload(array $formElement, $pathFileName, $chmod, array $statusUpload) {
+    private
+    function splitUpload(array $formElement, $pathFileName, $chmod, array $statusUpload) {
 
         if (empty($formElement[FE_FILE_SPLIT]) || $statusUpload[FILES_TYPE] != MIME_TYPE_SPLIT_CAPABLE) {
             return;
@@ -1016,7 +1052,8 @@ class Save {
      * @throws UserFormException
      * @throws UserReportException
      */
-    private function doUploadSlave(array $fe, $modeUpload) {
+    private
+    function doUploadSlave(array $fe, $modeUpload) {
         $sql = '';
         $flagUpdateSlaveId = false;
         $flagSlaveDeleted = false;
diff --git a/extension/Source/core/sample.xlsx b/extension/Source/core/sample.xlsx
deleted file mode 100644
index bbcfe0f7bb0c9217fb6114a4177af6cc83fec086..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 4797
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

-- 
GitLab