55use Piwik \Common ;
66use Piwik \DataTable \Renderer \Json ;
77use Piwik \Http ;
8+ use Piwik \Option ;
89use Piwik \Piwik ;
910use Piwik \Plugins \GeoIp2 \LocationProvider \GeoIp2 ;
1011use Piwik \Plugins \UserCountry \UserCountry ;
1112use Piwik \View ;
1213
1314class Controller extends \Piwik \Plugin \ControllerAdmin
1415{
16+ private const DOWNLOAD_URL_OPTION_PREFIX = 'geoip2.download_url. ' ;
17+
1518 /**
1619 * Starts or continues download of DBIP-City.mmdb.
1720 *
@@ -28,6 +31,8 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
2831 * 'expected_file_size' - The expected finished file size as returned by the HTTP server.
2932 * 'next_screen' - When the download finishes, this is the next screen that should be shown.
3033 * 'error' - When an error occurs, the message is returned in this property.
34+ *
35+ * @return string
3136 */
3237 public function downloadFreeDBIPLiteDB ()
3338 {
@@ -64,11 +69,13 @@ public function downloadFreeDBIPLiteDB()
6469 $ result ['settings ' ] = GeoIP2AutoUpdater::getConfiguredUrls ();
6570 }
6671
67- return json_encode ($ result );
72+ return ( string ) json_encode ($ result );
6873 } catch (\Exception $ ex ) {
69- return json_encode (array ('error ' => $ ex ->getMessage ()));
74+ return ( string ) json_encode (array ('error ' => $ ex ->getMessage ()));
7075 }
7176 }
77+
78+ return '' ;
7279 }
7380
7481 /**
@@ -83,6 +90,8 @@ public function downloadFreeDBIPLiteDB()
8390 * Output (json):
8491 * 'error' - if an error occurs its message is set as the resulting JSON object's
8592 * 'error' property.
93+ *
94+ * @return string
8695 */
8796 public function updateGeoIPLinks ()
8897 {
@@ -100,17 +109,19 @@ public function updateGeoIPLinks()
100109 $ info = $ this ->getNextMissingDbUrlInfoGeoIp2 ();
101110
102111 if ($ info !== false ) {
103- return json_encode ($ info );
112+ return ( string ) json_encode ($ info );
104113 } else {
105114 $ view = new View ("@GeoIp2/_updaterNextRunTime " );
106115 $ view ->nextRunTime = GeoIP2AutoUpdater::getNextRunTime ();
107116 $ nextRunTimeHtml = $ view ->render ();
108- return json_encode (array ('nextRunTime ' => $ nextRunTimeHtml ));
117+ return ( string ) json_encode (array ('nextRunTime ' => $ nextRunTimeHtml ));
109118 }
110119 } catch (\Exception $ ex ) {
111- return json_encode (array ('error ' => $ ex ->getMessage ()));
120+ return ( string ) json_encode (array ('error ' => $ ex ->getMessage ()));
112121 }
113122 }
123+
124+ return '' ;
114125 }
115126
116127 /**
@@ -129,6 +140,8 @@ public function updateGeoIPLinks()
129140 * downloading.
130141 * 'current_size' - Size of the current file on disk.
131142 * 'expected_file_size' - Size of the completely downloaded file.
143+ *
144+ * @return string
132145 */
133146 public function downloadMissingGeoIpDb ()
134147 {
@@ -144,39 +157,130 @@ public function downloadMissingGeoIpDb()
144157 // based on the database type (provided by the 'key' query param) determine the
145158 // url & output file name
146159 $ key = Common::getRequestVar ('key ' , null , 'string ' );
160+ $ isContinuation = Common::getRequestVar ('continue ' , true , 'int ' );
147161
148162 $ url = GeoIP2AutoUpdater::getConfiguredUrl ($ key );
163+ if (!is_string ($ url ) || $ url === '' ) {
164+ throw new \Exception (Piwik::translate ('General_DownloadFail_HttpRequestFail ' ));
165+ }
166+ $ this ->trackOrValidateConfiguredDownloadUrl ($ key , $ url , $ isContinuation );
167+
149168 $ filename = GeoIP2AutoUpdater::getZippedFilenameToDownloadTo ($ url , $ key , GeoIP2AutoUpdater::getGeoIPUrlExtension ($ url ));
150169 $ outputPath = GeoIP2AutoUpdater::getTemporaryFolder ($ filename , true );
151170
152171 // download part of the file
153172 $ result = Http::downloadChunk (
154173 $ url ,
155174 $ outputPath ,
156- Common:: getRequestVar ( ' continue ' , true , ' int ' )
175+ $ isContinuation
157176 );
158177
159178 // if the file is done
160179 if ($ result ['current_size ' ] >= $ result ['expected_file_size ' ]) {
180+ $ this ->assertConfiguredUrlDidNotChangeDuringDownload ($ key , $ url );
161181 GeoIP2AutoUpdater::unzipDownloadedFile ($ outputPath , $ key , $ url , $ unlink = true );
182+ $ this ->deleteTrackedDownloadUrl ($ key );
162183
163184 $ info = $ this ->getNextMissingDbUrlInfoGeoIp2 ();
164185 if ($ info !== false ) {
165- return json_encode ($ info );
186+ return ( string ) json_encode ($ info );
166187 }
167188 }
168189
169- return json_encode ($ result );
190+ return ( string ) json_encode ($ result );
170191 } catch (\Exception $ ex ) {
171- return json_encode (array ('error ' => $ ex ->getMessage ()));
192+ return ( string ) json_encode (array ('error ' => $ ex ->getMessage ()));
172193 }
173194 }
195+
196+ return '' ;
197+ }
198+
199+ private function trackOrValidateConfiguredDownloadUrl (string $ key , string $ configuredUrl , int $ isContinuation ): void
200+ {
201+ $ optionName = $ this ->getDownloadUrlOptionName ($ key );
202+ $ expectedUrl = Option::get ($ optionName );
203+
204+ if (!$ isContinuation || empty ($ expectedUrl )) {
205+ Option::set ($ optionName , (string ) $ configuredUrl );
206+ return ;
207+ }
208+
209+ if ((string ) $ expectedUrl !== (string ) $ configuredUrl ) {
210+ $ this ->abortDownloadForChangedConfiguredUrl ($ key , (string ) $ expectedUrl , $ configuredUrl );
211+ }
212+ }
213+
214+ private function assertConfiguredUrlDidNotChangeDuringDownload (string $ key , string $ expectedUrl ): void
215+ {
216+ $ configuredUrl = GeoIP2AutoUpdater::getConfiguredUrl ($ key );
217+
218+ if ((string ) $ configuredUrl !== (string ) $ expectedUrl ) {
219+ $ this ->abortDownloadForChangedConfiguredUrl ($ key , $ expectedUrl , (string ) $ configuredUrl );
220+ }
221+ }
222+
223+ private function abortDownloadForChangedConfiguredUrl (string $ key , string $ expectedUrl , string $ configuredUrl ): void
224+ {
225+ $ this ->deleteDownloadedChunksForType ($ key , $ expectedUrl , $ configuredUrl );
226+ $ this ->deleteTrackedDownloadUrl ($ key );
227+
228+ throw new \Exception (Piwik::translate ('General_DownloadFail_HttpRequestFail ' ));
229+ }
230+
231+ private function deleteDownloadedChunksForType (string $ key , string $ expectedUrl , string $ configuredUrl ): void
232+ {
233+ $ pathsToDelete = [];
234+
235+ $ expectedOutputPath = $ this ->getDownloadOutputPath ($ key , $ expectedUrl );
236+ if (!empty ($ expectedOutputPath )) {
237+ $ pathsToDelete [] = $ expectedOutputPath ;
238+ }
239+
240+ $ configuredOutputPath = $ this ->getDownloadOutputPath ($ key , $ configuredUrl );
241+ if (!empty ($ configuredOutputPath )) {
242+ $ pathsToDelete [] = $ configuredOutputPath ;
243+ }
244+
245+ foreach (array_unique ($ pathsToDelete ) as $ downloadPath ) {
246+ if (!is_file ($ downloadPath )) {
247+ continue ;
248+ }
249+
250+ @unlink ($ downloadPath );
251+ Option::delete ($ downloadPath . '_expectedDownloadSize ' );
252+ }
253+ }
254+
255+ private function getDownloadOutputPath (string $ key , string $ url ): ?string
256+ {
257+ try {
258+ $ filename = GeoIP2AutoUpdater::getZippedFilenameToDownloadTo (
259+ $ url ,
260+ $ key ,
261+ GeoIP2AutoUpdater::getGeoIPUrlExtension ($ url )
262+ );
263+
264+ return GeoIP2AutoUpdater::getTemporaryFolder ($ filename , true );
265+ } catch (\Exception $ e ) {
266+ return null ;
267+ }
268+ }
269+
270+ private function getDownloadUrlOptionName (string $ key ): string
271+ {
272+ return self ::DOWNLOAD_URL_OPTION_PREFIX . $ key ;
273+ }
274+
275+ private function deleteTrackedDownloadUrl (string $ key ): void
276+ {
277+ Option::delete ($ this ->getDownloadUrlOptionName ($ key ));
174278 }
175279
176280 /**
177281 * Gets information for the first missing GeoIP2 database (if any).
178282 *
179- * @return array|bool
283+ * @return array<string, string>|false
180284 */
181285 private function getNextMissingDbUrlInfoGeoIp2 ()
182286 {
@@ -195,7 +299,7 @@ private function getNextMissingDbUrlInfoGeoIp2()
195299 return false ;
196300 }
197301
198- private function dieIfGeolocationAdminIsDisabled ()
302+ private function dieIfGeolocationAdminIsDisabled (): void
199303 {
200304 if (!UserCountry::isGeoLocationAdminEnabled ()) {
201305 throw new \Exception ('Geo location setting page has been disabled. ' );
0 commit comments