Commit f97f42c6 authored by Michael Ochmann's avatar Michael Ochmann

added the `/new`, the `/get` and the `/help` endpoints.

parent 2ce938c6
.idea
settings.ini
apikeys.json
......@@ -6,4 +6,9 @@ RewriteRule ^([\w\+\-\$\§\(\)]+)/? index.php?shortcode=$1
<FilesMatch "\.(?:ini)$">
Order allow,deny
Deny from all
</FilesMatch>
\ No newline at end of file
</FilesMatch>
<FilesMatch "apikeys.json">
Order allow,deny
Deny from all
</FilesMatch>
<?php
namespace FSI;
class ErrorCode {
const ERROR = 500;
const MALFORMED_REQUEST = 501;
const MALFORMED_URL = 502;
const AUTHENTICATION_FAILED = 510;
const PERMISSION_ERROR = 511;
const INVALID_SHORTCODE_FORMAT = 520;
const SHORTCODE_ALREADY_EXISTS = 521;
const SHORTCODE_NOT_EXISTS = 522;
const UNKNOWN_ENDPOINT = 555;
}
class API {
private $route;
private $keys;
private $shortener;
private $request;
private $settings;
private $accessLevel;
public function __construct() {
$this->shortener = new Shortener();
$this->settings = SettingsHandler::Instance();
$route = explode('/', trim(strstr($_SERVER["REQUEST_URI"], "api/"), "/ "));
array_shift($route);
$this->route = (string) trim($route[0]);
$this->keys = json_decode(file_get_contents(dirname(__FILE__)."/../apikeys.json"));
$this->request = json_decode(file_get_contents("php://input"));
if (!isset($this->request->apikey) && $this->route != "help")
$this->error("authentication failure.", ErrorCode::AUTHENTICATION_FAILED);
if ($this->route !== "help" && (!isset($this->request) || !isset($this->request->request) || !is_object($this->request->request)))
$this->error("a malformed request has been sent", ErrorCode::MALFORMED_REQUEST);
$this->checkAPIKey();
if (count($route) <= 0)
$this->error("this endpoint does not exist on the API.", ErrorCode::UNKNOWN_ENDPOINT);
$this->route();
}
private function checkAPIKey() {
if ($this->route === "help")
return;
if (!isset($this->keys->keys->{$this->request->apikey}))
$this->error("authentication failure.", ErrorCode::AUTHENTICATION_FAILED);
$this->accessLevel = $this->keys->keys->{$this->request->apikey}->accesslevel;
}
private function route() {
if (in_array("route_".$this->route, get_class_methods($this)))
$this->{"route_".$this->route}();
else
$this->error("the endpoint '".$this->route."' does not exist on the API.", ErrorCode::UNKNOWN_ENDPOINT);
}
private function route_add() {
if (!isset($this->request->request->url))
$this->error("a malformed request has been sent.", ErrorCode::MALFORMED_REQUEST);
$shortcode = isset($this->request->request->shortcode) ? $this->request->request->shortcode : $this->shortener->generateShortcode($this->settings->system->shortcodeLength);
if (strlen($shortcode) < $this->settings->system->shortcodeMinLength)
$this->error("the shortcode must be at least ".$this->settings->system->shortcodeMinLength." characters long.", ErrorCode::INVALID_SHORTCODE_FORMAT);
if (!preg_match("/^(http|https):\/\//", $this->request->request->url))
$this->error("a malformed URL has been sent.", ErrorCode::MALFORMED_URL);
if (preg_match("/^[A-Z]/", $shortcode) && $this->accessLevel > 1)
$this->error("this API key can not create shortcodes starting with an uppercase letter.", ErrorCode::PERMISSION_ERROR);
try {
$user = isset($this->keys->keys->{$this->request->apikey}->user) ? $this->keys->keys->{$this->request->apikey}->user :
$this->request->apikey;
$this->shortener->add($this->request->request->url, $shortcode, $user);
} catch (ShortcodeAlreadyExistsException $exception) {
$this->error("this shortcode has already been taken.", ErrorCode::SHORTCODE_ALREADY_EXISTS);
}
$response = new \stdClass();
$response->shortcode = $shortcode;
$response->url = $this->request->request->url;
$this->success($response);
}
private function route_get() {
if (!isset($this->request->request->url) && !isset($this->request->request->shortcode))
$this->error("a malformed request has been sent. Too few parameters.", ErrorCode::MALFORMED_REQUEST);
if (isset($this->request->request->url) && isset($this->request->request->shortcode))
$this->error("a malformed request has been sent. Too many parameters", ErrorCode::MALFORMED_REQUEST);
$shortcode = isset( $this->request->request->shortcode) ? $this->request->request->shortcode : null;
$url = isset( $this->request->request->url) ? $this->request->request->url : null;
$out = $this->shortener->get($url, $shortcode);
if ($out === null)
$this->error("the requested URL/shortcode does not exist", ErrorCode::SHORTCODE_NOT_EXISTS);
$response = new \stdClass();
if ($url !== null)
$response->shortcode = $out;
elseif($shortcode !== null)
$response->url = $out;
$this->success($response);
}
private function route_help() {
header("Content-Type: application/json");
echo file_get_contents(dirname(__FILE__)."/../help.txt");
}
private function error($msg, $code = ErrorCode::ERROR) {
$obj = new \stdClass();
$obj->status = "ERROR";
$obj->code = $code;
$response = new \stdClass();
$response->msg = $msg;
$obj->response = $response;
$this->respond($obj);
}
function success($response) {
$obj = new \stdClass();
$obj->status = "OK";
$obj->code = 0;
$obj->response = $response;
$this->respond($obj);
}
private function respond($response) {
header("Content-Type: application/json");
echo json_encode($response, JSON_PRETTY_PRINT);
exit;
}
}
\ No newline at end of file
......@@ -6,13 +6,15 @@ class PostHandler {
private $root;
private $db;
private $settings;
private $shortener;
public function __construct(URLShortener $root) {
$this->root = $root;
if (!$root->authenticator->authenticated())
return;
$this->db = DatabaseHandler::Instance();
$this->settings = SettingsHandler::Instance();
$this->db = DatabaseHandler::Instance();
$this->settings = SettingsHandler::Instance();
$this->shortener = new Shortener();
$this->add();
}
......@@ -26,8 +28,7 @@ class PostHandler {
exit;
}
$shortcode = $_POST["shorturl"] == "" ? $this->generateShortcode() : $_POST["shorturl"];
$shortcode = $_POST["shorturl"] == "" ? $this->shortener->generateShortcode($this->settings->system->shortcodeLength) : $_POST["shorturl"];
$minLen = $this->settings->system->shortcodeMinLength;
if (strlen($shortcode) < $minLen) {
new Alert("Der Shortcode muss mindestens <b>$minLen</b> Zeichen lang sein.", AlertLevel::ERROR);
......@@ -40,37 +41,16 @@ class PostHandler {
exit;
}
$query = $this->db->prepare("SELECT shortcode FROM urls WHERE shortcode = ?");
$query->bind_param("s", $shortcode);
$query->execute();
$query->store_result();
if ($query->num_rows > 0) {
try {
$this->shortener->add($_POST["url"], $shortcode, $this->root->authenticator->user());
} catch (ShortcodeAlreadyExistsException $exception) {
new Alert("Der Shortcode <code>$shortcode</code> ist leider bereits vergeben.", AlertLevel::WARNING);
header("Location: ./");
exit;
}
$query = $this->db->prepare("INSERT INTO urls (url, shortcode, rft) VALUES(?, ?, ?)");
$query->bind_param(
"sss",
$_POST["url"],
$shortcode,
$this->root->authenticator->user()
);
$query->execute();
new Alert("Dein Link wurde hinzugefügt.", AlertLevel::OK, "");
header("Location: ./");
}
private function generateShortcode() {
$code = substr(hash("md5", uniqid('', true)), 0, $this->settings->system->shortcodeLength);
$query = $this->db->query("SELECT COUNT(*) as amount FROM urls WHERE shortcode = '$code'");
$result = $query->fetch_object();
if ($result->amount == 0)
return $code;
else
return $this->generateShortcode();
}
}
\ No newline at end of file
<?php
namespace FSI;
class ShortcodeAlreadyExistsException extends \Exception {
}
\ No newline at end of file
<?php
namespace FSI;
class Shortener {
private $db;
public function __construct() {
$this->db = \FSI\DatabaseHandler::Instance();
}
/**
* @param $url
* @param $shortcode
* @param $user
*
* @throws ShortcodeAlreadyExistsException
*/
public function add($url, $shortcode, $user) {
if ($this->shortcodeExists($shortcode)) {
throw new ShortcodeAlreadyExistsException();
}
$query = $this->db->prepare("INSERT INTO urls (url, shortcode, rft) VALUES(?, ?, ?)");
$query->bind_param(
"sss",
$url,
$shortcode,
$user
);
$query->execute();
}
public function get($url = null, $shortcode = null) {
$out = null;
if ($url === null) {
$query = $this->db->prepare("SELECT url FROM urls WHERE shortcode = ? LIMIT 1");
$query->bind_param("s", $shortcode);
$query->execute();
$query->bind_result($out);
$query->fetch();
}
else if ($shortcode == null) {
$query = $this->db->prepare("SELECT shortcode FROM urls WHERE url = ? LIMIT 1");
$query->bind_param("s", $url);
$query->execute();
$query->bind_result($out);
$query->fetch();
}
else {
return $out = null;
}
return $out;
}
private function shortcodeExists($shortcode) {
$query = $this->db->prepare("SELECT shortcode FROM urls WHERE shortcode = ?");
$query->bind_param("s", $shortcode);
$query->execute();
$query->store_result();
return $query->num_rows > 0;
}
public function generateShortcode($length = 6) {
$code = substr(hash("md5", uniqid('', true)), 0, $length);
$query = $this->db->query("SELECT COUNT(*) as amount FROM urls WHERE shortcode = '$code'");
$result = $query->fetch_object();
if ($result->amount == 0)
return $code;
else
return $this->generateShortcode();
}
}
\ No newline at end of file
......@@ -9,8 +9,13 @@ class URLShortener {
public function __construct() {
spl_autoload_register([$this, "autoload"]);
if (isset($_GET["shortcode"]))
if (isset($_GET["shortcode"])) {
if ($_GET["shortcode"] == "api") {
new API();
return;
}
new Redirector();
}
$this->authenticator = new Authenticator($this);
if (isset($_GET["auth"]))
$this->authenticator->sendRequest();
......
# API documentation
Any API request has to be sent via `HTTP PUT` and has to fullfill the following
format:
{
"apikey" : "YOUR_API_KEY"
"request" : {
// [PARAMS] (dependent on endpoint)
}
}
## Endpoints
/new add a new URL <-> shortcode combination
/get get the corresponding URL/shortcode to the request
/help shows this help
### new
Add a new URL <-> shortcode combination. If the parameter `shortcode` is
omitted, a random code is assigned to the URL.
**Example:**
HTTP/1.1 PUT https://fsi.rocks/api/new
{
"apikey" : "YOUR_API_KEY"
"request" : {
"url" : "http(s)://url-to.shorten"
"shortcode" : "your-shortcode" //optional
}
}
### get
Get either the URL or the shortcode of an existing entry, depending on which
information is supplied in the request.
**Example:**
HTTP/1.1 PUT https://fsi.rocks/api/get
{
"apikey" : "YOUR_API_KEY"
"request" : {
"url" : "http(s)://url-for.shortcode" // optional
"shortcode" : "shortcode-for-url" // optional
}
}
submitting both "url" and "shortcode" or neither of them will result in an
error.
## Example
A simple cURL example:
curl -X PUT -H "Content-Type: application/json" -d \
'{"apikey" : "YOUR_API_KEY", "request" : { "shortcode": "test"}}' \
"https://fsi.rocks/api/get"
**Response:**
{
"status": "OK",
"code": 0,
"response": {
"url": "https://fsi.rocks"
}
}
## Error codes
ERROR = 500;
MALFORMED_REQUEST = 501;
MALFORMED_URL = 502;
AUTHENTICATION_FAILED = 510;
PERMISSION_ERROR = 511;
INVALID_SHORTCODE_FORMAT = 520;
SHORTCODE_ALREADY_EXISTS = 521;
SHORTCODE_NOT_EXISTS = 522;
UNKNOWN_ENDPOINT = 555;
......@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width" />
<meta charset="utf-8" />
<title>URL-shortener – fsi.rocks</title>
<link rel="icon" href="images/favicon.png" sizes="128x128" />
</head>
<body>
<a class="github-fork-ribbon" href="https://gitlab.fsi.hochschule-trier.de/fachschaftsrat/url-shortener" title="open beta">open beta</a>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment