Skip to content

Commit

Permalink
Fix various issues on shared hosting (#7059)
Browse files Browse the repository at this point in the history
# Description & Issue number it closes 
<!-- Please include a summary of the changes and the related issue.
Please also include relevant motivation and context. -->

- Fix mod_rewrite detection on shared hosting
- Fix formatting of `apache_get_modules` notice
- Make said notice easier to translate
- Add more gettext fields
- Clean up
- Attempt to exclude `.htaccess` from change validation (it frequently
needs to be changed on shared hosting)

Resolves #7056

## Screenshots (if appropriate)
<!-- Before and after --> 

## How to test the changes?

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- [ ] This change requires a documentation update

# How Has This Been Tested?

<!-- Please describe the tests that you ran to verify your changes.
Provide instructions so we can reproduce. Please also list any relevant
details for your test configuration -->

# Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
  • Loading branch information
DAcodedBEAT committed Jun 12, 2024
2 parents 17e502a + a6194d9 commit 025dbc9
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 137 deletions.
2 changes: 1 addition & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ module.exports = function (grunt) {
src: [
"**/*.php",
"**/*.js",
"**/.htaccess",
"!**/.htaccess",
"!**/.gitignore",
"!vendor/**/example/**",
"!vendor/**/tests/**",
Expand Down
155 changes: 67 additions & 88 deletions src/ChurchCRM/Service/AppIntegrityService.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,40 +14,46 @@ class AppIntegrityService
private static function getIntegrityCheckData()
{
$logger = LoggerUtils::getAppLogger();

$integrityCheckFile = SystemURLs::getDocumentRoot() . '/integrityCheck.json';
if (AppIntegrityService::$IntegrityCheckDetails === null) {
$logger->debug('Integrity check results not cached; reloading from file');
if (is_file($integrityCheckFile)) {
$logger->debug("Integrity check result file found at: {integrityCheckFile}", [
'integrityCheckFile' => $integrityCheckFile,
]);
if (AppIntegrityService::$IntegrityCheckDetails !== null) {
$logger->debug('Integrity check results already cached; not reloading from file');

try {
$integrityCheckFileContents = file_get_contents($integrityCheckFile);
MiscUtils::throwIfFailed($integrityCheckFileContents);
AppIntegrityService::$IntegrityCheckDetails = json_decode($integrityCheckFileContents, null, 512, JSON_THROW_ON_ERROR);
} catch (\Exception $e) {
$logger->warning("Error decoding integrity check result file: {integrityCheckFile}", [
'integrityCheckFile' => $integrityCheckFile,
'exception' => $e,
]);
AppIntegrityService::$IntegrityCheckDetails = new \stdClass();
AppIntegrityService::$IntegrityCheckDetails->status = 'failure';
AppIntegrityService::$IntegrityCheckDetails->message = gettext('Error decoding integrity check result file');
}
} else {
$logger->debug("Integrity check result file not found at: {integrityCheckFile}", [
return AppIntegrityService::$IntegrityCheckDetails;
}

$logger->debug('Integrity check results not cached; reloading from file');

if (is_file($integrityCheckFile)) {
$logger->debug("Integrity check result file found at: {integrityCheckFile}", [
'integrityCheckFile' => $integrityCheckFile,
]);

try {
$integrityCheckFileContents = file_get_contents($integrityCheckFile);
MiscUtils::throwIfFailed($integrityCheckFileContents);
AppIntegrityService::$IntegrityCheckDetails = json_decode($integrityCheckFileContents, null, 512, JSON_THROW_ON_ERROR);
} catch (\Exception $e) {
$logger->warning("Error decoding integrity check result file: {integrityCheckFile}", [
'integrityCheckFile' => $integrityCheckFile,
'exception' => $e,
]);

AppIntegrityService::$IntegrityCheckDetails = new \stdClass();
AppIntegrityService::$IntegrityCheckDetails->status = 'failure';
AppIntegrityService::$IntegrityCheckDetails->message = gettext('integrityCheck.json file missing');
AppIntegrityService::$IntegrityCheckDetails->message = gettext('Error decoding integrity check result file');
}
} else {
$logger->debug('Integrity check results already cached; not reloading from file');

return AppIntegrityService::$IntegrityCheckDetails;
}

$logger->debug("Integrity check result file not found at: {integrityCheckFile}", [
'integrityCheckFile' => $integrityCheckFile,
]);

AppIntegrityService::$IntegrityCheckDetails = new \stdClass();
AppIntegrityService::$IntegrityCheckDetails->status = 'failure';
AppIntegrityService::$IntegrityCheckDetails->message = gettext('integrityCheck.json file missing');

return AppIntegrityService::$IntegrityCheckDetails;
}

Expand Down Expand Up @@ -77,7 +83,6 @@ public static function getFilesFailingIntegrityCheck()
public static function verifyApplicationIntegrity(): array
{
$logger = LoggerUtils::getAppLogger();

$signatureFile = SystemURLs::getDocumentRoot() . '/signatures.json';
$signatureFailures = [];
if (is_file($signatureFile)) {
Expand Down Expand Up @@ -153,11 +158,11 @@ public static function verifyApplicationIntegrity(): array
'message' => gettext('One or more files failed signature validation'),
'files' => $signatureFailures,
];
} else {
return [
'status' => 'success',
];
}

return [
'status' => 'success',
];
}

private static function verifyDirectoryWriteable(string $path): bool
Expand Down Expand Up @@ -223,70 +228,44 @@ public static function arePrerequisitesMet(): bool

public static function hasModRewrite(): bool
{
// mod_rewrite can be tricky to detect properly.
// First check if it's loaded as an apache module
// Second check (if supported) if apache cli lists the module
// Third, finally try calling a known invalid URL on this installation
// and check for a header that would only be present if .htaccess was processed.
// This header comes from index.php (which is the target of .htaccess for invalid URLs)

$check = false;
$logger = LoggerUtils::getAppLogger();

if (isset($_SERVER['HTTP_MOD_REWRITE'])) {
$logger->info("Webserver configuration has set mod_rewrite variable: {$_SERVER['HTTP_MOD_REWRITE']}");
$check = strtolower($_SERVER['HTTP_MOD_REWRITE']) === 'on';
} elseif (stristr($_SERVER['SERVER_SOFTWARE'], 'apache') !== false) {
$logger->debug('PHP is running through Apache; looking for mod_rewrite');
if (function_exists('apache_get_modules')) {
$check = in_array('mod_rewrite', apache_get_modules());
}
$logger->debug("Apache mod_rewrite check status: $check");
if (empty($check)) {
try {
if (!empty(shell_exec('/usr/sbin/apachectl -M | grep rewrite'))) {
$logger->debug('Found rewrite module enabled using apachectl');
$check = true;
}
} catch (\Throwable) {
// do nothing
}
}
} else {
$logger->debug('PHP is not running through Apache');
}
if (function_exists('curl_version')) {
$ch = curl_init();

if ($check === false) {
$logger->debug('Previous rewrite checks failed');
if (function_exists('curl_version')) {
$ch = curl_init();
$request_url_parser = parse_url($_SERVER['HTTP_REFERER']);
$request_scheme = $request_url_parser['scheme'] ?? 'http';
$rewrite_chk_url = $request_scheme . '://' . $_SERVER['SERVER_ADDR'] . SystemURLs::getRootPath() . '/INVALID';
$logger->debug("Testing CURL loopback check to: $rewrite_chk_url");
curl_setopt($ch, CURLOPT_URL, $rewrite_chk_url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_NOBODY, 1);
$output = curl_exec($ch);
curl_close($ch);
$headers = [];
$data = explode("\n", $output);
$headers['status'] = $data[0];
array_shift($data);
foreach ($data as $part) {
if (strpos($part, ':')) {
$middle = explode(':', $part);
$headers[trim($middle[0])] = trim($middle[1]);
}
$request_url_parser = parse_url($_SERVER['HTTP_REFERER']);
$request_scheme = $request_url_parser['scheme'] ?? 'http';
$request_host = $request_url_parser['host'] ?? $_SERVER['HTTP_HOST'];
// Run a test against an URL we know does not exist to check for ModRewrite like functionality
$rewrite_chk_url = $request_scheme . '://' . $request_host . SystemURLs::getRootPath() . '/INVALID';
$logger->debug("Testing CURL loopback check to: $rewrite_chk_url");

curl_setopt($ch, CURLOPT_URL, $rewrite_chk_url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_NOBODY, true);

$result = curl_exec($ch);
curl_close($ch);

$header = [];
$headers = substr($result, 0, strpos($result, "\r\n\r\n"));

foreach (explode("\r\n", $headers) as $lineKey => $line) {
if ($lineKey === 0) {
$header['status'] = $line;
}
$check = $headers['CRM'] === 'would redirect';
$logger->debug('CURL loopback check headers observed: ' . ($check ? 'true' : 'false'));

list($key, $value) = explode(': ', $line);
$header[$key] = $value;
}

$logger->debug('CURL loopback check header observed: ' . (array_key_exists('crm', $header) ? 'true' : 'false'));
return array_key_exists('crm', $header) && $header['crm'] === 'would redirect';
}

return $check;
return false;
}
}
Loading

0 comments on commit 025dbc9

Please sign in to comment.