diff --git a/.gitignore b/.gitignore index ff72e2d08c7e284b7a0c34eef012f55628fea54d..27d28a7b7c51527995398c234c0b7f187bb03229 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /composer.lock /vendor +.idea diff --git a/Dockerfile b/Dockerfile index 5b427f1cca9487af08e4f8f636726d6fa296e0c6..290b03e1565507749770e978ed0c8a1d6b5deb6b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,35 @@ -FROM php:7.1-apache +FROM php:7.2-apache MAINTAINER Julian Xhokaxhiu # internal variables ENV HTML_DIR /var/www/html ENV FULL_BUILDS_DIR $HTML_DIR/builds/full +ARG DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --fix-missing \ + apt-utils \ + gnupg + +RUN apt-get update \ + && apt-get install -y git zlib1g-dev libzip-dev\ + && docker-php-ext-install zip\ + && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false zlib1g-dev libzip-dev + +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" + # set the working directory WORKDIR $HTML_DIR +# add all the project files +COPY . $HTML_DIR # enable mod_rewrite RUN a2enmod rewrite +COPY conf/perf.conf /etc/apache2/conf-available/perf.conf +RUN a2enconf perf # install the PHP extensions we need RUN apt-get update \ + && apt-get install nano \ && buildDeps=" \ zlib1g-dev \ " \ @@ -25,8 +42,7 @@ RUN apt-get update \ && docker-php-ext-enable apcu \ \ && docker-php-source delete \ - && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false $buildDeps - + && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false $buildDeps # set recommended settings for APCu # see http://php.net/manual/en/apcu.configuration.php RUN { \ @@ -49,6 +65,3 @@ RUN composer install --no-plugins --no-scripts # fix permissions RUN chmod -R 0775 /var/www/html \ && chown -R www-data:www-data /var/www/html - -# create volumes -VOLUME $FULL_BUILDS_DIR \ No newline at end of file diff --git a/LICENSE b/LICENSE index cfc5daf70d013f47ad0114513ad2e04e73e70aa6..231b13affd4887705c3af1f054eef430fae4bb85 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016 Julian Xhokaxhiu +Copyright (c) 2020 Julian Xhokaxhiu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index 2e647c1739cdb927a48c543c94188500f8776d31..7645b22333f6bf4467915a0f2cf8d0c295b092ec 100644 --- a/README.md +++ b/README.md @@ -14,29 +14,6 @@ Got a question? Not sure where it should be made? See [CONTRIBUTING](CONTRIBUTIN ## How to use -### Test - -``` -$ sudo docker build -t e/lineageota . \ - && sudo docker run \ - -d \ - -p 80:80 \ - --rm \ - -v "${PWD}/builds/full:/var/www/html/builds/full" \ - --name lineageota \ - e/lineageota -``` - -Access to API via http://localhost/api/v1/\/\ - -Get logs with - -``` -$ sudo docker exec -ti lineageota tail -f LineageOTA.log -``` - -You can also use this server with a device connected to the same network, specifying the correct IP (see below). - ### Composer ```shell @@ -44,7 +21,7 @@ $ cd /var/www/html # Default Apache WWW directory, feel free to choose your own $ composer create-project julianxhokaxhiu/lineage-ota LineageOTA ``` -then finally visit http://localhost/LineageOTA to see the REST Server up and running. +then finally visit http://localhost/LineageOTA to see the REST Server up and running. Please note that this is only for a quick test, when you plan to use that type of setup for production (your users), make sure to also provide HTTPS support. > If you get anything else then a list of files, contained inside the `builds` directory, this means something is wrong in your environment. Double check it, before creating an issue report here. @@ -124,10 +101,12 @@ In order to integrate this in your LineageOS based ROM, you need to add the [`li ```properties # ... -lineage.updater.uri=http://my.ota.uri/api/v1/{device}/{type}/{incr} +lineage.updater.uri=https://my.ota.uri/api/v1/{device}/{type}/{incr} # ... ``` +Make always sure to provide a HTTPS based uri, otherwise the updater will reject to connect with your server! This is caused by the security policies newer versions of Android (at least 10+) include, as any app wanting to use non-secured connections must explicitly enable this during the compilation. The LineageOS Updater does not support that. + > Since https://review.lineageos.org/#/c/191274/ is merged, the property `cm.updater.uri` is renamed to `lineage.updater.uri`. Make sure to update your entry. > As of [5252d60](https://github.com/LineageOS/android_packages_apps_Updater/commit/5252d606716c3f8d81617babc1293c122359a94d): @@ -147,6 +126,12 @@ In order to integrate this in your [CyanogenMod](https://github.com/lineageos/an ## Changelog +### v2.9.0 +- Add PHP 7.4 compatibility: Prevent null array access on `isValid()` ( thanks to @McNutnut ) +- Update RegEx pattern to match more roms than just CM/LineageOS ( thanks to @toolstack ) +- Use Forwarded HTTP Extension to determine protocol and host ( thanks to @TpmKranz ) +- Add detection of HTTP_X_FORWARDED_* headers ( thanks to @ionphractal ) + ### v2.8.0 - Use md5sum files if available ( thanks to @jplitza ) diff --git a/composer.json b/composer.json index 5b076bf68bae96901e76e3ecef25461202b02824..6d66bcec1e42a6aa3bb6493a2ea855f7a0cc45c5 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "julianxhokaxhiu/lineage-ota", "description": "A simple OTA REST Server for LineageOS OTA Updater System Application", - "version": "2.8.0", + "version": "2.9.0", "type": "project", "keywords": [ "android", @@ -25,7 +25,7 @@ { "type":"package", "package": { - "name": "julianxhokaxhiu/DotNotation", + "name": "julianxhokaxhiu/dotnotation", "version": "master", "source": { "url": "https://gist.github.com/a6098de64195f604f56a.git", @@ -42,7 +42,7 @@ "mikecao/flight": "1.*", "julianxhokaxhiu/dotnotation": "dev-master", "ext-zip": "*", - "monolog/monolog": "1.23.*" + "monolog/monolog": "2.2.*" }, "autoload": { "psr-4": { diff --git a/conf/perf.conf b/conf/perf.conf new file mode 100644 index 0000000000000000000000000000000000000000..9d94c96adeb4163e648efd1984859705a59bc39e --- /dev/null +++ b/conf/perf.conf @@ -0,0 +1,2 @@ +ServerLimit 1024 +MaxClients 1024 \ No newline at end of file diff --git a/index.php b/index.php index fbcef566e921075ff518c1ec71466a699abdc423..0a22afb0c611719c456f09cb923f2ad0b153c78c 100644 --- a/index.php +++ b/index.php @@ -2,7 +2,7 @@ /* The MIT License (MIT) - Copyright (c) 2016 Julian Xhokaxhiu + Copyright (c) 2020 Julian Xhokaxhiu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -28,8 +28,33 @@ use Monolog\Logger; use Monolog\Handler\StreamHandler; - $protocol = 'https://'; + if ( isset($_SERVER['HTTP_FORWARDED']) ) { + $fwd_ar = explode(';', $_SERVER['HTTP_FORWARDED']); + for ( $i = 0; $i < count($fwd_ar); $i++ ) { + $kv = explode('=', $fwd_ar[$i]); + if ( count($kv) > 1 ) { + $forwarded[strtoupper($kv[0])] = $kv[1]; + } + } + if ( array_key_exists('HOST', $forwarded) ) { + $_SERVER['HTTP_HOST'] = $forwarded['HOST']; + } + if ( array_key_exists('PROTO', $forwarded) && strtoupper($forwarded['PROTO']) === 'HTTPS') { + $_SERVER['HTTPS'] = 'on'; + } + } else { + if ( isset($_SERVER['HTTP_X_FORWARDED_HOST']) ) { + $_SERVER['HTTP_HOST'] = $_SERVER['HTTP_X_FORWARDED_HOST']; + } + if ( isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtoupper($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'HTTPS' ) { + $_SERVER['HTTPS'] = 'on'; + } + } + if( isset($_SERVER['HTTPS']) ) + $protocol = 'https://'; + else + $protocol = 'http://'; $logger = new Logger('main'); $logger->pushHandler(new StreamHandler('LineageOTA.log', Logger::WARNING)); diff --git a/src/CmOta.php b/src/CmOta.php index e3fb532b5d2cd0c1442f99cbd093711ab43a8cbc..296c1b9e49b519041be8c937d9cdbdecf53281eb 100644 --- a/src/CmOta.php +++ b/src/CmOta.php @@ -2,7 +2,7 @@ /* The MIT License (MIT) - Copyright (c) 2016 Julian Xhokaxhiu + Copyright (c) 2020 Julian Xhokaxhiu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/src/Helpers/Build.php b/src/Helpers/Build.php index 68574e2f8c0fce0ef44d570ac68e83a85bc829f5..b802e421d7461ace46d944eb677e27b2d0edc658 100644 --- a/src/Helpers/Build.php +++ b/src/Helpers/Build.php @@ -2,7 +2,7 @@ /* The MIT License (MIT) - Copyright (c) 2016 Julian Xhokaxhiu + Copyright (c) 2020 Julian Xhokaxhiu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -50,7 +50,7 @@ * @param type $physicalPath The current path where the build lives */ public function __construct($fileName, $physicalPath, $logger) { - $this->logger = $logger; + $this->logger = $logger; /* $tokens Schema: @@ -81,16 +81,44 @@ // - builds/CURRENT_ZIP_FILE.zip.prop ( which must exist ) $this->buildProp = explode( "\n", @file_get_contents($this->getPropFilePath())); // Try to fetch build.prop values. In some cases, we can provide a fallback, in other a null value will be given + $prop = ""; $this->timestamp = intval( $this->getBuildPropValue( 'ro.build.date.utc' ) ?? filemtime($this->filePath) ); + $prop.='ro.build.date.utc='. $this->timestamp . "\n"; $this->incremental = $this->getBuildPropValue( 'ro.build.version.incremental' ) ?? ''; + $prop.='ro.build.version.incremental='. $this->incremental . "\n"; $this->apiLevel = $this->getBuildPropValue( 'ro.build.version.sdk' ) ?? ''; - $this->model = $this->getBuildPropValue( 'ro.lineage.device' ) ?? $this->getBuildPropValue( 'ro.cm.device' ) ?? ( $tokens[1] == 'cm' ? $tokens[6] : $tokens[5] ); + $prop.='ro.build.version.sdk='. $this->apiLevel . "\n"; + if ($this->getBuildPropValue( 'ro.lineage.device' )){ + $this->model = $this->getBuildPropValue( 'ro.lineage.device' ); + $prop.='ro.lineage.device='.$this->model."\n"; + } + else if( $this->getBuildPropValue( 'ro.cm.device' )) { + $this->model = $this->getBuildPropValue( 'ro.cm.device' ); + $prop.='ro.cm.device='.$this->model."\n"; + } + else if ( $tokens[1] == 'cm') { + $this->model = $tokens[6]; + } else { + $this->model = $tokens[5]; + } $this->version = $tokens[2]; - $this->displayVersion = $this->getBuildPropValue( 'ro.cm.display.version' ) ?? $this->getBuildPropValue( 'ro.lineage.display.version' ) ?? ''; + if( $this->getBuildPropValue( 'ro.cm.display.version' )){ + $this->displayVersion = $this->getBuildPropValue( 'ro.cm.display.version' ); + $prop.='ro.cm.display.version='.$this->displayVersion."\n"; + } + else if( $this->getBuildPropValue( 'ro.lineage.display.version' )){ + $this->displayVersion = $this->getBuildPropValue( 'ro.lineage.display.version' ); + $prop.='ro.lineage.display.version='.$this->displayVersion."\n"; + } else { + $this->displayVersion= ''; + } $this->androidVersion = $this->getBuildPropValue( 'ro.build.version.release' ) ?? ''; + $prop.='ro.build.version.release='.$this->androidVersion."\n"; + if(!file_exists($this->filePath.'.prop')) { + file_put_contents($this->filePath . '.prop', $prop); + } $this->uid = hash( 'sha256', $this->timestamp.$this->model.$this->apiLevel, false ); $this->size = filesize($this->filePath); - $position = strrpos( $physicalPath, '/builds/full' ); if ( $position === FALSE ) $this->url = $this->_getUrl( '', Flight::cfg()->get('buildsPath') ); @@ -106,6 +134,8 @@ * @return boolean True if valid, False if not. */ public function isValid($params){ + if ( $params === NULL ) return true; // Assume valid if no parameters + $ret = false; if ( $params['device'] == $this->model ) { @@ -276,6 +306,7 @@ $this->filePath.'.prop' : 'zip://'.$this->filePath.'#system/build.prop'; } + /* Utility / Internal */ /** * Remove trailing dashes diff --git a/src/Helpers/Builds.php b/src/Helpers/Builds.php index 4220d4cd001eabf8b8cad20fa7a72c5aac1b1ffc..5523feebce53fd60fe4e66573a49a75b17bc9c37 100644 --- a/src/Helpers/Builds.php +++ b/src/Helpers/Builds.php @@ -2,7 +2,7 @@ /* The MIT License (MIT) - Copyright (c) 2016 Julian Xhokaxhiu + Copyright (c) 2020 Julian Xhokaxhiu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -29,18 +29,20 @@ class Builds { - // This will contain the build list based on the current request + // This will contain the build list based on the current request private $builds = array(); - private $postData = array(); - private $logger = null; - private $currentBuild = null; - - /** - * Constructor of the Builds class. - */ - public function __construct($logger) { - $this->logger = $logger; + private $postData = array(); + + private $logger = null; + + private $currentBuild = null; + + /** + * Constructor of the Builds class. + */ + public function __construct($logger) { + $this->logger = $logger; // Set required paths for properly builds Urls later Flight::cfg()->set( 'buildsPath', Flight::cfg()->get('basePath') . '/builds/full' ); Flight::cfg()->set( 'deltasPath', Flight::cfg()->get('basePath') . '/builds/delta' ); @@ -52,15 +54,14 @@ $this->getBuilds(); } - /** - * Return a valid response list of builds available based on the current request - * @return array An array preformatted with builds - */ + /** + * Return a valid response list of builds available based on the current request + * @return array An array preformatted with builds + */ public function get() { $ret = array(); foreach ( $this->builds as $build ) { - if (!is_null($this->currentBuild) && strcmp($this->currentBuild->getAndroidVersion(), $build->getAndroidVersion()) != 0) { $this->logger->info($build->getIncremental().' ignored as Android version is not the same'); continue; @@ -96,21 +97,21 @@ return $ret; } - /** - * Set a custom set of POST data. Useful to hack the flow in case the data doesn't come within the body of the HTTP request - * @param array An array structured as POST data - * @return void - */ - public function setPostData( $customData ){ - $this->postData = $customData; - $this->builds = array(); - $this->getBuilds(); - } - - /** - * Return a valid response of the delta build (if available) based on the current request - * @return array An array preformatted with the delta build - */ + /** + * Set a custom set of POST data. Useful to hack the flow in case the data doesn't come within the body of the HTTP request + * @param array An array structured as POST data + * @return void + */ + public function setPostData( $customData ){ + $this->postData = $customData; + $this->builds = array(); + $this->getBuilds(); + } + + /** + * Return a valid response of the delta build (if available) based on the current request + * @return array An array preformatted with the delta build + */ public function getDelta() { $ret = false; @@ -138,7 +139,7 @@ return $ret; } - /* Utility / Internal */ + /* Utility / Internal */ private function getBuilds() { // Get physical paths of where the files resides @@ -154,7 +155,7 @@ array_push( $dirs, $path ); foreach ( $dirs as $dir ) { // Get the file list and parse it - $files = preg_grep( '/^([^.Thumbs])/', scandir( $dir ) ); + $files = scandir( $dir ); if ( count( $files ) > 0 ) { foreach ( $files as $file ) { @@ -179,27 +180,23 @@ // If not found there, we have to find it with the old fashion method... if ( $build === FALSE ) { - $build = new Build( $file, $dir, $this->logger); + $build = new Build( $file, $dir, $this->logger ); // ...and then save it for 72h until it expires again apcu_store( $file, $build, 72*60*60 ); } } else - $build = new Build( $file, $dir, $this->logger); + $build = new Build( $file, $dir, $this->logger ); if ( $build->isValid( $this->postData['params'] ) ) { array_push( $this->builds , $build ); - if (strcmp($this->postData['params']['source_incremental'], $build->getIncremental()) == 0) { - $this->currentBuild = $build; - $this->logger->info($build->getIncremental().' is the current build'); - } + $this->currentBuild = $build; + $this->logger->info($build->getIncremental().' is the current build'); + } } } } } } - - if (!empty($this->postData['params']['source_incremental']) && is_null($this->currentBuild)) - $this->currentBuild = $this->builds[1]; } }