From 6f107e3fee71fbb381794446e0bfabe9e11d01ea Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 27 Dec 2017 15:48:20 +0100 Subject: [PATCH] Fix API to download/build doc --- htdocs/api/class/api_documents.class.php | 206 ++++++++++++++++------- htdocs/api/class/api_login.class.php | 4 +- htdocs/core/lib/files.lib.php | 15 +- 3 files changed, 161 insertions(+), 64 deletions(-) diff --git a/htdocs/api/class/api_documents.class.php b/htdocs/api/class/api_documents.class.php index fcc221d5e9e..df2ca288f35 100644 --- a/htdocs/api/class/api_documents.class.php +++ b/htdocs/api/class/api_documents.class.php @@ -58,19 +58,16 @@ class Documents extends DolibarrApi * * @param string $module_part Name of module or area concerned by file download ('facture', ...) * @param string $original_file Relative path with filename, relative to modulepart (for example: IN201701-999/IN201701-999.pdf) - * @param int $regeneratedoc If requested document is the main document of an object, setting this to 1 ask API to regenerate document before returning it (supported for some module_part only). It is no effect in other cases. - * Also, note that setting this to 1 nead write access on object. * @return array List of documents * - * @throws 500 - * @throws 501 * @throws 400 * @throws 401 + * @throws 404 * @throws 200 * * @url GET /download */ - public function index($module_part, $original_file='', $regeneratedoc=0) + public function index($module_part, $original_file='') { global $conf, $langs; @@ -78,13 +75,13 @@ class Documents extends DolibarrApi throw new RestException(400, 'bad value for parameter modulepart'); } if (empty($original_file)) { - throw new RestException(400, 'bad value for parameter ref or subdir'); + throw new RestException(400, 'bad value for parameter original_file'); } //--- Finds and returns the document $entity=$conf->entity; - $check_access = dol_check_secure_access_document($module_part, $original_file, $entity, DolibarrApiAccess::$user, '', ($regeneratedoc ? 'write' : 'read')); + $check_access = dol_check_secure_access_document($module_part, $original_file, $entity, DolibarrApiAccess::$user, '', 'read'); $accessallowed = $check_access['accessallowed']; $sqlprotectagainstexternals = $check_access['sqlprotectagainstexternals']; $original_file = $check_access['original_file']; @@ -97,42 +94,6 @@ class Documents extends DolibarrApi throw new RestException(401); } - // --- Generates the document - if ($regeneratedoc) - { - $hidedetails = empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS) ? 0 : 1; - $hidedesc = empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DESC) ? 0 : 1; - $hideref = empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_REF) ? 0 : 1; - - if ($module_part == 'facture' || $module_part == 'invoice') - { - require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; - $this->invoice = new Facture($this->db); - $result = $this->invoice->fetch(0, preg_replace('/\.[^\.]+$/', '', basename($original_file))); - if( ! $result ) { - throw new RestException(404, 'Invoice not found'); - } - $result = $this->invoice->generateDocument($this->invoice->modelpdf, $langs, $hidedetails, $hidedesc, $hideref); - if( $result <= 0 ) { - throw new RestException(500, 'Error generating document'); - } - } - if ($module_part == 'commande' || $module_part == 'order') - { - require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php'; - $this->order = new Commande($this->db); - $result = $this->order->fetch(0, preg_replace('/\.[^\.]+$/', '', basename($original_file))); - if( ! $result ) { - throw new RestException(404, 'Order not found'); - } - $result = $this->order->generateDocument($this->order->modelpdf, $langs, $hidedetails, $hidedesc, $hideref); - if( $result <= 0 ) { - throw new RestException(500, 'Error generating document'); - } - } - - } - $filename = basename($original_file); $original_file_osencoded=dol_osencode($original_file); // New file name encoded in OS encoding charset @@ -145,6 +106,114 @@ class Documents extends DolibarrApi return array('filename'=>$filename, 'content'=>base64_encode($file_content), 'encoding'=>'MIME base64 (base64_encode php function, http://php.net/manual/en/function.base64-encode.php)' ); } + + /** + * Build a document. + * + * Test sample 1: { "module_part": "invoice", "original_file": "FA1701-001/FA1701-001.pdf", "doctemplate": "crabe", "langcode": "fr_FR" }. + * + * @param string $module_part Name of module or area concerned by file download ('invoice', 'order', ...). + * @param string $original_file Relative path with filename, relative to modulepart (for example: IN201701-999/IN201701-999.pdf). + * @param string $doctemplate Set here the doc template to use for document generation (If not set, use the default template). + * @param string $langcode Language code like 'en_US', 'fr_FR', 'es_ES', ... (If not set, use the default language). + * @return array List of documents + * + * @throws 500 + * @throws 501 + * @throws 400 + * @throws 401 + * @throws 404 + * @throws 200 + * + * @url PUT /builddoc + */ + public function builddoc($module_part, $original_file='', $doctemplate='', $langcode='') + { + global $conf, $langs; + + if (empty($module_part)) { + throw new RestException(400, 'bad value for parameter modulepart'); + } + if (empty($original_file)) { + throw new RestException(400, 'bad value for parameter original_file'); + } + + $outputlangs = $langs; + if ($langcode && $langs->defaultlang != $langcode) + { + $outputlangs=new Translate('', $conf); + $outputlangs->setDefaultLang($langcode); + } + + //--- Finds and returns the document + $entity=$conf->entity; + + $check_access = dol_check_secure_access_document($module_part, $original_file, $entity, DolibarrApiAccess::$user, '', 'write'); + $accessallowed = $check_access['accessallowed']; + $sqlprotectagainstexternals = $check_access['sqlprotectagainstexternals']; + $original_file = $check_access['original_file']; + + if (preg_match('/\.\./',$original_file) || preg_match('/[<>|]/',$original_file)) { + throw new RestException(401); + } + if (!$accessallowed) { + throw new RestException(401); + } + + // --- Generates the document + $hidedetails = empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS) ? 0 : 1; + $hidedesc = empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DESC) ? 0 : 1; + $hideref = empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_REF) ? 0 : 1; + + $templateused=''; + + if ($module_part == 'facture' || $module_part == 'invoice') + { + require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; + $this->invoice = new Facture($this->db); + $result = $this->invoice->fetch(0, preg_replace('/\.[^\.]+$/', '', basename($original_file))); + if( ! $result ) { + throw new RestException(404, 'Invoice not found'); + } + + $templateused = $doctemplate?$doctemplate:$this->invoice->modelpdf; + $result = $this->invoice->generateDocument($templateused, $outputlangs, $hidedetails, $hidedesc, $hideref); + if( $result <= 0 ) { + throw new RestException(500, 'Error generating document'); + } + } + elseif ($module_part == 'commande' || $module_part == 'order') + { + require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php'; + $this->order = new Commande($this->db); + $result = $this->order->fetch(0, preg_replace('/\.[^\.]+$/', '', basename($original_file))); + if( ! $result ) { + throw new RestException(404, 'Order not found'); + } + $templateused = $doctemplate?$doctemplate:$this->order->modelpdf; + $result = $this->order->generateDocument($templateused, $outputlangs, $hidedetails, $hidedesc, $hideref); + if( $result <= 0 ) { + throw new RestException(500, 'Error generating document'); + } + } + else + { + throw new RestException(403, 'Generation not available for this modulepart'); + } + + $filename = basename($original_file); + $original_file_osencoded=dol_osencode($original_file); // New file name encoded in OS encoding charset + + if (! file_exists($original_file_osencoded)) + { + throw new RestException(404, 'File not found'); + } + + $file_content=file_get_contents($original_file_osencoded); + return array('filename'=>$filename, 'content'=>base64_encode($file_content), 'langcode'=>$outputlangs->defaultlang, 'template'=>$templateused, 'encoding'=>'MIME base64 (base64_encode php function, http://php.net/manual/en/function.base64-encode.php)' ); + } + + /** * Return the list of documents of a dedicated element (from its ID or Ref) * @@ -155,7 +224,11 @@ class Documents extends DolibarrApi * @param string $sortorder Sort order ('asc' or 'desc') * @return array Array of documents with path * - * @throws RestException + * @throws 200 + * @throws 400 + * @throws 401 + * @throws 404 + * @throws 500 * * @url GET / */ @@ -301,19 +374,23 @@ class Documents extends DolibarrApi * Upload a file. * * Test sample 1: { "filename": "mynewfile.txt", "modulepart": "facture", "ref": "FA1701-001", "subdir": "", "filecontent": "content text", "fileencoding": "", "overwriteifexists": "0" }. - * Test sample 2: { "filename": "mynewfile.txt", "modulepart": "medias", "ref": "", "subdir": "mysubdir1/mysubdir2", "filecontent": "content text", "fileencoding": "", "overwriteifexists": "0" }. + * Test sample 2: { "filename": "mynewfile.txt", "modulepart": "medias", "ref": "", "subdir": "image/mywebsite", "filecontent": "content text", "fileencoding": "", "overwriteifexists": "0" }. * * @param string $filename Name of file to create ('FA1705-0123.txt') * @param string $modulepart Name of module or area concerned by file upload ('facture', 'project', 'project_task', ...) * @param string $ref Reference of object (This will define subdir automatically and store submited file into it) - * @param string $subdir Subdirectory (Only if ref not provided) + * @param string $subdir Subdirectory (Only if ref not provided) * @param string $filecontent File content (string with file content. An empty file will be created if this parameter is not provided) * @param string $fileencoding File encoding (''=no encoding, 'base64'=Base 64) * @param int $overwriteifexists Overwrite file if exists (1 by default) - * @return bool State of copy - * @throws RestException * - * @url GET /upload + * @throws 200 + * @throws 400 + * @throws 401 + * @throws 404 + * @throws 500 + * + * @url POST /upload */ public function post($filename, $modulepart, $ref='', $subdir='', $filecontent='', $fileencoding='', $overwriteifexists=0) { @@ -367,7 +444,7 @@ class Documents extends DolibarrApi $task_result = $object->fetch('', $ref); - // Fetching the tasks project is required because its out_dir might be a subdirectory of the project + // Fetching the tasks project is required because its out_dir might be a sub-directory of the project if($task_result > 0) { $project_result = $object->fetch_projet(); @@ -394,7 +471,7 @@ class Documents extends DolibarrApi if($result == 0) { - throw new RestException(500, "Object with ref '".$ref.'" was not found.'); + throw new RestException(404, "Object with ref '".$ref."' was not found."); } elseif ($result < 0) { @@ -404,11 +481,13 @@ class Documents extends DolibarrApi if (! ($object->id > 0)) { - throw new RestException(500, 'The object '.$modulepart." with ref '".$ref."' was not found."); + throw new RestException(404, 'The object '.$modulepart." with ref '".$ref."' was not found."); } - $tmp = dol_check_secure_access_document($modulepart, $tmpreldir.dol_sanitizeFileName($object->ref), $entity, DolibarrApiAccess::$user, $ref, 'write'); - $upload_dir = $tmp['original_file']; + $relativefile = $tmpreldir.dol_sanitizeFileName($object->ref); + + $tmp = dol_check_secure_access_document($modulepart, $relativefile, $entity, DolibarrApiAccess::$user, $ref, 'write'); + $upload_dir = $tmp['original_file']; // No dirname here, tmp['original_file'] is already the dir because dol_check_secure_access_document was called with param original_file that is only the dir if (empty($upload_dir) || $upload_dir == '/') { @@ -419,24 +498,27 @@ class Documents extends DolibarrApi { if ($modulepart == 'invoice') $modulepart ='facture'; - $tmp = dol_check_secure_access_document($modulepart, $subdir, $entity, DolibarrApiAccess::$user, '', 'write'); - $upload_dir = $tmp['original_file']; + $relativefile = $subdir; + + $tmp = dol_check_secure_access_document($modulepart, $relativefile, $entity, DolibarrApiAccess::$user, '', 'write'); + $upload_dir = $tmp['original_file']; // No dirname here, tmp['original_file'] is already the dir because dol_check_secure_access_document was called with param original_file that is only the dir if (empty($upload_dir) || $upload_dir == '/') { throw new RestException(500, 'This value of modulepart does not support yet usage of ref. Check modulepart parameter or try to use subdir parameter instead of ref.'); } } - + // $original_file here is still value of filename without any dir. $upload_dir = dol_sanitizePathName($upload_dir); $destfile = $upload_dir . '/' . $original_file; $destfiletmp = DOL_DATA_ROOT.'/admin/temp/' . $original_file; dol_delete_file($destfiletmp); + //var_dump($original_file);exit; - if (!dol_is_dir($upload_dir)) { - throw new RestException(401,'Directory not exists : '.$upload_dir); + if (!dol_is_dir(dirname($destfile))) { + throw new RestException(401, 'Directory not exists : '.dirname($destfile)); } if (! $overwriteifexists && dol_is_file($destfile)) @@ -457,8 +539,12 @@ class Documents extends DolibarrApi } $result = dol_move($destfiletmp, $destfile, 0, $overwriteifexists, 1); + if (! $result) + { + throw new RestException(500, "Failed to move file into '".$destfile."'"); + } - return $result; + return dol_basename($destfile); } /** diff --git a/htdocs/api/class/api_login.class.php b/htdocs/api/class/api_login.class.php index a1bcedc8fbe..56808819b3f 100644 --- a/htdocs/api/class/api_login.class.php +++ b/htdocs/api/class/api_login.class.php @@ -44,7 +44,9 @@ class Login * @param int $reset Reset token (0=get current token, 1=ask a new token and canceled old token. This means access using current existing API token of user will fails: new token will be required for new access) * @return array Response status and user token * - * @throws RestException + * @throws 200 + * @throws 403 + * @throws 500 * * @url GET / * @url POST / diff --git a/htdocs/core/lib/files.lib.php b/htdocs/core/lib/files.lib.php index daf4621ebcb..d357141fa4d 100644 --- a/htdocs/core/lib/files.lib.php +++ b/htdocs/core/lib/files.lib.php @@ -1947,14 +1947,18 @@ function dol_most_recent_file($dir,$regexfilter='',$excludefilter=array('(\.meta */ function dol_check_secure_access_document($modulepart, $original_file, $entity, $fuser='', $refname='', $mode='read') { - global $user, $conf, $db; + global $conf, $db, $user; global $dolibarr_main_data_root, $dolibarr_main_document_root_alt; if (! is_object($fuser)) $fuser=$user; if (empty($modulepart)) return 'ErrorBadParameter'; - if (empty($entity)) $entity=0; - dol_syslog('modulepart='.$modulepart.' original_file='.$original_file); + if (empty($entity)) + { + if (empty($conf->multicompany->enabled)) $entity=1; + else $entity=0; + } + dol_syslog('modulepart='.$modulepart.' original_file='.$original_file.' entity='.$entity); // We define $accessallowed and $sqlprotectagainstexternals $accessallowed=0; $sqlprotectagainstexternals=''; @@ -1975,6 +1979,7 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity, // Wrapping for miscellaneous medias files if ($modulepart == 'medias' && !empty($dolibarr_main_data_root)) { + if (empty($entity) || empty($conf->medias->multidir_output[$entity])) return array('accessallowed'=>0, 'error'=>'Value entity must be provided'); $accessallowed=1; $original_file=$conf->medias->multidir_output[$entity].'/'.$original_file; } @@ -2133,6 +2138,7 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity, // Wrapping for categories elseif ($modulepart == 'category' && !empty($conf->categorie->dir_output)) { + if (empty($entity) || empty($conf->categorie->multidir_output[$entity])) return array('accessallowed'=>0, 'error'=>'Value entity must be provided'); if ($fuser->rights->categorie->{$lire}) $accessallowed=1; $original_file=$conf->categorie->multidir_output[$entity].'/'.$original_file; } @@ -2202,6 +2208,7 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity, // Wrapping for third parties else if (($modulepart == 'company' || $modulepart == 'societe') && !empty($conf->societe->dir_output)) { + if (empty($entity) || empty($conf->societe->multidir_output[$entity])) return array('accessallowed'=>0, 'error'=>'Value entity must be provided'); if ($fuser->rights->societe->{$lire} || preg_match('/^specimen/i',$original_file)) { $accessallowed=1; @@ -2213,6 +2220,7 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity, // Wrapping for contact else if ($modulepart == 'contact' && !empty($conf->societe->dir_output)) { + if (empty($entity) || empty($conf->societe->multidir_output[$entity])) return array('accessallowed'=>0, 'error'=>'Value entity must be provided'); if ($fuser->rights->societe->{$lire}) { $accessallowed=1; @@ -2463,6 +2471,7 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity, // Wrapping pour les produits et services else if ($modulepart == 'product' || $modulepart == 'produit' || $modulepart == 'service' || $modulepart == 'produit|service') { + if (empty($entity) || (empty($conf->product->multidir_output[$entity]) && empty($conf->service->multidir_output[$entity]))) return array('accessallowed'=>0, 'error'=>'Value entity must be provided'); if (($fuser->rights->produit->{$lire} || $fuser->rights->service->{$lire}) || preg_match('/^specimen/i',$original_file)) { $accessallowed=1;