Updates to more strict type checking after some static analysis

This commit is contained in:
Alex Cabal 2019-01-17 21:26:48 -06:00
parent 152f86d616
commit c879dcab34
10 changed files with 50 additions and 34 deletions

View file

@ -38,7 +38,7 @@ set_exception_handler(function($ex){
$errorString = "----------------------------------------\n"; $errorString = "----------------------------------------\n";
$errorString .= trim(vds(array_intersect_key($_SERVER, array('REQUEST_URI' => '', 'QUERY_STRING' => '', 'REQUEST_METHOD' => '', 'REDIRECT_QUERY_STRING' => '', 'REDIRECT_URL' => '', 'SCRIPT_FILENAME' => '', 'REMOTE_ADDR' => '', 'HTTP_COOKIE' => '', 'HTTP_USER_AGENT' => '', 'SCRIPT_URI' => '')))); $errorString .= trim(vds(array_intersect_key($_SERVER, array('REQUEST_URI' => '', 'QUERY_STRING' => '', 'REQUEST_METHOD' => '', 'REDIRECT_QUERY_STRING' => '', 'REDIRECT_URL' => '', 'SCRIPT_FILENAME' => '', 'REMOTE_ADDR' => '', 'HTTP_COOKIE' => '', 'HTTP_USER_AGENT' => '', 'SCRIPT_URI' => ''))));
if(isset($_POST) && sizeof($_POST) > 0){ if(sizeof($_POST) > 0){
$errorString .= "POST DATA:\n"; $errorString .= "POST DATA:\n";
$errorString .= vds($_POST); $errorString .= vds($_POST);
} }

View file

@ -41,7 +41,7 @@ class Ebook{
public $TitleWithCreditsHtml = ''; public $TitleWithCreditsHtml = '';
public $Timestamp; public $Timestamp;
public function __construct($wwwFilesystemPath){ public function __construct(string $wwwFilesystemPath){
// First, construct a source repo path from our WWW filesystem path. // First, construct a source repo path from our WWW filesystem path.
$this->RepoFilesystemPath = str_replace(SITE_ROOT . '/www/ebooks/', '', $wwwFilesystemPath); $this->RepoFilesystemPath = str_replace(SITE_ROOT . '/www/ebooks/', '', $wwwFilesystemPath);
$this->RepoFilesystemPath = SITE_ROOT . '/ebooks/' . str_replace('/', '_', $this->RepoFilesystemPath) . '.git'; $this->RepoFilesystemPath = SITE_ROOT . '/ebooks/' . str_replace('/', '_', $this->RepoFilesystemPath) . '.git';
@ -65,14 +65,14 @@ class Ebook{
$this->WwwFilesystemPath = $wwwFilesystemPath; $this->WwwFilesystemPath = $wwwFilesystemPath;
$this->Url = str_replace(SITE_ROOT . '/www', '', $this->WwwFilesystemPath); $this->Url = str_replace(SITE_ROOT . '/www', '', $this->WwwFilesystemPath);
$rawMetadata = file_get_contents($wwwFilesystemPath . '/src/epub/content.opf'); $rawMetadata = file_get_contents($wwwFilesystemPath . '/src/epub/content.opf') ?: '';
// Get the SE identifier. // Get the SE identifier.
preg_match('|<dc:identifier[^>]*?>(.+?)</dc:identifier>|ius', $rawMetadata, $matches); preg_match('|<dc:identifier[^>]*?>(.+?)</dc:identifier>|ius', $rawMetadata, $matches);
if(sizeof($matches) != 2){ if(sizeof($matches) != 2){
throw new EbookParsingException('Invalid <dc:identifier> element.'); throw new EbookParsingException('Invalid <dc:identifier> element.');
} }
$this->Identifier = $matches[1]; $this->Identifier = (string)$matches[1];
$this->UrlSafeIdentifier = str_replace(['url:https://standardebooks.org/ebooks/', '/'], ['', '_'], $this->Identifier); $this->UrlSafeIdentifier = str_replace(['url:https://standardebooks.org/ebooks/', '/'], ['', '_'], $this->Identifier);
@ -114,7 +114,7 @@ class Ebook{
} }
// Fill in the short history of this repo. // Fill in the short history of this repo.
$historyEntries = explode("\n", shell_exec('cd ' . escapeshellarg($this->RepoFilesystemPath) . ' && git log -n5 --pretty=format:"%ct %s"')); $historyEntries = explode("\n", shell_exec('cd ' . escapeshellarg($this->RepoFilesystemPath) . ' && git log -n5 --pretty=format:"%ct %s"') ?? '');
foreach($historyEntries as $entry){ foreach($historyEntries as $entry){
$array = explode(' ', $entry, 2); $array = explode(' ', $entry, 2);
@ -133,7 +133,7 @@ class Ebook{
$this->HeroImage2xUrl = '/images/covers/' . $this->UrlSafeIdentifier . '-' . $hash . '-hero@2x.jpg'; $this->HeroImage2xUrl = '/images/covers/' . $this->UrlSafeIdentifier . '-' . $hash . '-hero@2x.jpg';
// Now do some heavy XML lifting! // Now do some heavy XML lifting!
$xml = new SimpleXmlElement(str_replace('xmlns=', 'ns=', $rawMetadata)); $xml = new SimpleXMLElement(str_replace('xmlns=', 'ns=', $rawMetadata));
$xml->registerXPathNamespace('dc', 'http://purl.org/dc/elements/1.1/'); $xml->registerXPathNamespace('dc', 'http://purl.org/dc/elements/1.1/');
$this->Title = $this->NullIfEmpty($xml->xpath('/package/metadata/dc:title')); $this->Title = $this->NullIfEmpty($xml->xpath('/package/metadata/dc:title'));
@ -148,17 +148,17 @@ class Ebook{
$this->Timestamp = new \DateTime((string)$xml->xpath('/package/metadata/dc:date')[0]); $this->Timestamp = new \DateTime((string)$xml->xpath('/package/metadata/dc:date')[0]);
// Get SE tags // Get SE tags
foreach($xml->xpath('/package/metadata/meta[@property="meta-auth"]') as $tag){ foreach($xml->xpath('/package/metadata/meta[@property="meta-auth"]') ?: [] as $tag){
$this->Tags[] = (string)$tag; $this->Tags[] = (string)$tag;
} }
// Get LoC tags // Get LoC tags
foreach($xml->xpath('/package/metadata/dc:subject') as $tag){ foreach($xml->xpath('/package/metadata/dc:subject') ?: [] as $tag){
$this->LocTags[] = (string)$tag; $this->LocTags[] = (string)$tag;
} }
// Figure out authors and contributors. // Figure out authors and contributors.
foreach($xml->xpath('/package/metadata/dc:creator') as $author){ foreach($xml->xpath('/package/metadata/dc:creator') ?: [] as $author){
$id = $author->attributes()->id; $id = $author->attributes()->id;
$this->Authors[] = new Contributor( (string)$author, $this->Authors[] = new Contributor( (string)$author,
(string)$xml->xpath('/package/metadata/meta[@property="file-as"][@refines="#' . $id . '"]')[0], (string)$xml->xpath('/package/metadata/meta[@property="file-as"][@refines="#' . $id . '"]')[0],
@ -173,9 +173,9 @@ class Ebook{
$this->AuthorsUrl = preg_replace('|url:https://standardebooks.org/ebooks/([^/]+)/.*|ius', '/ebooks/\1/', $this->Identifier); $this->AuthorsUrl = preg_replace('|url:https://standardebooks.org/ebooks/([^/]+)/.*|ius', '/ebooks/\1/', $this->Identifier);
foreach($xml->xpath('/package/metadata/dc:contributor') as $contributor){ foreach($xml->xpath('/package/metadata/dc:contributor') ?: [] as $contributor){
$id = $contributor->attributes()->id; $id = $contributor->attributes()->id;
foreach($xml->xpath('/package/metadata/meta[@property="role"][@refines="#' . $id . '"]') as $role){ foreach($xml->xpath('/package/metadata/meta[@property="role"][@refines="#' . $id . '"]') ?: [] as $role){
$c = new Contributor( $c = new Contributor(
(string)$contributor, (string)$contributor,
$this->NullIfEmpty($xml->xpath('/package/metadata/meta[@property="file-as"][@refines="#' . $id . '"]')), $this->NullIfEmpty($xml->xpath('/package/metadata/meta[@property="file-as"][@refines="#' . $id . '"]')),
@ -266,7 +266,7 @@ class Ebook{
$this->WikipediaUrl = $this->NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:url.encyclopedia.wikipedia"][not(@refines)]')); $this->WikipediaUrl = $this->NullIfEmpty($xml->xpath('/package/metadata/meta[@property="se:url.encyclopedia.wikipedia"][not(@refines)]'));
// Next the page scan source URLs. // Next the page scan source URLs.
foreach($xml->xpath('/package/metadata/dc:source') as $element){ foreach($xml->xpath('/package/metadata/dc:source') ?: [] as $element){
if(mb_stripos((string)$element, '//www.gutenberg.org/') !== false){ if(mb_stripos((string)$element, '//www.gutenberg.org/') !== false){
$this->SourceUrls[] = ['source' => SOURCE_PROJECT_GUTENBERG, 'url' => (string)$element]; $this->SourceUrls[] = ['source' => SOURCE_PROJECT_GUTENBERG, 'url' => (string)$element];
} }
@ -335,8 +335,8 @@ class Ebook{
} }
// Remove diacritics and non-alphanumeric characters // Remove diacritics and non-alphanumeric characters
$searchString = trim(preg_replace('|[^a-zA-Z0-9 ]|ius', ' ', @iconv('UTF-8', 'ASCII//TRANSLIT', $searchString))); $searchString = trim(preg_replace('|[^a-zA-Z0-9 ]|ius', ' ', iconv('UTF-8', 'ASCII//TRANSLIT', $searchString) ?: '') ?? '');
$query = trim(preg_replace('|[^a-zA-Z0-9 ]|ius', ' ', @iconv('UTF-8', 'ASCII//TRANSLIT', $query))); $query = trim(preg_replace('|[^a-zA-Z0-9 ]|ius', ' ', iconv('UTF-8', 'ASCII//TRANSLIT', $query) ?: '') ?? '');
if($query == ''){ if($query == ''){
return false; return false;
@ -428,7 +428,7 @@ class Ebook{
} }
} }
return json_encode($output, JSON_PRETTY_PRINT); return json_encode($output, JSON_PRETTY_PRINT) ?: '';
} }
private function GenerateContributorJsonLd(Contributor $contributor): stdClass{ private function GenerateContributorJsonLd(Contributor $contributor): stdClass{
@ -476,7 +476,11 @@ class Ebook{
return $string; return $string;
} }
private function NullIfEmpty(array $elements){ // Can't use type hinting until PHP 7.1 which supports nullable return types private function NullIfEmpty($elements): ?string{
if($elements === false){
return null;
}
// Helper function when getting values from SimpleXml. // Helper function when getting values from SimpleXml.
// Checks if the result is set, and returns the value if so; if the value is the empty string, return null. // Checks if the result is set, and returns the value if so; if the value is the empty string, return null.
if(isset($elements[0])){ if(isset($elements[0])){

View file

@ -2,19 +2,19 @@
class Formatter{ class Formatter{
public static function MakeUrlSafe(string $text): string{ public static function MakeUrlSafe(string $text): string{
// Remove accent characters // Remove accent characters
$text = @iconv('UTF-8', 'ASCII//TRANSLIT', $text); $text = iconv('UTF-8', 'ASCII//TRANSLIT', $text) ?: '';
// Trim and convert to lowercase // Trim and convert to lowercase
$text = mb_strtolower(trim($text)); $text = mb_strtolower(trim($text));
// Remove apostrophes // Remove apostrophes
$text = preg_replace("/[']/ius", '', $text); $text = preg_replace("/[']/ius", '', $text) ?: '';
// Then convert any non-digit, non-letter character to a space // Then convert any non-digit, non-letter character to a space
$text = preg_replace('/[^0-9a-zA-Z]/ius', ' ', $text); $text = preg_replace('/[^0-9a-zA-Z]/ius', ' ', $text) ?: '';
// Then convert any instance of one or more space to dash // Then convert any instance of one or more space to dash
$text = preg_replace('/\s+/ius', '-', $text); $text = preg_replace('/\s+/ius', '-', $text) ?: '';
// Finally, trim dashes // Finally, trim dashes
$text = trim($text, '-'); $text = trim($text, '-');

View file

@ -1,6 +1,6 @@
<? <?
class HttpInput{ class HttpInput{
public static function GetString(string $variable, bool $allowEmptyString = true, $default = null){ // Can't type hint return values because it might return null public static function GetString(string $variable, bool $allowEmptyString = true, $default = null): ?string{
$var = self::GetHttpVar($variable, HTTP_VAR_STR, GET, $default); $var = self::GetHttpVar($variable, HTTP_VAR_STR, GET, $default);
if(!$allowEmptyString && $var === ''){ if(!$allowEmptyString && $var === ''){
@ -10,15 +10,15 @@ class HttpInput{
return $var; return $var;
} }
public static function GetInt(string $variable, $default = null){ // Can't type hint return values because it might return null public static function GetInt(string $variable, $default = null): ?int{
return self::GetHttpVar($variable, HTTP_VAR_INT, GET, $default); return self::GetHttpVar($variable, HTTP_VAR_INT, GET, $default);
} }
public static function GetBool(string $variable, $default = null){ // Can't type hint return values because it might return null public static function GetBool(string $variable, $default = null): ?bool{
return self::GetHttpVar($variable, HTTP_VAR_BOOL, GET, $default); return self::GetHttpVar($variable, HTTP_VAR_BOOL, GET, $default);
} }
public static function GetDec(string $variable, $default = null){ // Can't type hint return values because it might return null public static function GetDec(string $variable, $default = null): ?float{
return self::GetHttpVar($variable, HTTP_VAR_DEC, GET, $default); return self::GetHttpVar($variable, HTTP_VAR_DEC, GET, $default);
} }

View file

@ -49,7 +49,7 @@ class Library{
$ebooks = apcu_fetch('ebooks', $success); $ebooks = apcu_fetch('ebooks', $success);
if(!$success){ if(!$success){
foreach(explode("\n", trim(shell_exec('find ' . SITE_ROOT . '/www/ebooks/ -name "content.opf"'))) as $filename){ foreach(explode("\n", trim(shell_exec('find ' . SITE_ROOT . '/www/ebooks/ -name "content.opf"') ?? '')) as $filename){
$ebookWwwFilesystemPath = preg_replace('|/src/.+|ius', '', $filename); $ebookWwwFilesystemPath = preg_replace('|/src/.+|ius', '', $filename);
$ebook = apcu_fetch('ebook-' . $ebookWwwFilesystemPath, $success); $ebook = apcu_fetch('ebook-' . $ebookWwwFilesystemPath, $success);
@ -76,7 +76,7 @@ class Library{
if(!$success){ if(!$success){
$ebooks = []; $ebooks = [];
foreach(explode("\n", trim(shell_exec('find ' . escapeshellarg($wwwFilesystemPath) . ' -name "content.opf"'))) as $filename){ foreach(explode("\n", trim(shell_exec('find ' . escapeshellarg($wwwFilesystemPath) . ' -name "content.opf"') ?? '')) as $filename){
try{ try{
$ebookWwwFilesystemPath = preg_replace('|/src/.+|ius', '', $filename); $ebookWwwFilesystemPath = preg_replace('|/src/.+|ius', '', $filename);
$ebook = apcu_fetch('ebook-' . $ebookWwwFilesystemPath, $success); $ebook = apcu_fetch('ebook-' . $ebookWwwFilesystemPath, $success);

View file

@ -2,6 +2,12 @@
class Logger{ class Logger{
public static function WriteGithubWebhookLogEntry(string $requestId, string $text){ public static function WriteGithubWebhookLogEntry(string $requestId, string $text){
$fp = fopen(GITHUB_WEBHOOK_LOG_FILE_PATH, 'a+'); $fp = fopen(GITHUB_WEBHOOK_LOG_FILE_PATH, 'a+');
if($fp === false){
self::WriteErrorLogEntry('Could not open log file: ' . GITHUB_WEBHOOK_LOG_FILE_PATH);
return;
}
fwrite($fp, gmdate('Y-m-d H:i:s') . "\t" . $requestId . "\t" . $text . "\n"); fwrite($fp, gmdate('Y-m-d H:i:s') . "\t" . $requestId . "\t" . $text . "\n");
fclose($fp); fclose($fp);
} }

View file

@ -19,7 +19,7 @@ class Template{
ob_start(); ob_start();
eval(' ?>' . $fileContents . '<? '); eval(' ?>' . $fileContents . '<? ');
$contents = ob_get_contents(); $contents = ob_get_contents() ?: '';
ob_end_clean(); ob_end_clean();
return $contents; return $contents;

View file

@ -1,3 +1,9 @@
<?
if(!isset($ebooks)){
$ebooks = [];
}
?>
<ol> <ol>
<? foreach($ebooks as $ebook){ ?> <? foreach($ebooks as $ebook){ ?>
<li> <li>

View file

@ -153,7 +153,7 @@ catch(\Exception $ex){
<? if($source['source'] == SOURCE_INTERNET_ARCHIVE){ ?><a href="<?= Formatter::ToPlainText($source['url']) ?>" class="internet-archive">Page scans at the Internet Archive</a><? } ?> <? if($source['source'] == SOURCE_INTERNET_ARCHIVE){ ?><a href="<?= Formatter::ToPlainText($source['url']) ?>" class="internet-archive">Page scans at the Internet Archive</a><? } ?>
<? if($source['source'] == SOURCE_HATHI_TRUST){ ?><a href="<?= Formatter::ToPlainText($source['url']) ?>" class="hathitrust">Page scans at HathiTrust</a><? } ?> <? if($source['source'] == SOURCE_HATHI_TRUST){ ?><a href="<?= Formatter::ToPlainText($source['url']) ?>" class="hathitrust">Page scans at HathiTrust</a><? } ?>
<? if($source['source'] == SOURCE_GOOGLE_BOOKS){ ?><a href="<?= Formatter::ToPlainText($source['url']) ?>" class="google">Page scans at Google Books</a><? } ?> <? if($source['source'] == SOURCE_GOOGLE_BOOKS){ ?><a href="<?= Formatter::ToPlainText($source['url']) ?>" class="google">Page scans at Google Books</a><? } ?>
<? if($source['source'] == SOURCE_OTHER){ ?><a href="<?= Formatter::ToPlainText($source['url']) ?>" class="globe"><?= Formatter::ToPlainText(preg_replace(['|https?://(en\.)?|', '|/.+$|'], '', $source['url'])) ?></a><? } ?> <? if($source['source'] == SOURCE_OTHER){ ?><a href="<?= Formatter::ToPlainText($source['url']) ?>" class="globe"><?= Formatter::ToPlainText(preg_replace(['|https?://(en\.)?|', '|/.+$|'], '', $source['url']) ?? '') ?></a><? } ?>
</p> </p>
</li> </li>
<? } ?> <? } ?>

View file

@ -1,24 +1,24 @@
<? <?
require_once('Core.php'); require_once('Core.php');
try{
// Get a semi-random ID to identify this request within the log. // Get a semi-random ID to identify this request within the log.
$requestId = substr(sha1(time() . rand()), 0, 8); $requestId = substr(sha1(time() . rand()), 0, 8);
try{
Logger::WriteGithubWebhookLogEntry($requestId, 'Received GitHub webhook.'); Logger::WriteGithubWebhookLogEntry($requestId, 'Received GitHub webhook.');
if($_SERVER['REQUEST_METHOD'] != 'POST'){ if($_SERVER['REQUEST_METHOD'] != 'POST'){
throw new WebhookException('Expected HTTP POST.'); throw new WebhookException('Expected HTTP POST.');
} }
$post = file_get_contents('php://input'); $post = file_get_contents('php://input') ?: '';
// Validate the GitHub secret. // Validate the GitHub secret.
$splitHash = explode('=', $_SERVER['HTTP_X_HUB_SIGNATURE']); $splitHash = explode('=', $_SERVER['HTTP_X_HUB_SIGNATURE']);
$hashAlgorithm = $splitHash[0]; $hashAlgorithm = $splitHash[0];
$hash = $splitHash[1]; $hash = $splitHash[1];
if(!hash_equals($hash, hash_hmac($hashAlgorithm, $post, preg_replace("/[\r\n]/ius", '', file_get_contents(GITHUB_SECRET_FILE_PATH))))){ if(!hash_equals($hash, hash_hmac($hashAlgorithm, $post, preg_replace("/[\r\n]/ius", '', file_get_contents(GITHUB_SECRET_FILE_PATH) ?: '')))){
throw new WebhookException('Invalid GitHub webhook secret.', $post); throw new WebhookException('Invalid GitHub webhook secret.', $post);
} }
@ -65,7 +65,7 @@ try{
// Check the local repo's last commit. If it matches this push, then don't do anything; we're already up to date. // Check the local repo's last commit. If it matches this push, then don't do anything; we're already up to date.
$lastCommitSha1 = trim(shell_exec('git -C ' . escapeshellarg($dir) . ' rev-parse HEAD 2>&1; echo $?')); $lastCommitSha1 = trim(shell_exec('git -C ' . escapeshellarg($dir) . ' rev-parse HEAD 2>&1; echo $?'));
if($lastCommitSha1 === null){ if($lastCommitSha1 == ''){
Logger::WriteGithubWebhookLogEntry($requestId, 'Error getting last local commit. Output: ' . $lastCommitSha1); Logger::WriteGithubWebhookLogEntry($requestId, 'Error getting last local commit. Output: ' . $lastCommitSha1);
throw new WebhookException('Couldn\'t process ebook.', $post); throw new WebhookException('Couldn\'t process ebook.', $post);
} }