diff options
Diffstat (limited to 'application')
-rw-r--r-- | application/LinkDB.php | 4 | ||||
-rw-r--r-- | application/Utils.php | 98 | ||||
-rw-r--r-- | application/api/ApiUtils.php | 35 | ||||
-rw-r--r-- | application/api/controllers/ApiController.php | 10 | ||||
-rw-r--r-- | application/api/controllers/Links.php | 44 |
5 files changed, 183 insertions, 8 deletions
diff --git a/application/LinkDB.php b/application/LinkDB.php index 4cee2af9..1e4d7ce8 100644 --- a/application/LinkDB.php +++ b/application/LinkDB.php | |||
@@ -144,10 +144,10 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
144 | if (!isset($value['id']) || empty($value['url'])) { | 144 | if (!isset($value['id']) || empty($value['url'])) { |
145 | die('Internal Error: A link should always have an id and URL.'); | 145 | die('Internal Error: A link should always have an id and URL.'); |
146 | } | 146 | } |
147 | if ((! empty($offset) && ! is_int($offset)) || ! is_int($value['id'])) { | 147 | if (($offset !== null && ! is_int($offset)) || ! is_int($value['id'])) { |
148 | die('You must specify an integer as a key.'); | 148 | die('You must specify an integer as a key.'); |
149 | } | 149 | } |
150 | if (! empty($offset) && $offset !== $value['id']) { | 150 | if ($offset !== null && $offset !== $value['id']) { |
151 | die('Array offset and link ID must be equal.'); | 151 | die('Array offset and link ID must be equal.'); |
152 | } | 152 | } |
153 | 153 | ||
diff --git a/application/Utils.php b/application/Utils.php index 5c077450..ab463af9 100644 --- a/application/Utils.php +++ b/application/Utils.php | |||
@@ -321,25 +321,117 @@ function normalize_spaces($string) | |||
321 | * otherwise default format '%c' will be returned. | 321 | * otherwise default format '%c' will be returned. |
322 | * | 322 | * |
323 | * @param DateTime $date to format. | 323 | * @param DateTime $date to format. |
324 | * @param bool $time Displays time if true. | ||
324 | * @param bool $intl Use international format if true. | 325 | * @param bool $intl Use international format if true. |
325 | * | 326 | * |
326 | * @return bool|string Formatted date, or false if the input is invalid. | 327 | * @return bool|string Formatted date, or false if the input is invalid. |
327 | */ | 328 | */ |
328 | function format_date($date, $intl = true) | 329 | function format_date($date, $time = true, $intl = true) |
329 | { | 330 | { |
330 | if (! $date instanceof DateTime) { | 331 | if (! $date instanceof DateTime) { |
331 | return false; | 332 | return false; |
332 | } | 333 | } |
333 | 334 | ||
334 | if (! $intl || ! class_exists('IntlDateFormatter')) { | 335 | if (! $intl || ! class_exists('IntlDateFormatter')) { |
335 | return strftime('%c', $date->getTimestamp()); | 336 | $format = $time ? '%c' : '%x'; |
337 | return strftime($format, $date->getTimestamp()); | ||
336 | } | 338 | } |
337 | 339 | ||
338 | $formatter = new IntlDateFormatter( | 340 | $formatter = new IntlDateFormatter( |
339 | setlocale(LC_TIME, 0), | 341 | setlocale(LC_TIME, 0), |
340 | IntlDateFormatter::LONG, | 342 | IntlDateFormatter::LONG, |
341 | IntlDateFormatter::LONG | 343 | $time ? IntlDateFormatter::LONG : IntlDateFormatter::NONE |
342 | ); | 344 | ); |
343 | 345 | ||
344 | return $formatter->format($date); | 346 | return $formatter->format($date); |
345 | } | 347 | } |
348 | |||
349 | /** | ||
350 | * Check if the input is an integer, no matter its real type. | ||
351 | * | ||
352 | * PHP is a bit messy regarding this: | ||
353 | * - is_int returns false if the input is a string | ||
354 | * - ctype_digit returns false if the input is an integer or negative | ||
355 | * | ||
356 | * @param mixed $input value | ||
357 | * | ||
358 | * @return bool true if the input is an integer, false otherwise | ||
359 | */ | ||
360 | function is_integer_mixed($input) | ||
361 | { | ||
362 | if (is_array($input) || is_bool($input) || is_object($input)) { | ||
363 | return false; | ||
364 | } | ||
365 | $input = strval($input); | ||
366 | return ctype_digit($input) || (startsWith($input, '-') && ctype_digit(substr($input, 1))); | ||
367 | } | ||
368 | |||
369 | /** | ||
370 | * Convert post_max_size/upload_max_filesize (e.g. '16M') parameters to bytes. | ||
371 | * | ||
372 | * @param string $val Size expressed in string. | ||
373 | * | ||
374 | * @return int Size expressed in bytes. | ||
375 | */ | ||
376 | function return_bytes($val) | ||
377 | { | ||
378 | if (is_integer_mixed($val) || $val === '0' || empty($val)) { | ||
379 | return $val; | ||
380 | } | ||
381 | $val = trim($val); | ||
382 | $last = strtolower($val[strlen($val)-1]); | ||
383 | $val = intval(substr($val, 0, -1)); | ||
384 | switch($last) { | ||
385 | case 'g': $val *= 1024; | ||
386 | case 'm': $val *= 1024; | ||
387 | case 'k': $val *= 1024; | ||
388 | } | ||
389 | return $val; | ||
390 | } | ||
391 | |||
392 | /** | ||
393 | * Return a human readable size from bytes. | ||
394 | * | ||
395 | * @param int $bytes value | ||
396 | * | ||
397 | * @return string Human readable size | ||
398 | */ | ||
399 | function human_bytes($bytes) | ||
400 | { | ||
401 | if ($bytes === '') { | ||
402 | return t('Setting not set'); | ||
403 | } | ||
404 | if (! is_integer_mixed($bytes)) { | ||
405 | return $bytes; | ||
406 | } | ||
407 | $bytes = intval($bytes); | ||
408 | if ($bytes === 0) { | ||
409 | return t('Unlimited'); | ||
410 | } | ||
411 | |||
412 | $units = [t('B'), t('kiB'), t('MiB'), t('GiB')]; | ||
413 | for ($i = 0; $i < count($units) && $bytes >= 1024; ++$i) { | ||
414 | $bytes /= 1024; | ||
415 | } | ||
416 | |||
417 | return round($bytes) . $units[$i]; | ||
418 | } | ||
419 | |||
420 | /** | ||
421 | * Try to determine max file size for uploads (POST). | ||
422 | * Returns an integer (in bytes) or formatted depending on $format. | ||
423 | * | ||
424 | * @param mixed $limitPost post_max_size PHP setting | ||
425 | * @param mixed $limitUpload upload_max_filesize PHP setting | ||
426 | * @param bool $format Format max upload size to human readable size | ||
427 | * | ||
428 | * @return int|string max upload file size | ||
429 | */ | ||
430 | function get_max_upload_size($limitPost, $limitUpload, $format = true) | ||
431 | { | ||
432 | $size1 = return_bytes($limitPost); | ||
433 | $size2 = return_bytes($limitUpload); | ||
434 | // Return the smaller of two: | ||
435 | $maxsize = min($size1, $size2); | ||
436 | return $format ? human_bytes($maxsize) : $maxsize; | ||
437 | } | ||
diff --git a/application/api/ApiUtils.php b/application/api/ApiUtils.php index d4015865..b8155a34 100644 --- a/application/api/ApiUtils.php +++ b/application/api/ApiUtils.php | |||
@@ -12,7 +12,7 @@ class ApiUtils | |||
12 | /** | 12 | /** |
13 | * Validates a JWT token authenticity. | 13 | * Validates a JWT token authenticity. |
14 | * | 14 | * |
15 | * @param string $token JWT token extracted from the headers. | 15 | * @param string $token JWT token extracted from the headers. |
16 | * @param string $secret API secret set in the settings. | 16 | * @param string $secret API secret set in the settings. |
17 | * | 17 | * |
18 | * @throws ApiAuthorizationException the token is not valid. | 18 | * @throws ApiAuthorizationException the token is not valid. |
@@ -50,7 +50,7 @@ class ApiUtils | |||
50 | /** | 50 | /** |
51 | * Format a Link for the REST API. | 51 | * Format a Link for the REST API. |
52 | * | 52 | * |
53 | * @param array $link Link data read from the datastore. | 53 | * @param array $link Link data read from the datastore. |
54 | * @param string $indexUrl Shaarli's index URL (used for relative URL). | 54 | * @param string $indexUrl Shaarli's index URL (used for relative URL). |
55 | * | 55 | * |
56 | * @return array Link data formatted for the REST API. | 56 | * @return array Link data formatted for the REST API. |
@@ -77,4 +77,35 @@ class ApiUtils | |||
77 | } | 77 | } |
78 | return $out; | 78 | return $out; |
79 | } | 79 | } |
80 | |||
81 | /** | ||
82 | * Convert a link given through a request, to a valid link for LinkDB. | ||
83 | * | ||
84 | * If no URL is provided, it will generate a local note URL. | ||
85 | * If no title is provided, it will use the URL as title. | ||
86 | * | ||
87 | * @param array $input Request Link. | ||
88 | * @param bool $defaultPrivate Request Link. | ||
89 | * | ||
90 | * @return array Formatted link. | ||
91 | */ | ||
92 | public static function buildLinkFromRequest($input, $defaultPrivate) | ||
93 | { | ||
94 | $input['url'] = ! empty($input['url']) ? cleanup_url($input['url']) : ''; | ||
95 | if (isset($input['private'])) { | ||
96 | $private = filter_var($input['private'], FILTER_VALIDATE_BOOLEAN); | ||
97 | } else { | ||
98 | $private = $defaultPrivate; | ||
99 | } | ||
100 | |||
101 | $link = [ | ||
102 | 'title' => ! empty($input['title']) ? $input['title'] : $input['url'], | ||
103 | 'url' => $input['url'], | ||
104 | 'description' => ! empty($input['description']) ? $input['description'] : '', | ||
105 | 'tags' => ! empty($input['tags']) ? implode(' ', $input['tags']) : '', | ||
106 | 'private' => $private, | ||
107 | 'created' => new \DateTime(), | ||
108 | ]; | ||
109 | return $link; | ||
110 | } | ||
80 | } | 111 | } |
diff --git a/application/api/controllers/ApiController.php b/application/api/controllers/ApiController.php index 1dd47f17..f35b923a 100644 --- a/application/api/controllers/ApiController.php +++ b/application/api/controllers/ApiController.php | |||
@@ -51,4 +51,14 @@ abstract class ApiController | |||
51 | $this->jsonStyle = null; | 51 | $this->jsonStyle = null; |
52 | } | 52 | } |
53 | } | 53 | } |
54 | |||
55 | /** | ||
56 | * Get the container. | ||
57 | * | ||
58 | * @return Container | ||
59 | */ | ||
60 | public function getCi() | ||
61 | { | ||
62 | return $this->ci; | ||
63 | } | ||
54 | } | 64 | } |
diff --git a/application/api/controllers/Links.php b/application/api/controllers/Links.php index d4f1a09c..0db10fd0 100644 --- a/application/api/controllers/Links.php +++ b/application/api/controllers/Links.php | |||
@@ -97,11 +97,53 @@ class Links extends ApiController | |||
97 | */ | 97 | */ |
98 | public function getLink($request, $response, $args) | 98 | public function getLink($request, $response, $args) |
99 | { | 99 | { |
100 | if (! isset($this->linkDb[$args['id']])) { | 100 | if (!isset($this->linkDb[$args['id']])) { |
101 | throw new ApiLinkNotFoundException(); | 101 | throw new ApiLinkNotFoundException(); |
102 | } | 102 | } |
103 | $index = index_url($this->ci['environment']); | 103 | $index = index_url($this->ci['environment']); |
104 | $out = ApiUtils::formatLink($this->linkDb[$args['id']], $index); | 104 | $out = ApiUtils::formatLink($this->linkDb[$args['id']], $index); |
105 | |||
105 | return $response->withJson($out, 200, $this->jsonStyle); | 106 | return $response->withJson($out, 200, $this->jsonStyle); |
106 | } | 107 | } |
108 | |||
109 | /** | ||
110 | * Creates a new link from posted request body. | ||
111 | * | ||
112 | * @param Request $request Slim request. | ||
113 | * @param Response $response Slim response. | ||
114 | * | ||
115 | * @return Response response. | ||
116 | */ | ||
117 | public function postLink($request, $response) | ||
118 | { | ||
119 | $data = $request->getParsedBody(); | ||
120 | $link = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links')); | ||
121 | // duplicate by URL, return 409 Conflict | ||
122 | if (! empty($link['url']) && ! empty($dup = $this->linkDb->getLinkFromUrl($link['url']))) { | ||
123 | return $response->withJson( | ||
124 | ApiUtils::formatLink($dup, index_url($this->ci['environment'])), | ||
125 | 409, | ||
126 | $this->jsonStyle | ||
127 | ); | ||
128 | } | ||
129 | |||
130 | $link['id'] = $this->linkDb->getNextId(); | ||
131 | $link['shorturl'] = link_small_hash($link['created'], $link['id']); | ||
132 | |||
133 | // note: general relative URL | ||
134 | if (empty($link['url'])) { | ||
135 | $link['url'] = '?' . $link['shorturl']; | ||
136 | } | ||
137 | |||
138 | if (empty($link['title'])) { | ||
139 | $link['title'] = $link['url']; | ||
140 | } | ||
141 | |||
142 | $this->linkDb[$link['id']] = $link; | ||
143 | $this->linkDb->save($this->conf->get('resource.page_cache')); | ||
144 | $out = ApiUtils::formatLink($link, index_url($this->ci['environment'])); | ||
145 | $redirect = $this->ci->router->relativePathFor('getLink', ['id' => $link['id']]); | ||
146 | return $response->withAddedHeader('Location', $redirect) | ||
147 | ->withJson($out, 201, $this->jsonStyle); | ||
148 | } | ||
107 | } | 149 | } |