diff --git a/htdocs/api/class/api_documents.class.php b/htdocs/api/class/api_documents.class.php index da9545aa248..bdf86a5c2c9 100644 --- a/htdocs/api/class/api_documents.class.php +++ b/htdocs/api/class/api_documents.class.php @@ -535,13 +535,14 @@ class Documents extends DolibarrApi * Test sample for supplier invoice: { "filename": "mynewfile.txt", "modulepart": "supplier_invoice", "ref": "FA1701-001", "subdir": "", "filecontent": "content text", "fileencoding": "", "overwriteifexists": "0" }. * Test sample for medias file: { "filename": "mynewfile.txt", "modulepart": "medias", "ref": "", "subdir": "image/mywebsite", "filecontent": "Y29udGVudCB0ZXh0Cg==", "fileencoding": "base64", "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 $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) + * @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 $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) + * @param int $createdirifnotexists Create subdirectories if the doesn't exists (1 by default) * @return string * * @throws RestException 400 @@ -551,7 +552,7 @@ class Documents extends DolibarrApi * * @url POST /upload */ - public function post($filename, $modulepart, $ref = '', $subdir = '', $filecontent = '', $fileencoding = '', $overwriteifexists = 0) + public function post($filename, $modulepart, $ref = '', $subdir = '', $filecontent = '', $fileencoding = '', $overwriteifexists = 0, $createdirifnotexists = 1) { global $db, $conf; @@ -578,6 +579,8 @@ class Documents extends DolibarrApi // Define $uploadir $object = null; $entity = DolibarrApiAccess::$user->entity; + if (empty($entity)) $entity = 1; + if ($ref) { $tmpreldir = ''; @@ -663,8 +666,7 @@ class Documents extends DolibarrApi } } - if (!($object->id > 0)) - { + if (!($object->id > 0)) { throw new RestException(404, 'The object '.$modulepart." with ref '".$ref."' was not found."); } @@ -681,29 +683,32 @@ class Documents extends DolibarrApi 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.'); + throw new RestException(500, 'This value of modulepart ('.$modulepart.') does not support yet usage of ref. Check modulepart parameter or try to use subdir parameter instead of ref.'); } } else { if ($modulepart == 'invoice') $modulepart = 'facture'; if ($modulepart == 'member') $modulepart = 'adherent'; $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.'); + if (empty($upload_dir) || $upload_dir == '/') { + if (!empty($tmp['error'])) { + throw new RestException(401, 'Error returned by dol_check_secure_access_document: '.$tmp['error']); + } else { + throw new RestException(500, 'This value of modulepart ('.$modulepart.') is not allowed with this value of subdir ('.$relativefile.')'); + } } } // $original_file here is still value of filename without any dir. $upload_dir = dol_sanitizePathName($upload_dir); - if (dol_mkdir($upload_dir) < 0) // needed by products - { - throw new RestException(500, 'Error while trying to create directory.'); + if (!empty($createdirifnotexists)) { + if (dol_mkdir($upload_dir) < 0) { // needed by products + throw new RestException(500, 'Error while trying to create directory.'); + } } $destfile = $upload_dir.'/'.$original_file; @@ -715,8 +720,7 @@ class Documents extends DolibarrApi throw new RestException(401, 'Directory not exists : '.dirname($destfile)); } - if (!$overwriteifexists && dol_is_file($destfile)) - { + if (!$overwriteifexists && dol_is_file($destfile)) { throw new RestException(500, "File with name '".$original_file."' already exists."); } diff --git a/htdocs/core/lib/files.lib.php b/htdocs/core/lib/files.lib.php index 0be1cfbc3bc..22545b78cf8 100644 --- a/htdocs/core/lib/files.lib.php +++ b/htdocs/core/lib/files.lib.php @@ -2231,7 +2231,7 @@ function dol_check_secure_access_document($modulepart, $original_file, $entity, // Fix modulepart if ($modulepart == 'users') $modulepart = 'user'; - dol_syslog('modulepart='.$modulepart.' original_file='.$original_file.' entity='.$entity); + dol_syslog('dol_check_secure_access_document modulepart='.$modulepart.' original_file='.$original_file.' entity='.$entity); // We define $accessallowed and $sqlprotectagainstexternals $accessallowed = 0; diff --git a/htdocs/core/lib/geturl.lib.php b/htdocs/core/lib/geturl.lib.php index 8cfc08222cd..7e93623f3c1 100644 --- a/htdocs/core/lib/geturl.lib.php +++ b/htdocs/core/lib/geturl.lib.php @@ -33,7 +33,7 @@ * @param string[] $addheaders Array of string to add into header. Example: ('Accept: application/xrds+xml', ....) * @param string[] $allowedschemes List of schemes that are allowed ('http' + 'https' only by default) * @param int $localurl 0=Only external URL are possible, 1=Only local URL, 2=Both external and local URL are allowed. - * @return array Returns an associative array containing the response from the server array('content'=>response,'curl_error_no'=>errno,'curl_error_msg'=>errmsg...) + * @return array Returns an associative array containing the response from the server array('content'=>response, 'curl_error_no'=>errno, 'curl_error_msg'=>errmsg...) */ function getURLContent($url, $postorget = 'GET', $param = '', $followlocation = 1, $addheaders = array(), $allowedschemes = array('http', 'https'), $localurl = 0) { diff --git a/test/phpunit/AllTests.php b/test/phpunit/AllTests.php index 163bf320075..ce7f1074721 100644 --- a/test/phpunit/AllTests.php +++ b/test/phpunit/AllTests.php @@ -216,6 +216,8 @@ class AllTests require_once dirname(__FILE__).'/RestAPIUserTest.php'; $suite->addTestSuite('RestAPIUserTest'); + require_once dirname(__FILE__).'/RestAPIDocumentTest.php'; + $suite->addTestSuite('RestAPIDocumentTest'); // Test only with php7.2 or less //if ((float) phpversion() < 7.3) diff --git a/test/phpunit/RestAPIDocumentTest.php b/test/phpunit/RestAPIDocumentTest.php index 8dbaafab018..f45e0437ce5 100644 --- a/test/phpunit/RestAPIDocumentTest.php +++ b/test/phpunit/RestAPIDocumentTest.php @@ -152,35 +152,43 @@ class RestAPIDocumentTest extends PHPUnit\Framework\TestCase { global $conf,$user,$langs,$db; - $url = $this->api_url.'/documents/?api_key='.$this->api_key; + $url = $this->api_url.'/documents/upload?api_key='.$this->api_key; echo __METHOD__.' Request POST url='.$url."\n"; // Send to non existent directory - dol_delete_dir_recursive(DOL_DATA_ROOT.'/medias/tmpphpunit'); + dol_delete_dir_recursive(DOL_DATA_ROOT.'/medias/tmpphpunit/tmpphpunit1'); //$data = '{ "filename": "mynewfile.txt", "modulepart": "medias", "ref": "", "subdir": "mysubdir1/mysubdir2", "filecontent": "content text", "fileencoding": "" }'; $data = array( 'filename'=>"mynewfile.txt", 'modulepart'=>"medias", - 'ref'=>"", - 'subdir'=>"tmpphpunit/tmpphpunit2", + 'subdir'=>"tmpphpunit/tmpphpunit1", 'filecontent'=>"content text", - 'fileencoding'=>"" + 'fileencoding'=>"", + 'overwriteifexists'=>0, + 'createdirifnotexists'=>0 ); - $result = getURLContent($url, 'POST', $data, 1, array(), array('http', 'https'), 2); + $param = ''; + foreach($data as $key => $val) { + $param .= '&'.$key.'='.urlencode($val); + } + + $result = getURLContent($url, 'POST', $param, 1, array(), array('http', 'https'), 2); echo __METHOD__.' Result for sending document: '.var_export($result, true)."\n"; echo __METHOD__.' curl_error_no: '.$result['curl_error_no']."\n"; $object = json_decode($result['content'], true); - $this->assertNotNull($object, 'Parsing of json result must no be null'); - $this->assertEquals('401', $object['error']['code']); + $this->assertNotNull($object, 'Parsing of json result must not be null'); + $this->assertEquals('401', $result['http_code'], 'Return code is not 401'); + $this->assertEquals('401', empty($object['error']['code']) ? '' : $object['error']['code'], 'Error code is not 401'); // Send to existent directory + dol_delete_dir_recursive(DOL_DATA_ROOT.'/medias/tmpphpunit/tmpphpunit2'); dol_mkdir(DOL_DATA_ROOT.'/medias/tmpphpunit/tmpphpunit2'); $data = array( @@ -189,16 +197,53 @@ class RestAPIDocumentTest extends PHPUnit\Framework\TestCase 'ref'=>"", 'subdir'=>"tmpphpunit/tmpphpunit2", 'filecontent'=>"content text", - 'fileencoding'=>"" + 'fileencoding'=>"", + 'overwriteifexists'=>0, + 'createdirifnotexists'=>0 ); - $result2 = getURLContent($url, 'POST', $data, 1, array(), array('http', 'https'), 2); + $param = ''; + foreach($data as $key => $val) { + $param .= '&'.$key.'='.urlencode($val); + } + + $result2 = getURLContent($url, 'POST', $param, 1, array(), array('http', 'https'), 2); echo __METHOD__.' Result for sending document: '.var_export($result2, true)."\n"; echo __METHOD__.' curl_error_no: '.$result2['curl_error_no']."\n"; $object2 = json_decode($result2['content'], true); - $this->assertNotNull($object2, 'Parsing of json result must no be null'); + //$this->assertNotNull($object2, 'Parsing of json result must not be null'); + $this->assertEquals('200', $result2['http_code'], 'Return code must be 200'); $this->assertEquals($result2['curl_error_no'], ''); - $this->assertEquals($result2['content'], 'true'); + $this->assertEquals($object2, 'mynewfile.txt', 'Must contains basename of file'); + + + dol_delete_dir_recursive(DOL_DATA_ROOT.'/medias/tmpphpunit/tmpphpunit3'); + + $data = array( + 'filename'=>"mynewfile.txt", + 'modulepart'=>"medias", + 'ref'=>"", + 'subdir'=>"tmpphpunit/tmpphpunit3", + 'filecontent'=>"content text", + 'fileencoding'=>"", + 'overwriteifexists'=>0, + 'createdirifnotexists'=>1 + ); + + $param = ''; + foreach($data as $key => $val) { + $param .= '&'.$key.'='.urlencode($val); + } + + $result3 = getURLContent($url, 'POST', $param, 1, array(), array('http', 'https'), 2); + echo __METHOD__.' Result for sending document: '.var_export($result3, true)."\n"; + echo __METHOD__.' curl_error_no: '.$result3['curl_error_no']."\n"; + $object3 = json_decode($result3['content'], true); + //$this->assertNotNull($object2, 'Parsing of json result must not be null'); + $this->assertEquals('200', $result3['http_code'], 'Return code must be 200'); + $this->assertEquals($result3['curl_error_no'], ''); + $this->assertEquals($object3, 'mynewfile.txt', 'Must contains basename of file'); + dol_delete_dir_recursive(DOL_DATA_ROOT.'/medias/tmpphpunit'); }