Page MenuHomeCode

No OneTemporary

diff --git a/Engines/API/Cerbere.php b/Engines/API/Cerbere.php
new file mode 100644
index 0000000..70f470e
--- /dev/null
+++ b/Engines/API/Cerbere.php
@@ -0,0 +1,128 @@
+<?php
+
+namespace Zed\Engines\API;
+
+use Keruald\Database\DatabaseEngine;
+use Keruald\OmniTools\HTTP\Requests\Request;
+use Keruald\OmniTools\Identifiers\UUID;
+
+use Zed\Engines\Database\Tables;
+
+use InvalidArgumentException;
+
+trait Cerbere {
+
+ ///
+ /// Constants
+ ///
+
+ /**
+ * Determines if localhost calls could be passed.
+ *
+ * If true, any call from localhost is valid.
+ * Otherwise, normal security rules are applied.
+ */
+ public const ALLOW_LOCALHOST = false;
+
+ /**
+ * Determines if error should be printed.
+ *
+ * If true, the error will be printed according the FORMAT_ERROR setting.
+ * Otherwise, a blank page will be served.
+ */
+ public const OUTPUT_ERROR = true;
+
+ /**
+ * Determines if the error must be formatted.
+ *
+ * If true, any error will be sent as API output.
+ * Otherwise, it will be printed as is.
+ *
+ * Ignored if OUTPUT_ERROR is set to false.
+ */
+ public const FORMAT_ERROR = false;
+
+ ///
+ /// Traits requirements
+ ///
+
+ public abstract function die (string $message) : never;
+
+ public abstract function getDatabase () : DatabaseEngine;
+
+ ///
+ /// API security methods
+ ///
+
+ /**
+ * Checks if credentials are okay and exits if not
+ *
+ * If the credentials aren't valid, it will print an error message if
+ * OUTPUT_ERROR is defined and true.
+ *
+ * This error message will be formatted through the api_output function if
+ * FORMAT_ERROR is defined and true ; otherwise, it will be print as is.
+ *
+ * To help debug, you can also define ALLOW_LOCALHOST. If this constant is
+ * rue, any call from localhost will be accepted, without checking the key.
+ *
+ * @see die
+ */
+ public function guard () : void {
+ //If ALLOW_LOCALHOST is true, we allow 127.0.0.1 queries
+ //If you use one of your local IP in your webserver vhost like 10.0.0.3
+ //it could be easier to create yourself a test key
+ if (self::ALLOW_LOCALHOST && Request::isFromLocalHost()) {
+ return;
+ }
+
+ $key = $_REQUEST['key'] ?? "";
+ $this->validateApiKey($key); // never return when key is invalid
+ $this->increaseApiKeyHit($key);
+ }
+
+ private function validateApiKey($key): void {
+ //No key, no authentication
+ if ($key === "") {
+ $this->die('You must add credentials to your request.');
+ }
+
+ if (!UUID::isUUID($key)) {
+ $this->die("The key format is invalid.");
+ }
+
+ //Authenticates user
+ $db = $this->getDatabase();
+ $sql = "SELECT key_active FROM " . Tables::API_KEYS .
+ " WHERE key_guid like '$key'";
+
+ $result = $db->query($sql);
+ if (!$result) {
+ $this->die("Can't find API key in database.");
+ }
+
+ $row = $db->fetchRow($result);
+ if (!$row) {
+ $this->die("Key doesn't exist.");
+ }
+
+ if (!$row['key_active']) {
+ $this->die("Key disabled.");
+ }
+ }
+
+ public function increaseApiKeyHit (string $key) : void {
+ if (!UUID::isUUID($key)) {
+ throw new InvalidArgumentException("The key must be an UUID.");
+ }
+
+ $db = $this->getDatabase();
+
+ $sql = "UPDATE " . Tables::API_KEYS . " SET key_hits = key_hits + 1" .
+ " WHERE key_guid like '$key'";
+ if (!$db->query($sql)) {
+ $this->die("Can't record API call to database.");
+ }
+ }
+
+}
diff --git a/Engines/API/Format/ResponseFormat.php b/Engines/API/Format/ResponseFormat.php
new file mode 100644
index 0000000..dae312e
--- /dev/null
+++ b/Engines/API/Format/ResponseFormat.php
@@ -0,0 +1,33 @@
+<?php
+declare(strict_types=1);
+
+namespace Zed\Engines\API\Format;
+
+enum ResponseFormat : string {
+ /**
+ * Information about a variable in a way that's readable by humans
+ */
+ case Preview = "preview";
+
+ /**
+ * PHP serialization
+ */
+ case PHP = "php";
+
+ /**
+ * JSON payload
+ */
+ case JSON = "json";
+
+ /**
+ * XML payload
+ */
+ case XML = "xml";
+
+ /**
+ * A text representation.
+ *
+ * Useful only for scalar information.
+ */
+ case String = "string";
+}
diff --git a/Engines/API/Format/StringView.php b/Engines/API/Format/StringView.php
new file mode 100644
index 0000000..9284dce
--- /dev/null
+++ b/Engines/API/Format/StringView.php
@@ -0,0 +1,42 @@
+<?php
+declare(strict_types=1);
+
+namespace Zed\Engines\API\Format;
+
+use Keruald\OmniTools\Collections\HashMap;
+use Keruald\OmniTools\Collections\Vector;
+
+use LogicException;
+
+class StringView {
+ public static function toString (mixed $data) : string {
+ switch (ValueRepresentation::from($data)) {
+ case ValueRepresentation::List:
+ $response = Vector::from($data)
+ ->map(fn($item) => self::toString($item))
+ ->implode(", ");
+ return "[$response]";
+
+ case ValueRepresentation::Object:
+ $response = HashMap::from($data)
+ ->mapToVector(
+ fn($key, $value) => "$key: " . self::toString($value)
+ )
+ ->implode(", ");
+ return '{' . $response . '}';
+
+ case ValueRepresentation::Scalar:
+ return self::getScalarElement($data);
+ }
+
+ throw new LogicException("Unreachable code");
+ }
+
+ public static function getScalarElement (mixed $data) : string {
+ if (is_bool($data)) {
+ return $data ? 'true' : 'false';
+ }
+
+ return (string)$data;
+ }
+}
diff --git a/Engines/API/Format/ValueRepresentation.php b/Engines/API/Format/ValueRepresentation.php
new file mode 100644
index 0000000..25cfaa6
--- /dev/null
+++ b/Engines/API/Format/ValueRepresentation.php
@@ -0,0 +1,46 @@
+<?php
+declare(strict_types=1);
+
+namespace Zed\Engines\API\Format;
+
+enum ValueRepresentation {
+
+ ///
+ /// Possible values
+ ///
+
+ /**
+ * Representation as XML tags with generic key
+ */
+ case List;
+
+ /**
+ * Representation as XML tags matching keys
+ */
+ case Object;
+
+ /**
+ * Direct representation as string
+ */
+ case Scalar;
+
+ ///
+ /// Helper methods
+ ///
+
+ public static function from (mixed $value) : self {
+ if (is_object($value)) {
+ return self::Object;
+ }
+
+ if (is_array($value)) {
+ if (array_is_list($value)) {
+ return self::List;
+ }
+
+ return self::Object;
+ }
+
+ return self::Scalar;
+ }
+}
diff --git a/Engines/API/Format/XmlDocument.php b/Engines/API/Format/XmlDocument.php
new file mode 100644
index 0000000..cdbb108
--- /dev/null
+++ b/Engines/API/Format/XmlDocument.php
@@ -0,0 +1,172 @@
+<?php
+declare(strict_types=1);
+
+namespace Zed\Engines\API\Format;
+
+use Keruald\OmniTools\Reflection\CodeClass;
+
+use DOMDocument;
+use RuntimeException;
+use SimpleXMLElement;
+
+class XmlDocument {
+
+ ///
+ /// Constants
+ ///
+
+ const IGNORED_KEYS = [
+ "lastError",
+ ];
+
+ ///
+ /// Properties
+ ///
+
+ private SimpleXMLElement $output;
+
+ ///
+ /// Constructors
+ ///
+
+ public function __construct (
+ private readonly mixed $data,
+ private readonly string $rootNodeName = 'data',
+ private readonly string $dataNodeName = 'item',
+ private readonly string $unknownNodeName = 'unknownNode',
+ ) {
+ }
+
+ public static function toXml(
+ mixed $data,
+ string $rootNodeName = 'data',
+ string $dataNodeName = 'item',
+ string $unknownNodeName = 'unknownNode'
+ ) : string {
+ if (!is_array($data) && !is_object($data)) {
+ // Straightforward case
+ $data = StringView::getScalarElement($data);
+ return '<?xml version="1.0" encoding="utf-8"?>'
+ . "\n<$rootNodeName>$data</$rootNodeName>\n";
+ }
+
+ $document = new XmlDocument(
+ $data,
+ $rootNodeName,
+ $dataNodeName,
+ $unknownNodeName,
+ );
+
+ return $document->build();
+ }
+
+ ///
+ /// Document builder
+ ///
+
+ public function build () : string {
+ $this->output = simplexml_load_string(
+ "<?xml version='1.0' encoding='utf-8'?><$this->rootNodeName />"
+ );
+
+ $this->buildElement(
+ $this->data,
+ $this->output,
+ $this->dataNodeName,
+ true,
+ );
+
+ return $this->getXML();
+ }
+
+ private function buildElement (
+ mixed $data,
+ SimpleXMLElement $parent,
+ string $nodeName = null,
+ bool $isRootElement = false,
+ ): void {
+ if ($nodeName === null) {
+ $nodeName = $this->unknownNodeName;
+ }
+
+ // If we're at top level, we want to add properties directly
+ // to the root element, and not create <item></item> enclosure.
+ $get_element = fn() => match ($isRootElement) {
+ true => $parent,
+ false => $parent->addChild($nodeName),
+ };
+
+ switch (ValueRepresentation::from($data)) {
+ case ValueRepresentation::List:
+ $element = $get_element();
+ foreach ($data as $value) {
+ $key = match ($isRootElement) {
+ true => $nodeName,
+ false => $this->buildVectorKey($value),
+ };
+
+ $this->buildElement($value, $element, $key);
+ }
+ break;
+
+ case ValueRepresentation::Object:
+ $element = $get_element();
+ foreach ($data as $key => $value) {
+ if (self::isIgnoredProperty($key, $value)) {
+ continue;
+ }
+
+ $this->buildElement($value, $element, $key);
+ }
+ break;
+
+ case ValueRepresentation::Scalar:
+ $value = StringView::getScalarElement($data);
+
+ if ($isRootElement) {
+ $parent[0] = $value;
+ } else {
+ $parent->addChild($nodeName, $value);
+ }
+ break;
+ }
+ }
+
+ private static function isIgnoredProperty (
+ string $key, mixed $value
+ ) : bool {
+ return $value === null || in_array($key, self::IGNORED_KEYS);
+ }
+
+ ///
+ /// Document formatter
+ ///
+
+ public function getXML (): string {
+ $dom = new DOMDocument("1.0");
+ $dom->preserveWhiteSpace = false;
+ $dom->formatOutput = true;
+ $dom->loadXML($this->output->asXML());
+
+ $xml = $dom->saveXML();
+ return match ($xml) {
+ false => throw new RuntimeException("Can't parse XML"),
+ default => $xml,
+ };
+ }
+
+ private function buildVectorKey (mixed $value) : string {
+ // If $value is an object, we can derive the tag name
+ // from the object's class name.
+ if (is_object($value)) {
+ try {
+ $key = CodeClass::from($value)->getShortClassName();
+ return strtolower($key);
+ } catch (\ReflectionException $e) {
+ }
+ }
+
+ return $this->unknownNodeName;
+ }
+
+}
diff --git a/Engines/API/Response.php b/Engines/API/Response.php
new file mode 100644
index 0000000..fcde1ed
--- /dev/null
+++ b/Engines/API/Response.php
@@ -0,0 +1,109 @@
+<?php
+declare(strict_types=1);
+
+namespace Zed\Engines\API;
+
+use Keruald\Database\DatabaseEngine;
+use Zed\Engines\API\Format\ResponseFormat;
+use Zed\Engines\API\Format\StringView;
+use Zed\Engines\API\Format\XmlDocument;
+use Zed\Engines\Database\WithDatabase;
+
+class Response {
+
+ use WithDatabase;
+ use Cerbere;
+
+ ///
+ /// Properties
+ ///
+
+ public mixed $reply = "null";
+
+ private string $xmlRoot = "data";
+
+ private string $xmlChildren = "item";
+
+ private ResponseFormat $format = ResponseFormat::JSON;
+
+ ///
+ /// Constructors
+ ///
+
+ public function __construct (DatabaseEngine $db) {
+ $this->setDatabase($db);
+ }
+
+ public static function withFormat (
+ DatabaseEngine $db,
+ string $format,
+ ) : self {
+ $response = new self($db);
+ $response->format = ResponseFormat::from($format);
+
+ return $response;
+ }
+
+ ///
+ /// Output
+ ///
+
+ /**
+ * Sets response content and prints it
+ */
+ public function output (
+ mixed $reply = null,
+ string $xmlRoot = "data",
+ string $xmlChildren = "item",
+ ) : void {
+ $this->reply = $reply;
+ $this->xmlRoot = $xmlRoot;
+ $this->xmlChildren = $xmlChildren;
+
+ $this->print();
+ }
+
+ public function print () : void {
+ echo match ($this->format) {
+ ResponseFormat::Preview => $this->getPreview(),
+
+ ResponseFormat::PHP => serialize($this->reply),
+
+ ResponseFormat::JSON => json_encode($this->reply),
+
+ ResponseFormat::XML => XmlDocument::toXml(
+ $this->reply,
+ $this->xmlRoot,
+ $this->xmlChildren
+ ),
+
+ ResponseFormat::String => StringView::toString($this->reply),
+ };
+ }
+
+ private function getPreview () : string {
+ return '<pre>' . print_r($this->reply, true) . '</pre>';
+ }
+
+ ///
+ /// Error output
+ ///
+
+ /**
+ * Prints a message in raw or API format, then exits.
+ *
+ * The error message will be formatted through api_output if the constant
+ * FORMAT_ERROR is defined and true. Otherwise, it will be printed as is.
+ */
+ public function die (string $message) : never {
+ if (self::OUTPUT_ERROR) {
+ if (self::FORMAT_ERROR) {
+ $this->output($message, "error");
+ } else {
+ echo $message;
+ }
+ }
+ exit;
+ }
+
+}
diff --git a/Engines/Database/Tables.php b/Engines/Database/Tables.php
new file mode 100644
index 0000000..2b46182
--- /dev/null
+++ b/Engines/Database/Tables.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Zed\Engines\Database;
+
+class Tables {
+ public const API_KEYS = "api_keys";
+}
diff --git a/api.php b/api.php
index 5d099ab..336ddc6 100644
--- a/api.php
+++ b/api.php
@@ -1,338 +1,338 @@
<?php
/**
* API entry point
*
* Zed. The immensity of stars. The HyperShip. The people.
*
* (c) 2010, Dereckson, some rights reserved.
* Released under BSD license.
*
* @package Zed
* @subpackage EntryPoints
* @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
* @copyright 2010 Sébastien Santoro aka Dereckson
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* @version 0.1
* @link http://scherzo.dereckson.be/doc/zed
* @link http://zed.dereckson.be/
* @filesource
* @todo Consider to output documentation on / and /ship queries
* @todo /app/getdata
*/
+use Zed\Engines\API\Response;
use Zed\Engines\Database\Database;
use Zed\Models\Geo\Galaxy;
use Zed\Models\Geo\Location;
use Zed\Models\Objects\Application;
use Zed\Models\Objects\Perso;
use Zed\Models\Objects\Ship;
//API preferences
define('URL', 'http://' . $_SERVER['HTTP_HOST'] . '/index.php');
//Pluton library
require_once('includes/core.php');
require_once('includes/config.php');
-//API libs
-require_once('includes/api/api_helpers.php');
-require_once('includes/api/cerbere.php');
-
//Use our URL controller method if you want to mod_rewrite the API
$Config['SiteURL'] = get_server_url() . $_SERVER["PHP_SELF"];
$url = get_current_url_fragments();
//Database
$db = Database::load($Config['database']);
+//API response
+$format = $_REQUEST['format'] ?? 'preview';
+$apiResponse = Response::withFormat($db, $format);
+
switch ($module = $url[0]) {
/* -------------------------------------------------------------
Site API
/time
/location
/coordinates
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case '':
//Nothing to do
//TODO: offer documentation instead
die();
case 'time':
//Hypership time
- api_output(get_hypership_time(), "time");
+ $apiResponse->output(get_hypership_time(), "time");
break;
case 'location':
//Checks credentials
- cerbere();
+ $apiResponse->guard();
//Gets location info
$location = new Location($db, $url[1], $url[2]);
- api_output($location, "location");
+ $apiResponse->output($location, "location");
break;
case 'coordinates':
//Checks credentials
- cerbere();
+ $apiResponse->guard();
//Get coordinates
- api_output(Galaxy::getCoordinates($db), 'galaxy', 'object');
+ $apiResponse->output(Galaxy::getCoordinates($db), 'galaxy', 'object');
break;
-
/* -------------------------------------------------------------
Ship API
/authenticate
/appauthenticate
/appauthenticated
/move
/land
/flyout
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case 'ship':
//Ship API
//Gets ship from Ship API key (distinct of regular API keys)
- $ship = Ship::from_api_key($_REQUEST['key']) or cerbere_die("Invalid ship API key");
+ $ship = Ship::from_api_key($_REQUEST['key']) or $apiResponse->die("Invalid ship API key");
switch ($command = $url[1]) {
case '':
//Nothing to do
//TODO: offer documentation instead
die();
case 'authenticate':
//TODO: web authenticate
break;
case 'appauthenticate':
//Allows desktop application to authenticate an user
- $tmp_session_id = $url[2] or cerbere_die("/appauthenticate/ must be followed by any session identifier");
+ $tmp_session_id = $url[2] or $apiResponse->die("/appauthenticate/ must be followed by any session identifier");
if ($_REQUEST['name']) {
//Perso will be offered auth invite at next login.
//Handy for devices like PDA, where it's not easy to auth.
$perso = new Perso($db, $_REQUEST['name']);
if ($perso->lastError) {
- cerbere_die($perso->lastError);
+ $apiResponse->die($perso->lastError);
}
if (!$ship->is_perso_authenticated($perso->id)) {
$ship->request_perso_authenticate($perso->id);
}
$ship->request_perso_confirm_session($tmp_session_id, $perso->id);
} else {
//Delivers an URL. App have to redirects user to this URL
//launching a browser or printing the link.
$ship_code = $ship->get_code();
registry_set($db, "api.ship.session.$ship_code.$tmp_session_id", -1);
$url = get_server_url() . get_url() . "?action=api.ship.appauthenticate&session_id=" . $tmp_session_id;
- api_output($url, "URL");
+ $apiResponse->output($url, "URL");
}
break;
case 'appauthenticated':
//Checks the user authentication
- $tmp_session_id = $url[2] or cerbere_die("/appauthenticated/ must be followed by any session identifier you used in /appauthenticate");
+ $tmp_session_id = $url[2] or $apiResponse->die("/appauthenticated/ must be followed by any session identifier you used in /appauthenticate");
$perso_id = $ship->get_perso_from_session($tmp_session_id);
if (!$isPersoAuth = $ship->is_perso_authenticated($perso_id)) {
//Global auth not ok/revoked.
$auth->status = -1;
} else {
$perso = Perso::get($db, $perso_id);
$auth->status = 1;
$auth->perso->id = $perso->id;
$auth->perso->nickname = $perso->nickname;
$auth->perso->name = $perso->name;
//$auth->perso->location = $perso->location;
//Is the perso on board? Yes if its global location is S...
$auth->perso->onBoard = (
$perso->location_global[0] == 'S' &&
substr($perso->location_global, 1, 5) == $ship->id
);
if ($auth->perso->onBoard) {
//If so, give local location
$auth->perso->location_local = $perso->location_local;
}
}
- api_output($auth, "auth");
+ $apiResponse->output($auth, "auth");
break;
case 'move':
//Moves the ship to a new location, given absolute coordinates
//TODO: handle relative moves
if (count($url) < 2) {
- cerbere_die("/move/ must be followed by a location expression");
+ $apiResponse->die("/move/ must be followed by a location expression");
}
//Gets location class
//It's allow: (1) to normalize locations between formats
// (2) to ensure the syntax
//==> if the ship want to communicate free forms coordinates, must be added on Location a free format
try {
$location = new Location($db, $url[2]);
} catch (Exception $ex) {
$reply->success = 0;
$reply->error = $ex->getMessage();
- api_output($reply, "move");
+ $apiResponse->output($reply, "move");
break;
}
$ship->location_global = $location->global;
$ship->save_to_database();
$reply->success = 1;
$reply->location = $ship->location;
- api_output($reply, "move");
+ $apiResponse->output($reply, "move");
break;
case 'land':
case 'flyin':
//Flies in
try {
$location = new Location($db, $location);
} catch (Exception $ex) {
$reply->success = 0;
$reply->error = $ex->getMessage();
- api_output($reply, "land");
+ $apiResponse->output($reply, "land");
break;
}
break;
case 'flyout':
//Flies out
break;
}
break;
/* -------------------------------------------------------------
Application API
/checkuserkey
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
case 'app':
//Application API
- $app = Application::from_api_key($db, $_REQUEST['key']) or cerbere_die("Invalid application API key");
+ $app = Application::from_api_key($db, $_REQUEST['key']) or $apiResponse->die("Invalid application API key");
switch ($command = $url[1]) {
case '':
//Nothing to do
//TODO: offer documentation instead
die();
case 'checkuserkey':
if (count($url) < 2) {
- cerbere_die("/checkuserkey/ must be followed by an user key");
+ $apiResponse->die("/checkuserkey/ must be followed by an user key");
}
$reply = (boolean)$app->get_perso_id($url[2]);
- api_output($reply, "check");
+ $apiResponse->output($reply, "check");
break;
case 'pushuserdata':
if (count($url) < 3) {
- cerbere_die("/pushuserdata/ must be followed by an user key");
+ $apiResponse->die("/pushuserdata/ must be followed by an user key");
}
- $perso_id = $app->get_perso_id($url[2]) or cerbere_die("Invalid application user key");
+ $perso_id = $app->get_perso_id($url[2]) or $apiResponse->die("Invalid application user key");
//then, falls to 'pushdata'
case 'pushdata':
$data_id = $_REQUEST['data'] ?: new_guid();
//Gets data
switch ($mode = $_REQUEST['mode']) {
case '':
- cerbere_die("Add in your data posted or in the URL mode=file to read data from the file posted (one file per api call) or mode=request to read data from \$_REQUEST['data'].");
+ $apiResponse->die("Add in your data posted or in the URL mode=file to read data from the file posted (one file per api call) or mode=request to read data from \$_REQUEST['data'].");
case 'request':
$data = $_REQUEST['data'];
$format = "raw";
break;
case 'file':
- $file = $_FILES['datafile']['tmp_name'] or cerbere_die("File is missing");
+ $file = $_FILES['datafile']['tmp_name'] or $apiResponse->die("File is missing");
if (!is_uploaded_file($file)) {
- cerbere_die("Invalid form request");
+ $apiResponse->die("Invalid form request");
}
$data = "";
if (preg_match('/\.tar$/', $file)) {
$format = "tar";
$data = file_get_contents($file);
} elseif (preg_match('/\.tar\.bz2$/', $file)) {
$format = "tar";
} elseif (preg_match('/\.bz2$/', $file)) {
$format = "raw";
} else {
$format = "raw";
$data = file_get_contents($file);
}
if ($data === "") {
//.bz2
- $bz = bzopen($file, "r") or cerbere_die("Couldn't open $file");
+ $bz = bzopen($file, "r") or $apiResponse->die("Couldn't open $file");
while (!feof($bz)) {
$data .= bzread($bz, BUFFER_SIZE);
}
bzclose($bz);
}
unlink($file);
break;
default:
- cerbere_die("Invalid mode. Expected: file, request");
+ $apiResponse->die("Invalid mode. Expected: file, request");
}
//Saves data
global $db;
$data_id = $db->escape($data_id);
$data = $db->escape($data);
$perso_id = $perso_id ?: 'NULL';
$sql = "REPLACE INTO applications_data (application_id, data_id, data_content, data_format, perso_id) VALUES ('$app->id', '$data_id', '$data', '$format', $perso_id)";
if (!$db->query($sql)) {
+ $apiResponse->die("Can't save data");
message_die(SQL_ERROR, "Can't save data", '', __LINE__, __FILE__, $sql);
}
- //cerbere_die("Can't save data");
//Returns
- api_output($data_id);
+ $apiResponse->output($data_id, "data");
break;
case 'getuserdata':
// /api.php/getuserdata/data_id/perso_key
// /api.php/getdata/data_id
if (count($url) < 3) {
- cerbere_die("/getuserdata/ must be followed by an user key");
+ $apiResponse->die("/getuserdata/ must be followed by an user key");
}
- $perso_id = $app->get_perso_id($url[2]) or cerbere_die("Invalid application user key");
+ $perso_id = $app->get_perso_id($url[2]) or $apiResponse->die("Invalid application user key");
//then, falls to 'getdata'
case 'getdata':
if (count($url) < 2) {
- cerbere_die('/' . $url[0] . '/ must be followed by the data ID');
+ $apiResponse->die('/' . $url[0] . '/ must be followed by the data ID');
}
if (!$perso_id) {
$perso_id = 'NULL';
}
$data_id = $db->escape($url[1]);
$sql = "SELECT data_content FROM applications_data WHERE application_id = '$app->id' AND data_id = '$data_id' AND perso_id = $perso_id";
if (!$result = $db->query($sql)) {
message_die(SQL_ERROR, "Unable to query the table", '', __LINE__, __FILE__, $sql);
}
while ($row = $db->fetchRow($result)) {
}
break;
default:
echo "Unknown module:";
dprint_r($url);
break;
}
break;
default:
echo "Unknown module:";
dprint_r($url);
break;
}
diff --git a/composer.json b/composer.json
index e2eeed9..368cee3 100644
--- a/composer.json
+++ b/composer.json
@@ -1,35 +1,36 @@
{
"name": "zed/zed",
"description": "Hypership and galaxy",
"type": "project",
"require": {
"smarty/smarty": "dev-master",
"vlucas/phpdotenv": "^5.5",
"keruald/database": "^0.3.0",
"keruald/globalfunctions": "^0.5.1",
"keruald/omnitools": "^0.8.0",
"hypership/geo": "^0.1.0",
- "php": "^8.1",
+ "php": "^8.2",
+ "ext-dom": "*",
"ext-simplexml": "*"
},
"require-dev": {
"nasqueron/codestyle": "^0.0.1",
"phan/phan": "^5.3.1",
"squizlabs/php_codesniffer": "^3.6",
"phpunit/phpunit": "^10.2.2"
},
"autoload": {
"psr-4": {
"Zed\\Engines\\": "Engines",
"Zed\\Models\\": "Models",
"Zed\\Tests\\": "dev/tests"
}
},
"license": "BSD-2-Clause",
"authors": [
{
"name": "Sébastien Santoro",
"email": "dereckson@espace-win.org"
}
]
}
diff --git a/dev/tests/Engines/API/Format/StringViewTest.php b/dev/tests/Engines/API/Format/StringViewTest.php
new file mode 100644
index 0000000..e3290b4
--- /dev/null
+++ b/dev/tests/Engines/API/Format/StringViewTest.php
@@ -0,0 +1,79 @@
+<?php
+declare(strict_types=1);
+
+namespace Zed\Tests\Engines\API\Format;
+
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\TestCase;
+
+use Zed\Engines\API\Format\StringView;
+
+use stdClass;
+
+class StringViewTest extends TestCase {
+
+ ///
+ /// Tests
+ ///
+
+ #[DataProvider('provideScalarViews')]
+ public function testToString($data, $expected) : void {
+ $this->assertEquals($expected, StringView::toString($data));
+
+ }
+
+
+ #[DataProvider('provideScalarElements')]
+ public function testGetScalarElement($data, $expected) : void {
+ $this->assertEquals($expected, StringView::getScalarElement($data));
+ }
+
+ ///
+ /// Data providers
+ ///
+
+ public static function provideScalarViews () : iterable {
+ // Lists
+ yield [[], "[]"];
+ yield [[1, 2, 3], "[1, 2, 3]"];
+ yield [["foo", "bar"], "[foo, bar]"];
+ yield [[1, 2, 3, "foo", "bar"], "[1, 2, 3, foo, bar]"];
+
+ // Objects
+ $shipA = new stdClass;
+ $shipA->name = "Demios";
+ $shipA->id = 400;
+ yield [$shipA, "{name: Demios, id: 400}"];
+
+ $shipB = new stdClass;
+ $shipB->name = "Demios";
+ $shipB->id = 400;
+ $shipB->manifest = [12, 50, 97];
+ yield [$shipB, "{name: Demios, id: 400, manifest: [12, 50, 97]}"];
+
+ // List of objects
+ yield [
+ [$shipA, $shipB],
+ "[{name: Demios, id: 400}, {name: Demios, id: 400, manifest: [12, 50, 97]}]",
+ ];
+
+ // Scalar elements should be rendered as is
+ foreach (self::provideScalarElements() as $testParameters) {
+ yield $testParameters;
+ }
+ }
+
+ public static function provideScalarElements () : iterable {
+ yield [true, "true"];
+ yield [false, "false"];
+
+ yield [null, ""];
+
+ yield [42, "42"];
+ yield [42.5, "42.5"];
+
+ yield ["", ""];
+ yield ["foo", "foo"];
+ }
+
+}
diff --git a/dev/tests/Engines/API/Format/ValueRepresentationTest.php b/dev/tests/Engines/API/Format/ValueRepresentationTest.php
new file mode 100644
index 0000000..ae25ac1
--- /dev/null
+++ b/dev/tests/Engines/API/Format/ValueRepresentationTest.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Zed\Tests\Engines\API\Format;
+
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\TestCase;
+
+use Zed\Engines\API\Format\ValueRepresentation;
+
+class ValueRepresentationTest extends TestCase {
+
+ public static function provideValuesAndRepresentations () : iterable {
+ // Scalars
+ yield [null, ValueRepresentation::Scalar];
+ yield [true, ValueRepresentation::Scalar];
+ yield [false, ValueRepresentation::Scalar];
+ yield [0, ValueRepresentation::Scalar];
+ yield [1, ValueRepresentation::Scalar];
+ yield [1.1, ValueRepresentation::Scalar];
+ yield ["", ValueRepresentation::Scalar];
+ yield ["foo", ValueRepresentation::Scalar];
+
+ // Arrays
+ yield [[], ValueRepresentation::List];
+ yield [[1, 2, 3], ValueRepresentation::List];
+ yield [
+ [
+ "foo" => "bar",
+ ],
+ ValueRepresentation::Object
+ ];
+
+ // Objects
+ yield [new \stdClass, ValueRepresentation::Object];
+ yield [new \DateTimeImmutable, ValueRepresentation::Object];
+
+ }
+
+ #[DataProvider('provideValuesAndRepresentations')]
+ public function testFrom ($value, $representation) {
+ $this->assertEquals(
+ $representation,
+ ValueRepresentation::from($value)
+ );
+ }
+}
diff --git a/dev/tests/Engines/API/Format/XmlDocumentTest.php b/dev/tests/Engines/API/Format/XmlDocumentTest.php
new file mode 100644
index 0000000..105cef7
--- /dev/null
+++ b/dev/tests/Engines/API/Format/XmlDocumentTest.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Zed\Tests\Engines\API\Format;
+
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\TestCase;
+
+use stdClass;
+use Zed\Engines\API\Format\XmlDocument;
+
+class XmlDocumentTest extends TestCase {
+
+ ///
+ /// Data providers
+ ///
+
+ public static function provideXmlDocuments () : iterable {
+ $dir = dirname(__DIR__, 3) . "/data/XmlDocument";
+
+ yield [[1, 2, 3, 4, 5], "$dir/sequence.xml"];
+
+ $ship = new stdClass;
+ $ship->name = "Demios";
+ $ship->id = 400;
+ $ship->manifest = [12, 50, 97];
+ yield [$ship , "$dir/ship.xml"];
+ }
+
+ ///
+ /// Tests
+ ///
+
+ public function testToXmlWithScalar () {
+ $expected = '<?xml version="1.0" encoding="utf-8"?><foo>666</foo>';
+ $actual = XmlDocument::toXml(666, "foo");
+
+ $this->assertEquals($expected, str_replace("\n", "", $actual));
+ }
+
+ #[DataProvider("provideXmlDocuments")]
+ public function testXml ($data, $expectedPath) {
+ $expected = file_get_contents($expectedPath);
+
+ $this->assertEquals($expected, XmlDocument::toXml($data));
+ }
+
+}
diff --git a/dev/tests/data/XmlDocument/sequence.xml b/dev/tests/data/XmlDocument/sequence.xml
new file mode 100644
index 0000000..2ea70c2
--- /dev/null
+++ b/dev/tests/data/XmlDocument/sequence.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<data>
+ <item>1</item>
+ <item>2</item>
+ <item>3</item>
+ <item>4</item>
+ <item>5</item>
+</data>
diff --git a/dev/tests/data/XmlDocument/ship.xml b/dev/tests/data/XmlDocument/ship.xml
new file mode 100644
index 0000000..de6e5fb
--- /dev/null
+++ b/dev/tests/data/XmlDocument/ship.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<data>
+ <name>Demios</name>
+ <id>400</id>
+ <manifest>
+ <unknownNode>12</unknownNode>
+ <unknownNode>50</unknownNode>
+ <unknownNode>97</unknownNode>
+ </manifest>
+</data>
diff --git a/includes/api/BeautyXML.class.php b/includes/api/BeautyXML.class.php
deleted file mode 100644
index 98c7842..0000000
--- a/includes/api/BeautyXML.class.php
+++ /dev/null
@@ -1,161 +0,0 @@
-<?php
-
-/**
- * XML beautifier
- *
- * Zed. The immensity of stars. The HyperShip. The people.
- *
- * (c) 2010, Dereckson, some rights reserved.
- * Released under BSD license.
- *
- * This class is simple XML beautifier
- * it's very, very, very simple - feature version will be better :-)
- *
- * IMPORTANT NOTE
- * there is no warranty, implied or otherwise with this software.
- *
- * version 0.1 | August 2004
- *
- * released under a LGPL licence.
- *
- * Slawomir Jasinski,
- * http://www.jasinski.us (polish only - my home page)
- * http://www.cgi.csd.pl (english & polish)
- * contact me - sj@gex.pl
- *
- * @package Zed
- * @subpackage API
- * @author Slawomir Jasinski <sj@gex.pl>
- * @copyright 2004 Slawomir Jasinski, 2010 Sébastien Santoro aka Dereckson
- * @license http://www.gnu.org/licenses/lgpl.html LGPL
- * @version 0.1
- * @link http://scherzo.dereckson.be/doc/zed
- * @link http://zed.dereckson.be/
- * @filesource
- *
- * @todo Contact Slawomir Jasinski and ask it if the current code could be
- * relicensed under BSD license. If not, rewrite from scratch under BSD license.
- */
-
-/**
- * This class is simple XML beautifier.
- * It's very, very, very simple - feature version will be better :-)
- *
- * @author Slawomir Jasinski <sj@gex.pl>
- */
-class BeautyXML {
- /**
- * Indicates what characters to use to indent.
- *
- * If you wish a regular tabulation, the suggested value is \t ;
- * If you wish spaces instead, put the correct amount of spaces as value.
- *
- * @var string
- */
- var $how_to_indent = " "; // you can user also \t or more/less spaces
-
- /**
- * Determines if long text have to be wrapped.
- *
- * If true, the text will be wrapped ; otherwise, long lines will be kept.
- *
- * @var bool
- */
- var $wrap = false;
-
- /**
- * If $wrap is true, determines the line length.
- *
- * After this length, any text will be wrapped.
- *
- * @see $wrap
- * @var @int
- */
- var $wrap_cont = 80; // where wrap words
-
- /**
- * Indents the specified string.
- *
- * @param string $str the string to indent
- * @param int $level the indent level, ie the number of indentation to prepend the string with
- */
- function indent (&$str, $level) {
- $spaces = '';
- $level--;
- for ($a = 0; $a < $level; $a++) {
- $spaces .= $this->how_to_indent;
- }
- return $spaces .= $str;
- }
-
- /**
- * Formats the specified string, beautifying it, with proper indent.
- *
- * This is the main class method.
- *
- * @param $str the XML fragment to beautify
- * @return string the beautified XML fragment
- */
- function format ($str) {
-
- $str = preg_replace("/<\?[^>]+>/", "", $str);
-
- $tmp = explode("\n", $str); // extracting string into array
-
- // cleaning string from spaces and other stuff like \n \r \t
- for ($a = 0, $c = count($tmp); $a < $c; $a++) {
- $tmp[$a] = trim($tmp[$a]);
- }
-
- // joining to string ;-)
- $newstr = join("", $tmp);
-
- $newstr = preg_replace("/>([\s]+)<\//", "></", $newstr);
-
- // adding \n lines where tags ar near
- $newstr = str_replace("><", ">\n<", $newstr);
-
- // exploding - each line is one XML tag
- $tmp = explode("\n", $newstr);
-
- // preparing array for list of tags
- $stab = [''];
-
- // lets go :-)
- for ($a = 0, $c = count($tmp); $a <= $c; $a++) {
- $add = true;
-
- preg_match("/<([^\/\s>]+)/", $tmp[$a], $match);
-
- $lan = trim(strtr($match[0], "<>", " "));
-
- $level = count($stab);
-
- if (in_array($lan, $stab) && substr_count($tmp[$a], "</$lan") == 1) {
- $level--;
- $s = array_pop($stab);
- $add = false;
- }
-
- if (substr_count($tmp[$a], "<$lan") == 1 && substr_count($tmp[$a], "</$lan") == 1) {
- $add = false;
- }
-
- if (preg_match("/\/>$/", $tmp[$a], $match)) {
- $add = false;
- }
-
- $tmp[$a] = $this->indent($tmp[$a], $level);
-
- if ($this->wrap) {
- $tmp[$a] = wordwrap($tmp[$a], $this->wrap_cont, "\n" . $this->how_to_indent . $this->how_to_indent . $this->how_to_indent);
- }
-
- if ($add && !@in_array($lan, $stab) && $lan != '') {
- array_push($stab, $lan);
- }
- }
-
- return join("\n", $tmp);
- }
-}
diff --git a/includes/api/api_helpers.php b/includes/api/api_helpers.php
deleted file mode 100644
index f1bcae6..0000000
--- a/includes/api/api_helpers.php
+++ /dev/null
@@ -1,159 +0,0 @@
-<?php
-
-/**
- * API helper functions
- *
- * Zed. The immensity of stars. The HyperShip. The people.
- *
- * (c) 2010, Dereckson, some rights reserved.
- * Released under BSD license.
- *
- * This file provides a functions to output the API message in several formats.
- *
- * The supported formats are preview (PHP dump), XML, PHP serialize and json.
- *
- * The XML outputs code uses the following codes:
- * - http://www.thedeveloperday.com/xml-beautifier-tool/
- * - http://snipplr.com/view/3491/convert-php-array-to-xml-or-simple-xml-object-if-you-wish/
- *
- * @package Zed
- * @subpackage API
- * @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
- * @copyright 2010 Sébastien Santoro aka Dereckson
- * @license http://www.opensource.org/licenses/bsd-license.php BSD
- * @version 0.1
- * @link http://scherzo.dereckson.be/doc/zed
- * @link http://zed.dereckson.be/
- * @filesource
- */
-
-/**
- * The main function for converting to an XML document.
- *
- * Pass in a multidimensional array and this recursively loops through
- * and builds up an XML document.
- *
- * @param mixed $data
- * @param string $rootNodeName What you want the root node to be — defaults to data.
- * @param SimpleXMLElement $xml Should only be used recursively
- * @param string $unknownNodeName Name to give to unknown (numeric) keys
- * @return string XML
- */
-function toXml($data, $rootNodeName = 'data', $xml = null, $unknownNodeName = 'unknownNode') {
- if (!$rootNodeName) {
- $rootNodeName = 'data';
- }
- if (!$unknownNodeName) {
- $unknownNodeName = 'unknownNode';
- }
-
- if ($xml == null) {
- if (!is_array($data) && !is_object($data)) {
- //We've got a singleton
- if (is_bool($data)) {
- $data = $data ? 'true' : 'false';
- }
- return "<?xml version='1.0' encoding='utf-8'?><$rootNodeName>$data</$rootNodeName>";
- }
-
- //Starts with simple document
- $xml = simplexml_load_string("<?xml version='1.0' encoding='utf-8'?><$rootNodeName />");
- }
-
- // loop through the data passed in.
- foreach ($data as $key => $value) {
- // no numeric keys in our xml please!
- if (is_numeric($key)) {
- // make string key...
- $key = $unknownNodeName . '_'. (string)$key;
- }
-
- // replace anything not alphanumeric
- $key = preg_replace('/[^a-z]/i', '', $key);
-
- //If there is another array found recursively call this function
- if (is_array($value)) {
- $node = $xml->addChild($key);
- //Recursive call.
- toXml($value, $rootNodeName, $node, $unknownNodeName);
- } elseif (is_object($value)) {
- $node = $xml->addChild($key);
- foreach ($value as $subkey => $subvalue) {
- if ($subkey == "lastError") {
- continue;
- }
- if ($subvalue === null) {
- //Ignore null values
- continue;
- } elseif (is_array($subvalue) || is_object($subvalue)) {
- //TODO: test this
- //Recursive call.
- $subnode = $node->addChild($subkey);
- toXml($subvalue, $rootNodeName, $subnode, $unknownNodeName);
- } elseif (is_bool($subvalue)) {
- $node->addChild($subkey, $subvalue ? 'true' : 'false');
- } else {
- $node->addChild($subkey, htmlentities($subvalue));
- }
- }
- //die();
- //$array = array();
- //$node = $xml->addChild($key);
- //toXml($value, $rootNodeName, $node, $unknownNodeName);
- } elseif (is_bool($value)) {
- $xml->addChild($key, $value ? 'true' : 'false');
- } else {
- //Adds single node.
- if ($value || $value === 0) {
- $value = htmlentities($value);
- $xml->addChild($key, $value);
- }
- }
- }
- // pass back as string. or simple xml object if you want!
- return $xml->asXML();
-}
-
-/**
- * Outputs API reply, printing it in the specified format.
- *
- * The format will be read form $_REQUEST['format'].
- *
- * @param mixed $reply the reply to format
- * @param string $xmlRoot the XML root element name (optional, default value is 'data').
- * @param string $xmlChildren the XML children elements name (optional, will be deducted from the context if omitted, or, if not possible, will be unknownNode)
- */
-function api_output ($reply, $xmlRoot = null, $xmlChildren = null) {
- $format = $_REQUEST['format'] ?? 'preview';
- switch ($format) {
- case 'preview':
- echo '<pre>';
- print_r($reply);
- echo '</pre>';
- break;
-
- case 'php':
- echo serialize($reply);
- break;
-
- case 'json':
- echo json_encode($reply);
- break;
-
- case 'xml':
- require_once('BeautyXML.class.php');
- $bc = new BeautyXML();
- echo '<?xml version="1.0" encoding="utf-8"?>';
- echo "\n";
- echo $bc->format(toXml($reply, $xmlRoot, null, $xmlChildren));
- break;
-
- case 'string':
- echo $reply;
- break;
-
- default:
- echo "Unknown API format: $_GET[format]";
- break;
- }
-}
diff --git a/includes/api/cerbere.php b/includes/api/cerbere.php
deleted file mode 100644
index 7d06786..0000000
--- a/includes/api/cerbere.php
+++ /dev/null
@@ -1,125 +0,0 @@
-<?php
-
-/**
- * API security
- *
- * Zed. The immensity of stars. The HyperShip. The people.
- *
- * (c) 2010, Dereckson, some rights reserved.
- * Released under BSD license.
- *
- * This file provides a cerbere function, to assert the user is correctly
- * authenticated in the API call.
- *
- * @package Zed
- * @subpackage API
- * @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
- * @copyright 2010 Sébastien Santoro aka Dereckson
- * @license http://www.opensource.org/licenses/bsd-license.php BSD
- * @version 0.1
- * @link http://scherzo.dereckson.be/doc/zed
- * @link http://zed.dereckson.be/
- * @filesource
- */
-
-/**
- * Determines if localhost calls could be passed.
- *
- * If true, any call from localhost is valid. Otherwise, normal security rules are applied.
- */
-define('ALLOW_LOCALHOST', false);
-
-/**
- * Determines if error should be printed.
- *
- * If true, the error will be printed according the FORMAT_ERROR setting. Otherwise, a blank page will be served.
- */
-define('OUTPUT_ERROR', true);
-
-/**
- * Determines if the error must be formatted.
- *
- * If true, any error will be sent to api_output ; otherwise, it will be printed as is.
- */
-define('FORMAT_ERROR', false);
-
-
-if (!defined('TABLE_API_KEYS')) {
- /**
- * The table where are located the API keys
- */
- define('TABLE_API_KEYS', 'api_keys');
-}
-
-/**
- * Checks if credentials are okay and exits if not
- *
- * If the credentials aren't valid, it will print an error message if
- * OUTPUT_ERROR is defined and true.
- *
- * This error message will be formatted through the api_output function if
- * FORMAT_ERROR is defined and true ; otherwise, it will be print as is.
- *
- * To help debug, you can also define ALLOW_LOCALHOST. If this constant is
- * defined and true, any call from localhost will be accepted, without checking
- * the key.
- *
- * @see cerbere_die
- */
-function cerbere () {
- //If ALLOW_LOCALHOST is true, we allow 127.0.0.1 queries
- //If you use one of your local IP in your webserver vhost like 10.0.0.3
- //it could be easier to create yourself a test key
- if (ALLOW_LOCALHOST && $_SERVER['REMOTE_ADDR'] == '127.0.0.1') {
- return;
- }
-
- $guid = $_REQUEST['key'] ?? "";
-
- //No key, no authentication
- if ($guid === "") {
- cerbere_die('You must add credentials to your request.');
- }
-
- //Authenticates user
- global $db;
- $guid = $db->escape($guid);
- $sql = "SELECT key_active FROM " . TABLE_API_KEYS .
- " WHERE key_guid like '$guid'";
- if (!$result = $db->query($sql)) {
- message_die(SQL_ERROR, "Can't get key", '', __LINE__, __FILE__, $sql);
- }
- if ($row = $db->fetchRow($result)) {
- if ($row['key_active']) {
- //key_hits++
- $sql = "UPDATE " . TABLE_API_KEYS . " SET key_hits = key_hits + 1" .
- " WHERE key_guid like '$guid'";
- if (!$db->query($sql)) {
- message_die(SQL_ERROR, "Can't record api call", '', __LINE__, __FILE__, $sql);
- }
- } else {
- cerbere_die("Key disabled.");
- }
- } else {
- cerbere_die("Key doesn't exist.");
- }
-}
-
-/**
- * Prints a message in raw or API format, then exits.
- *
- * The error message will be formatted through api_output if the constant
- * FORMAT_ERROR is defined and true. Otherwise, it will be printed as is.
- *
- * @param string $message The error message to print
- */
-function cerbere_die ($message) : never {
- if (OUTPUT_ERROR) {
- if (FORMAT_ERROR) {
- api_output($message, 'error');
- } else {
- echo $message;
- }
- }
- exit;
-}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Nov 25, 05:23 (3 h, 22 m ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
20744
Default Alt Text
(51 KB)

Event Timeline