@@ -27,8 +27,6 @@ class LogException extends \Exception {
2727}
2828
2929
30- use CurlHandle ;
31-
3230class Updater {
3331 private string $ nextcloudDir ;
3432 private array $ configValues = [];
@@ -280,7 +278,6 @@ private function getExpectedElementsList(): array {
280278 'COPYING-AGPL ' ,
281279 'occ ' ,
282280 'db_structure.xml ' ,
283- 'REUSE.toml ' ,
284281 ];
285282 return array_merge ($ expected , $ this ->getAppDirectories ());
286283 }
@@ -520,7 +517,20 @@ private function getUpdateServerResponse(): array {
520517 $ this ->silentLog ('[info] updateURL: ' . $ updateURL );
521518
522519 // Download update response
523- $ curl = $ this ->getCurl ($ updateURL );
520+ $ curl = curl_init ();
521+ curl_setopt_array ($ curl , [
522+ CURLOPT_RETURNTRANSFER => 1 ,
523+ CURLOPT_URL => $ updateURL ,
524+ CURLOPT_USERAGENT => 'Nextcloud Updater ' ,
525+ ]);
526+
527+ if ($ this ->getConfigOption ('proxy ' ) !== null ) {
528+ curl_setopt_array ($ curl , [
529+ CURLOPT_PROXY => $ this ->getConfigOptionString ('proxy ' ),
530+ CURLOPT_PROXYUSERPWD => $ this ->getConfigOptionString ('proxyuserpwd ' ),
531+ CURLOPT_HTTPPROXYTUNNEL => $ this ->getConfigOption ('proxy ' ) ? 1 : 0 ,
532+ ]);
533+ }
524534
525535 /** @var false|string $response */
526536 $ response = curl_exec ($ curl );
@@ -555,20 +565,19 @@ private function getUpdateServerResponse(): array {
555565 *
556566 * @throws \Exception
557567 */
558- public function downloadUpdate (? string $ url = null ): void {
568+ public function downloadUpdate (): void {
559569 $ this ->silentLog ('[info] downloadUpdate() ' );
560570
561- if ($ url ) {
562- // If a URL is provided, use it directly
563- $ downloadURLs = [$ url ];
564- } else {
565- // Otherwise, get the download URLs from the update server
566- $ downloadURLs = $ this ->getDownloadURLs ();
571+ $ response = $ this ->getUpdateServerResponse ();
572+ if (!isset ($ response ['url ' ]) || !is_string ($ response ['url ' ])) {
573+ throw new \Exception ('Response from update server is missing url ' );
567574 }
568575
569- $ this ->silentLog ('[info] will try to download archive from: ' . implode (', ' , $ downloadURLs ));
570-
571576 $ storageLocation = $ this ->getUpdateDirectoryLocation () . '/updater- ' . $ this ->getConfigOptionMandatoryString ('instanceid ' ) . '/downloads/ ' ;
577+ $ saveLocation = $ storageLocation . basename ($ response ['url ' ]);
578+ $ this ->previousProgress = 0 ;
579+
580+ $ ch = curl_init ($ response ['url ' ]);
572581
573582 if (!file_exists ($ storageLocation )) {
574583 $ state = mkdir ($ storageLocation , 0750 , true );
@@ -583,64 +592,22 @@ public function downloadUpdate(?string $url = null): void {
583592 $ this ->silentLog ('[info] extracted Archive location exists ' );
584593 $ this ->recursiveDelete ($ storageLocation . 'nextcloud/ ' );
585594 }
586- }
587-
588- foreach ($ downloadURLs as $ url ) {
589- $ this ->previousProgress = 0 ;
590- $ saveLocation = $ storageLocation . basename ($ url );
591- if ($ this ->downloadArchive ($ url , $ saveLocation )) {
592- return ;
593- }
594- }
595-
596- throw new \Exception ('All downloads failed. See updater logs for more information. ' );
597- }
598-
599- private function getDownloadURLs (): array {
600- $ response = $ this ->getUpdateServerResponse ();
601- $ downloadURLs = [];
602- if (!isset ($ response ['downloads ' ]) || !is_array ($ response ['downloads ' ])) {
603- if (isset ($ response ['url ' ]) && is_string ($ response ['url ' ])) {
604- // Compatibility with previous verison of updater_server
605- $ ext = pathinfo ($ response ['url ' ], PATHINFO_EXTENSION );
606- $ response ['downloads ' ] = [
607- $ ext => [$ response ['url ' ]]
608- ];
609- } else {
610- throw new \Exception ('Response from update server is missing download URLs ' );
611- }
612- }
613- foreach ($ response ['downloads ' ] as $ format => $ urls ) {
614- if (!$ this ->isAbleToDecompress ($ format )) {
615- continue ;
616- }
617- foreach ($ urls as $ url ) {
618- if (!is_string ($ url )) {
619- continue ;
620- }
621- $ downloadURLs [] = $ url ;
595+ // see if there's an existing incomplete download to resume
596+ if (is_file ($ saveLocation )) {
597+ $ size = filesize ($ saveLocation );
598+ $ range = $ size . '- ' ;
599+ curl_setopt ($ ch , CURLOPT_RANGE , $ range );
600+ $ this ->silentLog ('[info] previous download found; resuming from ' . $ this ->formatBytes ($ size ));
622601 }
623602 }
624603
625- if (empty ($ downloadURLs )) {
626- throw new \Exception ('Your PHP install is not able to decompress any archive. Try to install modules like zip or bzip. ' );
627- }
628-
629- return array_unique ($ downloadURLs );
630-
631- }
632-
633- private function getCurl (string $ url ): CurlHandle {
634- $ ch = curl_init ($ url );
635- if ($ ch === false ) {
636- throw new \Exception ('Fail to open cUrl handler ' );
637- }
638-
604+ $ fp = fopen ($ saveLocation , 'a ' );
639605 curl_setopt_array ($ ch , [
640606 CURLOPT_RETURNTRANSFER => true ,
607+ CURLOPT_NOPROGRESS => false ,
608+ CURLOPT_PROGRESSFUNCTION => [$ this , 'downloadProgressCallback ' ],
609+ CURLOPT_FILE => $ fp ,
641610 CURLOPT_USERAGENT => 'Nextcloud Updater ' ,
642- CURLOPT_FOLLOWLOCATION => 1 ,
643- CURLOPT_MAXREDIRS => 2 ,
644611 ]);
645612
646613 if ($ this ->getConfigOption ('proxy ' ) !== null ) {
@@ -651,61 +618,47 @@ private function getCurl(string $url): CurlHandle {
651618 ]);
652619 }
653620
654- return $ ch ;
655- }
656-
657- private function downloadArchive (string $ fromUrl , string $ toLocation ): bool {
658- $ ch = $ this ->getCurl ($ fromUrl );
659-
660- // see if there's an existing incomplete download to resume
661- if (is_file ($ toLocation )) {
662- $ size = (int )filesize ($ toLocation );
663- $ range = $ size . '- ' ;
664- curl_setopt ($ ch , CURLOPT_RANGE , $ range );
665- $ this ->silentLog ('[info] previous download found; resuming from ' . $ this ->formatBytes ($ size ));
666- }
667-
668- $ fp = fopen ($ toLocation , 'ab ' );
669- if ($ fp === false ) {
670- throw new \Exception ('Fail to open file in ' . $ toLocation );
671- }
672-
673- curl_setopt_array ($ ch , [
674- CURLOPT_NOPROGRESS => false ,
675- CURLOPT_PROGRESSFUNCTION => [$ this , 'downloadProgressCallback ' ],
676- CURLOPT_FILE => $ fp ,
677- ]);
678-
679621 if (curl_exec ($ ch ) === false ) {
680622 throw new \Exception ('Curl error: ' . curl_error ($ ch ));
681623 }
682-
683624 $ httpCode = curl_getinfo ($ ch , CURLINFO_HTTP_CODE );
684625 if ($ httpCode !== 200 && $ httpCode !== 206 ) {
685- fclose ($ fp );
686- unlink ($ toLocation );
687- $ this ->silentLog ('[warn] fail to download archive from ' . $ fromUrl . '. Error: ' . $ httpCode . ' ' . curl_error ($ ch ));
688- curl_close ($ ch );
626+ $ statusCodes = [
627+ 400 => 'Bad request ' ,
628+ 401 => 'Unauthorized ' ,
629+ 403 => 'Forbidden ' ,
630+ 404 => 'Not Found ' ,
631+ 500 => 'Internal Server Error ' ,
632+ 502 => 'Bad Gateway ' ,
633+ 503 => 'Service Unavailable ' ,
634+ 504 => 'Gateway Timeout ' ,
635+ ];
636+
637+ $ message = 'Download failed ' ;
638+ if (is_int ($ httpCode ) && isset ($ statusCodes [$ httpCode ])) {
639+ $ message .= ' - ' . $ statusCodes [$ httpCode ] . ' (HTTP ' . $ httpCode . ') ' ;
640+ } else {
641+ $ message .= ' - HTTP status code: ' . (string )$ httpCode ;
642+ }
689643
690- return false ;
644+ $ curlErrorMessage = curl_error ($ ch );
645+ if (!empty ($ curlErrorMessage )) {
646+ $ message .= ' - curl error message: ' . $ curlErrorMessage ;
647+ }
648+
649+ $ message .= ' - URL: ' . htmlentities ($ response ['url ' ]);
650+
651+ throw new \Exception ($ message );
652+ } else {
653+ // download succeeded
654+ $ info = curl_getinfo ($ ch );
655+ $ this ->silentLog ('[info] download stats: size= ' . $ this ->formatBytes ((int )$ info ['size_download ' ]) . ' bytes; total_time= ' . round ($ info ['total_time ' ], 2 ) . ' secs; avg speed= ' . $ this ->formatBytes ((int )$ info ['speed_download ' ]) . '/sec ' );
691656 }
692- // download succeeded
693- $ info = curl_getinfo ($ ch );
694- $ this ->silentLog ('[info] download stats: size= ' . $ this ->formatBytes ((int )$ info ['size_download ' ]) . ' bytes; total_time= ' . round ($ info ['total_time ' ], 2 ) . ' secs; avg speed= ' . $ this ->formatBytes ((int )$ info ['speed_download ' ]) . '/sec ' );
695657
696658 curl_close ($ ch );
697659 fclose ($ fp );
698660
699661 $ this ->silentLog ('[info] end of downloadUpdate() ' );
700- return true ;
701- }
702-
703- /**
704- * Check if PHP is able to decompress archive format
705- */
706- private function isAbleToDecompress (string $ ext ): bool {
707- // Only zip is supported for now
708- return $ ext === 'zip ' && extension_loaded ($ ext );
709662 }
710663
711664 private function downloadProgressCallback (\CurlHandle $ resource , int $ download_size , int $ downloaded , int $ upload_size , int $ uploaded ): void {
@@ -722,7 +675,7 @@ private function downloadProgressCallback(\CurlHandle $resource, int $download_s
722675 }
723676
724677 private function formatBytes (int $ bytes , int $ precision = 2 ): string {
725- $ units = ['B ' , 'KiB ' , 'MiB ' , 'GiB ' , 'TiB ' ];
678+ $ units = ['B ' , 'KB ' , 'MB ' , 'GB ' , 'TB ' ];
726679
727680 $ bytes = max ($ bytes , 0 );
728681 $ pow = floor (($ bytes ? log ($ bytes ) : 0 ) / log (1024 ));
@@ -744,34 +697,28 @@ private function getDownloadedFilePath(): string {
744697
745698 $ filesInStorageLocation = scandir ($ storageLocation );
746699 $ files = array_values (array_filter ($ filesInStorageLocation , function (string $ path ) {
747- // Match files with - in the name and extension (*-*.*)
748- return preg_match ('/^.*-.*\..*$/i ' , $ path );
700+ return $ path !== '. ' && $ path !== '.. ' ;
749701 }));
750702 // only the downloaded archive
751703 if (count ($ files ) !== 1 ) {
752704 throw new \Exception ('There are more files than the downloaded archive in the downloads/ folder. ' );
753705 }
754- return $ storageLocation . $ files [0 ];
706+ return $ storageLocation . ' / ' . $ files [0 ];
755707 }
756708
757709 /**
758710 * Verifies the integrity of the downloaded file
759711 *
760712 * @throws \Exception
761713 */
762- public function verifyIntegrity (? string $ urlOverride = null ): void {
714+ public function verifyIntegrity (): void {
763715 $ this ->silentLog ('[info] verifyIntegrity() ' );
764716
765717 if ($ this ->getCurrentReleaseChannel () === 'daily ' ) {
766718 $ this ->silentLog ('[info] current channel is "daily" which is not signed. Skipping verification. ' );
767719 return ;
768720 }
769721
770- if ($ urlOverride ) {
771- $ this ->silentLog ('[info] custom download url provided, cannot verify signature ' );
772- return ;
773- }
774-
775722 $ response = $ this ->getUpdateServerResponse ();
776723 if (empty ($ response ['signature ' ])) {
777724 throw new \Exception ('No signature specified for defined update ' );
@@ -1059,7 +1006,7 @@ private function moveWithExclusions(string $dataLocation, array $excludedElement
10591006 throw new \Exception ('Could not mkdir ' . $ this ->nextcloudDir . '/ ' . dirname ($ fileName ));
10601007 }
10611008 }
1062- $ state = @ rename ($ path , $ this ->nextcloudDir . '/ ' . $ fileName );
1009+ $ state = rename ($ path , $ this ->nextcloudDir . '/ ' . $ fileName );
10631010 if ($ state === false ) {
10641011 throw new \Exception (
10651012 sprintf (
@@ -1129,6 +1076,20 @@ public function finalize(): void {
11291076 throw new \Exception ('Could not rmdir .step ' );
11301077 }
11311078
1079+ /* Check if there is the need to extend .user.ini */
1080+ $ user_ini_additional_lines = $ this ->getConfigOption ('user_ini_additional_lines ' );
1081+ if ($ user_ini_additional_lines ) {
1082+ $ this ->silentLog ('[info] Extend .user.ini ' );
1083+ if (is_array ($ user_ini_additional_lines )) {
1084+ $ user_ini_additional_lines = implode (PHP_EOL , $ user_ini_additional_lines );
1085+ }
1086+
1087+ $ result = file_put_contents ($ this ->nextcloudDir . '/.user.ini ' , PHP_EOL . '; Additional settings from config.php: ' . PHP_EOL . $ user_ini_additional_lines . PHP_EOL , FILE_APPEND );
1088+ if ($ result === false ) {
1089+ throw new \Exception ('Could not append to .user.ini ' );
1090+ }
1091+ }
1092+
11321093 if (function_exists ('opcache_reset ' )) {
11331094 $ this ->silentLog ('[info] call opcache_reset() ' );
11341095 opcache_reset ();
0 commit comments