Skip to content

Commit

Permalink
try to make sure we have set content type header
Browse files Browse the repository at this point in the history
when produceErrorResponse is called before we have init'd our parameters
  • Loading branch information
JohnRDOrazio committed Jun 22, 2024
1 parent 9975e13 commit 6066c24
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 85 deletions.
185 changes: 100 additions & 85 deletions src/AnniversaryCalculator.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
namespace LitCal;

use LitCal\AnniversaryCalculator\LitEvent;
use LitCal\AnniversaryCalculator\Enums\StatusCode;

class AnniversaryCalculator
{
public const ALLOWED_RETURN_TYPES = [ "json", "yaml", "xml", "html" ];
public const ALLOWED_ACCEPT_HEADERS = [ "application/json", "application/yaml", "application/xml", "text/html" ];
public const ALLOWED_CONTENT_TYPES = [ "application/json", "application/yaml", "application/x-www-form-urlencoded" ];
public const ALLOWED_REQUEST_METHODS = [ "GET", "POST" ];
public const ALLOWED_REQUEST_CONTENT_TYPES = [ "application/json", "application/yaml", "application/x-www-form-urlencoded" ];
public const ALLOWED_REQUEST_METHODS = [ "GET", "POST", "OPTIONS" ];
public const ALLOWED_LOCALES = [ "en", "it" ]; //, "es", "fr", "de", "pt"

public const RECURRING = [
Expand All @@ -27,19 +28,18 @@ class AnniversaryCalculator
"CENTENARY"
];

private string $responseContentType;
private string $acceptHeader = "";
//private string $table;
private array $parameterData = [];
private array $requestHeaders = [];
private static ?string $responseContentType = null;
private static ?string $acceptHeader = null;
private array $parameterData = [];
private array $requestHeaders = [];
private object $RESPONSE;
//private string|false $jsonEncodedRequestHeaders = "";

public function __construct()
{
$this->requestHeaders = getallheaders();
//$this->jsonEncodedRequestHeaders = json_encode( $this->requestHeaders );
$this->acceptHeader = isset($this->requestHeaders["Accept"]) && in_array($this->requestHeaders["Accept"], self::ALLOWED_ACCEPT_HEADERS) ? (string) $this->requestHeaders["Accept"] : "";
self::$acceptHeader = isset($this->requestHeaders["Accept"]) && in_array($this->requestHeaders["Accept"], self::ALLOWED_ACCEPT_HEADERS)
? (string) $this->requestHeaders["Accept"]
: "";
$this->RESPONSE = new \stdClass();
$this->RESPONSE->LitEvents = [];
$this->RESPONSE->Messages = [ "Anniversary Calculator instantiated" ];
Expand All @@ -53,9 +53,9 @@ public function init()

$this->initParameterData();
$this->prepareL10N();
$this->setReponseContentTypeHeader();
self::setReponseContentTypeHeader();
$this->readData();
$this->outputResults();
$this->produceResponse();
}

private static function allowFromAnyOrigin()
Expand All @@ -71,7 +71,7 @@ private static function setAccessControlAllowMethods()
{
if (isset($_SERVER['REQUEST_METHOD'])) {
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
header("Access-Control-Allow-Methods: GET, POST");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
}
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");
Expand All @@ -81,62 +81,47 @@ private static function setAccessControlAllowMethods()

private static function validateRequestContentType()
{
if (isset($_SERVER['CONTENT_TYPE']) && $_SERVER['CONTENT_TYPE'] !== '' && !in_array($_SERVER['CONTENT_TYPE'], self::ALLOWED_CONTENT_TYPES)) {
header($_SERVER["SERVER_PROTOCOL"] . " 415 Unsupported Media Type", true, 415);
die('{"error":"You seem to be forming a strange kind of request? Allowed Content Types are ' . implode(' and ', self::ALLOWED_CONTENT_TYPES) . ', but your Content Type was ' . $_SERVER['CONTENT_TYPE'] . '"}');
if (
isset($_SERVER['CONTENT_TYPE'])
&& $_SERVER['CONTENT_TYPE'] !== ''
&& !in_array($_SERVER['CONTENT_TYPE'], self::ALLOWED_REQUEST_CONTENT_TYPES)
) {
$message = "Allowed Content Types are: " . implode(', ', self::ALLOWED_REQUEST_CONTENT_TYPES) . "; but the Content Type of the request was " . $_SERVER['CONTENT_TYPE'];
self::produceErrorResponse(StatusCode::UNSUPPORTED_MEDIA_TYPE, $message);
}
}

private function initParameterData()
{
if (isset($_SERVER['CONTENT_TYPE']) && $_SERVER['CONTENT_TYPE'] === 'application/json') {
$rawJson = file_get_contents('php://input');
if (null === $rawJson || "" === $rawJson) {
header($_SERVER["SERVER_PROTOCOL"] . " 400 Bad Request", true, 400);
$response = new \stdClass();
$response->error = _("No JSON data received in the request");
die(json_encode($response));
if (false === $rawJson || "" === $rawJson) {
self::produceErrorResponse(StatusCode::BAD_REQUEST, "No JSON data received in the request");
}
$data = json_decode($rawJson, true);
if (json_last_error() !== JSON_ERROR_NONE) {
header($_SERVER["SERVER_PROTOCOL"] . " 400 Bad Request", true, 400);
$response = new \stdClass();
$response->error = sprintf(
_('Malformed JSON data received in the request: %s'),
json_last_error_msg()
);
die(json_encode($response));
$message = 'Malformed JSON data received in the request: ' . json_last_error_msg();
self::produceErrorResponse(StatusCode::BAD_REQUEST, $message);
} else {
$this->parameterData = $data;
}
} elseif (isset($_SERVER['CONTENT_TYPE']) && $_SERVER['CONTENT_TYPE'] === 'application/yaml') {
$rawYaml = file_get_contents('php://input');
if ("" === $rawYaml) {
header($_SERVER[ "SERVER_PROTOCOL" ] . " 400 Bad Request", true, 400);
$response = new \stdClass();
$response->error = _("No YAML data received in the request");
die(json_encode($response));
if (false === $rawYaml || "" === $rawYaml) {
self::produceErrorResponse(StatusCode::BAD_REQUEST, "No YAML data received in the request");
}

set_error_handler(array('self', 'warningHandler'), E_WARNING);
try {
$data = yaml_parse($rawYaml);
if (false === $data) {
header($_SERVER[ "SERVER_PROTOCOL" ] . " 400 Bad Request", true, 400);
$response = new \stdClass();
$response->error = _("Malformed YAML data received in the request");
die(json_encode($response));
self::produceErrorResponse(StatusCode::BAD_REQUEST, "Malformed YAML data received in the request");
} else {
$this->parameterData = $data;
}
} catch (\Exception $e) {
header($_SERVER[ "SERVER_PROTOCOL" ] . " 400 Bad Request", true, 400);
$response = new \stdClass();
$response->error = sprintf(
_("Malformed YAML data received in the request: %s"),
$e->getMessage()
);
die(json_encode($response));
$message = "Malformed YAML data received in the request: " . $e->getMessage();
self::produceErrorResponse(StatusCode::BAD_REQUEST, $message);
}
} else {
switch (strtoupper($_SERVER["REQUEST_METHOD"])) {
Expand All @@ -149,23 +134,36 @@ private function initParameterData()
$this->parameterData = $_GET;
break;
default:
header($_SERVER["SERVER_PROTOCOL"] . " 405 Method Not Allowed", true, 405);
$response = new \stdClass();
$response->status = "error";
$response->message = sprintf(
_('Allowed request methods are: %1$s; but request method was \'%2$s\''),
$message = sprintf(
'Allowed request methods are: %1$s; but request method was \'%2$s\'',
implode(', ', self::ALLOWED_REQUEST_METHODS),
strtoupper($_SERVER['REQUEST_METHOD'])
$_SERVER['REQUEST_METHOD']
);
die(json_encode($response));
self::produceErrorResponse(StatusCode::METHOD_NOT_ALLOWED, $message);
}
}

self::$responseContentType = (
isset($this->parameterData["RETURN"])
&& in_array(strtolower($this->parameterData["RETURN"]), self::ALLOWED_RETURN_TYPES)
)
? strtolower($this->parameterData["RETURN"])
: (
self::$acceptHeader !== null
? (string) self::ALLOWED_RETURN_TYPES[array_search(self::$acceptHeader, self::ALLOWED_ACCEPT_HEADERS)]
: (string) self::ALLOWED_RETURN_TYPES[0]
);
$this->RESPONSE->Messages[] = sprintf(
'Return parameter set to \'%1$s\', response content type set to \'%2$s\'',
$this->parameterData["RETURN"],
self::$responseContentType
);

if (!isset($this->parameterData["YEAR"]) || $this->parameterData["YEAR"] === "") {
$this->parameterData["YEAR"] = (int)date("Y");
}
$this->RESPONSE->Messages[] = sprintf(
_('Year set to %d'),
'Year set to %d',
$this->parameterData["YEAR"]
);

Expand All @@ -176,7 +174,7 @@ private function initParameterData()
$this->parameterData["LOCALE"] = "en_US";
$this->parameterData["BASE_LOCALE"] = \Locale::getPrimaryLanguage($this->parameterData["LOCALE"]);
$this->RESPONSE->Messages[] = sprintf(
_('Allowed base locales are: \'%1$s\'; but base locale requested was \'%2$s\''),
'Allowed base locales are: \'%1$s\'; but base locale requested was \'%2$s\'',
implode(', ', self::ALLOWED_LOCALES),
$this->parameterData["BASE_LOCALE"]
);
Expand All @@ -186,27 +184,11 @@ private function initParameterData()
$this->parameterData["BASE_LOCALE"] = \Locale::getPrimaryLanguage($this->parameterData["LOCALE"]);
}
$this->RESPONSE->Messages[] = sprintf(
_('Locale set to \'%1$s\', base locale set to \'%2$s\''),
'Locale set to \'%1$s\', base locale set to \'%2$s\'',
$this->parameterData["LOCALE"],
$this->parameterData["BASE_LOCALE"]
);

$this->responseContentType = (
isset($this->parameterData["RETURN"])
&& in_array(strtolower($this->parameterData["RETURN"]), self::ALLOWED_RETURN_TYPES)
)
? strtolower($this->parameterData["RETURN"])
: (
$this->acceptHeader !== ""
? (string) self::ALLOWED_RETURN_TYPES[array_search($this->requestHeaders["Accept"], self::ALLOWED_ACCEPT_HEADERS)]
: (string) self::ALLOWED_RETURN_TYPES[0]
);
$this->RESPONSE->Messages[] = sprintf(
_('Return parameter set to \'%1$s\', response content type set to \'%2$s\''),
$this->parameterData["RETURN"],
$this->responseContentType
);
$this->RESPONSE->Messages[] = _("parameter data initialized");
$this->RESPONSE->Messages[] = "parameter data initialized";
}

private static function warningHandler($errno, $errstr)
Expand All @@ -231,17 +213,17 @@ private function prepareL10N(): void
$textdomainpath = bindtextdomain("litcal", "i18n");
$textdomain = textdomain("litcal");
$this->RESPONSE->Messages[] = sprintf(
_('PHP setlocale set to locale %1$s, text domain path set to %2$s, text domain set to %3$s'),
'PHP setlocale set to locale %1$s, text domain path set to %2$s, text domain set to %3$s',
$locale ? $locale : 'false',
$textdomainpath,
$textdomain
);
}

private function setReponseContentTypeHeader()
private static function setReponseContentTypeHeader()
{
$header = '';
switch ($this->responseContentType) {
$header = null;
switch (self::$responseContentType) {
case "xml":
$header = 'Content-Type: application/xml; charset=utf-8';
break;
Expand All @@ -258,10 +240,6 @@ private function setReponseContentTypeHeader()
$header = 'Content-Type: application/json; charset=utf-8';
}
header($header);
$this->RESPONSE->Messages[] = sprintf(
_("Response Content-Type header set to '%s'"),
$header
);
}

private function readData()
Expand All @@ -273,12 +251,14 @@ private function readData()
$lclData = json_decode(file_get_contents($translationFile));
$results = json_decode(file_get_contents($dataFile));
$this->RESPONSE->Messages[] = sprintf(
_("%d localized data events loaded from translation file %s"),
/**translators: 1: count, 2: filename */
_('%1$d localized data events loaded from translation file %2$s'),
count(get_object_vars($lclData)),
$translationFile
);
$this->RESPONSE->Messages[] = sprintf(
_("%d events loaded from data file %s"),
/**translators: 1: count, 2: filename */
_('%1$d events loaded from data file %2$s'),
count($results),
$dataFile
);
Expand Down Expand Up @@ -309,17 +289,20 @@ private function readData()
});
}
$this->RESPONSE->Messages[] = sprintf(
/**translators: count */
_("%d data rows calculated"),
count($this->RESPONSE->LitEvents)
);
} else {
$this->RESPONSE->Messages[] = sprintf(
/**translators: filename */
_("missing translation file: %s"),
$translationFile
);
}
} else {
$this->RESPONSE->Messages[] = sprintf(
/**translators: filename */
_("missing data file: %s"),
$dataFile
);
Expand Down Expand Up @@ -359,16 +342,48 @@ private function isAnniversary(LitEvent $litEvent): bool
return false;
}

private function outputResults()
private static function produceErrorResponse(int $statusCode, string $description): void
{
switch ($this->responseContentType) {
// if $responseContentType is null, we probably haven't set the response Content-Type header yet
if (null === self::$responseContentType) {
// so let's attempt at doing so the same way initParameterData handles it
if (null !== self::$acceptHeader && in_array(self::$acceptHeader, self::ALLOWED_ACCEPT_HEADERS)) {
self::$responseContentType = (string) self::ALLOWED_RETURN_TYPES[array_search(self::$acceptHeader, self::ALLOWED_ACCEPT_HEADERS)];
} else {
self::$responseContentType = (string) self::ALLOWED_RETURN_TYPES[0];
}
self::setReponseContentTypeHeader();
}
header($_SERVER[ "SERVER_PROTOCOL" ] . StatusCode::toString($statusCode), true, $statusCode);
$message = new \stdClass();
$message->status = "ERROR";
$message->description = $description;
$response = json_encode($message);
switch (self::$responseContentType) {
case 'yaml':
$responseObj = json_decode(json_encode($this->RESPONSE), true);
$responseObj = json_decode($response, true);
echo yaml_emit($responseObj, YAML_UTF8_ENCODING);
break;
break;
case 'xml':
//TODO: NOT YET SUPPORTED
case 'html':
// do not emit anything, the header should be enough
break;
case 'json':
default:
echo $response;
}
die();
}

private function produceResponse()
{
switch (self::$responseContentType) {
case 'yaml':
$responseObj = json_decode(json_encode($this->RESPONSE), true);
echo yaml_emit($responseObj, YAML_UTF8_ENCODING);
break;
case 'xml':
case 'html':
//TODO: NOT YET SUPPORTED
break;
Expand Down
27 changes: 27 additions & 0 deletions src/AnniversaryCalculator/Enums/StatusCode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace LitCal\AnniversaryCalculator\Enums;

class StatusCode
{
public const BAD_REQUEST = 400;
public const NOT_FOUND = 404;
public const METHOD_NOT_ALLOWED = 405;
public const NOT_ACCEPTABLE = 406;
public const UNSUPPORTED_MEDIA_TYPE = 415;
public const UNPROCESSABLE_CONTENT = 422;
public const SERVICE_UNAVAILABLE = 503;
private const STATUS_CODES = [
StatusCode::BAD_REQUEST => " 400 Bad Request",
StatusCode::NOT_FOUND => " 404 Not Found",
StatusCode::METHOD_NOT_ALLOWED => " 405 Method Not Allowed",
StatusCode::NOT_ACCEPTABLE => " 406 Not Acceptable",
StatusCode::UNSUPPORTED_MEDIA_TYPE => " 415 Unsupported Media Type",
StatusCode::UNPROCESSABLE_CONTENT => " 422 Unprocessable Content",
StatusCode::SERVICE_UNAVAILABLE => " 503 Service Unavailable"
];
public static function toString(int $code): string
{
return StatusCode::STATUS_CODES[ $code ];
}
}

0 comments on commit 6066c24

Please sign in to comment.