Merge pull request #10329 from annando/unified-request

API: Unified request parameter handling
This commit is contained in:
Hypolite Petovan 2021-05-29 10:53:16 -04:00 committed by GitHub
commit b621d2c714
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 54 additions and 80 deletions

View file

@ -122,7 +122,6 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/en
These emdpoints are planned to be implemented somewhere in the future. These emdpoints are planned to be implemented somewhere in the future.
- [`PATCH /api/v1/accounts/update_credentials`](https://docs.joinmastodon.org/methods/accounts/) - [`PATCH /api/v1/accounts/update_credentials`](https://docs.joinmastodon.org/methods/accounts/)
- [`GET /api/v1/instance/activity`](https://docs.joinmastodon.org/methods/instance#weekly-activity)
## Dummy endpoints ## Dummy endpoints
@ -139,7 +138,7 @@ They refer to features that don't exist in Friendica yet.
## Non supportable endpoints ## Non supportable endpoints
These endpoints won't be implemented at the moment. These endpoints won't be implemented at the moment.
They refer to features that don't exist in Friendica yet. They refer to features or data that don't exist in Friendica yet.
- [`POST /api/v1/accounts`](https://docs.joinmastodon.org/methods/accounts/) - [`POST /api/v1/accounts`](https://docs.joinmastodon.org/methods/accounts/)
- [`POST /api/v1/accounts/:id/pin`](https://docs.joinmastodon.org/methods/accounts/) - [`POST /api/v1/accounts/:id/pin`](https://docs.joinmastodon.org/methods/accounts/)
@ -164,6 +163,7 @@ They refer to features that don't exist in Friendica yet.
- [`POST /api/v1/filters/:id`](https://docs.joinmastodon.org/methods/accounts/filters/) - [`POST /api/v1/filters/:id`](https://docs.joinmastodon.org/methods/accounts/filters/)
- [`PUT /api/v1/filters/:id`](https://docs.joinmastodon.org/methods/accounts/filters/) - [`PUT /api/v1/filters/:id`](https://docs.joinmastodon.org/methods/accounts/filters/)
- [`DELETE /api/v1/filters/:id`](https://docs.joinmastodon.org/methods/accounts/filters/) - [`DELETE /api/v1/filters/:id`](https://docs.joinmastodon.org/methods/accounts/filters/)
- [`GET /api/v1/instance/activity`](https://docs.joinmastodon.org/methods/instance#weekly-activity)
- [`POST /api/v1/markers`](https://docs.joinmastodon.org/methods/timelines/markers/) - [`POST /api/v1/markers`](https://docs.joinmastodon.org/methods/timelines/markers/)
- [`GET /api/v1/polls/:id`](https://docs.joinmastodon.org/methods/statuses/polls/) - [`GET /api/v1/polls/:id`](https://docs.joinmastodon.org/methods/statuses/polls/)
- [`POST /api/v1/polls/:id/votes`](https://docs.joinmastodon.org/methods/statuses/polls/) - [`POST /api/v1/polls/:id/votes`](https://docs.joinmastodon.org/methods/statuses/polls/)

View file

@ -78,13 +78,16 @@ class Lists extends BaseApi
public static function put(array $parameters = []) public static function put(array $parameters = [])
{ {
$data = self::getPutData(); $request = self::getRequest([
'title' => '', // The title of the list to be updated.
'replies_policy' => '', // One of: "followed", "list", or "none".
]);
if (empty($data['title']) || empty($parameters['id'])) { if (empty($request['title']) || empty($parameters['id'])) {
DI::mstdnError()->UnprocessableEntity(); DI::mstdnError()->UnprocessableEntity();
} }
Group::update($parameters['id'], $data['title']); Group::update($parameters['id'], $request['title']);
} }
/** /**

View file

@ -58,7 +58,12 @@ class Media extends BaseApi
self::login(self::SCOPE_WRITE); self::login(self::SCOPE_WRITE);
$uid = self::getCurrentUserID(); $uid = self::getCurrentUserID();
$data = self::getPutData(); $request = self::getRequest([
'file' => [], // The file to be attached, using multipart form data.
'thumbnail' => [], // The custom thumbnail of the media to be attached, using multipart form data.
'description' => '', // A plain-text description of the media, for accessibility purposes.
'focus' => '', // Two floating points (x,y), comma-delimited ranging from -1.0 to 1.0
]);
if (empty($parameters['id'])) { if (empty($parameters['id'])) {
DI::mstdnError()->UnprocessableEntity(); DI::mstdnError()->UnprocessableEntity();
@ -69,7 +74,7 @@ class Media extends BaseApi
DI::mstdnError()->RecordNotFound(); DI::mstdnError()->RecordNotFound();
} }
Photo::update(['desc' => $data['description'] ?? ''], ['resource-id' => $photo['resource-id']]); Photo::update(['desc' => $request['description']], ['resource-id' => $photo['resource-id']]);
System::jsonExit(DI::mstdnAttachment()->createFromPhoto($parameters['id'])); System::jsonExit(DI::mstdnAttachment()->createFromPhoto($parameters['id']));
} }

View file

@ -46,21 +46,22 @@ class Statuses extends BaseApi
self::login(self::SCOPE_WRITE); self::login(self::SCOPE_WRITE);
$uid = self::getCurrentUserID(); $uid = self::getCurrentUserID();
$data = self::getJsonPostData(); $request = self::getRequest([
'status' => '', // Text content of the status. If media_ids is provided, this becomes optional. Attaching a poll is optional while status is provided.
$status = $data['status'] ?? ''; 'media_ids' => [], // Array of Attachment ids to be attached as media. If provided, status becomes optional, and poll cannot be used.
$media_ids = $data['media_ids'] ?? []; 'poll' => [], // Poll data. If provided, media_ids cannot be used, and poll[expires_in] must be provided.
$in_reply_to_id = $data['in_reply_to_id'] ?? 0; 'in_reply_to_id' => 0, // ID of the status being replied to, if status is a reply
$sensitive = $data['sensitive'] ?? false; // @todo Possibly trigger "nsfw" flag? 'sensitive' => false, // Mark status and attached media as sensitive?
$spoiler_text = $data['spoiler_text'] ?? ''; 'spoiler_text' => '', // Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field.
$visibility = $data['visibility'] ?? ''; 'visibility' => '', // Visibility of the posted status. One of: "public", "unlisted", "private" or "direct".
$scheduled_at = $data['scheduled_at'] ?? ''; // Currently unsupported, but maybe in the future 'scheduled_at' => '', // ISO 8601 Datetime at which to schedule a status. Providing this paramter will cause ScheduledStatus to be returned instead of Status. Must be at least 5 minutes in the future.
$language = $data['language'] ?? ''; 'language' => '', // ISO 639 language code for this status.
]);
$owner = User::getOwnerDataById($uid); $owner = User::getOwnerDataById($uid);
// The imput is defined as text. So we can use Markdown for some enhancements // The imput is defined as text. So we can use Markdown for some enhancements
$body = Markdown::toBBCode($status); $body = Markdown::toBBCode($request['status']);
$body = BBCode::expandTags($body); $body = BBCode::expandTags($body);
@ -69,7 +70,7 @@ class Statuses extends BaseApi
$item['verb'] = Activity::POST; $item['verb'] = Activity::POST;
$item['contact-id'] = $owner['id']; $item['contact-id'] = $owner['id'];
$item['author-id'] = $item['owner-id'] = Contact::getPublicIdByUserId($uid); $item['author-id'] = $item['owner-id'] = Contact::getPublicIdByUserId($uid);
$item['title'] = $spoiler_text; $item['title'] = $request['spoiler_text'];
$item['body'] = $body; $item['body'] = $body;
if (!empty(self::getCurrentApplication()['name'])) { if (!empty(self::getCurrentApplication()['name'])) {
@ -80,7 +81,7 @@ class Statuses extends BaseApi
$item['app'] = 'API'; $item['app'] = 'API';
} }
switch ($visibility) { switch ($request['visibility']) {
case 'public': case 'public':
$item['allow_cid'] = ''; $item['allow_cid'] = '';
$item['allow_gid'] = ''; $item['allow_gid'] = '';
@ -112,7 +113,7 @@ class Statuses extends BaseApi
case 'direct': case 'direct':
// Direct messages are currently unsupported // Direct messages are currently unsupported
DI::mstdnError()->InternalError('Direct messages are currently unsupported'); DI::mstdnError()->InternalError('Direct messages are currently unsupported');
break; break;
default: default:
$item['allow_cid'] = $owner['allow_cid']; $item['allow_cid'] = $owner['allow_cid'];
$item['allow_gid'] = $owner['allow_gid']; $item['allow_gid'] = $owner['allow_gid'];
@ -129,12 +130,12 @@ class Statuses extends BaseApi
break; break;
} }
if (!empty($language)) { if (!empty($request['language'])) {
$item['language'] = json_encode([$language => 1]); $item['language'] = json_encode([$request['language'] => 1]);
} }
if ($in_reply_to_id) { if ($request['in_reply_to_id']) {
$parent = Post::selectFirst(['uri'], ['uri-id' => $in_reply_to_id, 'uid' => [0, $uid]]); $parent = Post::selectFirst(['uri'], ['uri-id' => $request['in_reply_to_id'], 'uid' => [0, $uid]]);
$item['thr-parent'] = $parent['uri']; $item['thr-parent'] = $parent['uri'];
$item['gravity'] = GRAVITY_COMMENT; $item['gravity'] = GRAVITY_COMMENT;
$item['object-type'] = Activity\ObjectType::COMMENT; $item['object-type'] = Activity\ObjectType::COMMENT;
@ -143,16 +144,16 @@ class Statuses extends BaseApi
$item['object-type'] = Activity\ObjectType::NOTE; $item['object-type'] = Activity\ObjectType::NOTE;
} }
if (!empty($media_ids)) { if (!empty($request['media_ids'])) {
$item['object-type'] = Activity\ObjectType::IMAGE; $item['object-type'] = Activity\ObjectType::IMAGE;
$item['post-type'] = Item::PT_IMAGE; $item['post-type'] = Item::PT_IMAGE;
$item['attachments'] = []; $item['attachments'] = [];
foreach ($media_ids as $id) { foreach ($request['media_ids'] as $id) {
$media = DBA::toArray(DBA::p("SELECT `resource-id`, `scale`, `type`, `desc`, `filename`, `datasize`, `width`, `height` FROM `photo` $media = DBA::toArray(DBA::p("SELECT `resource-id`, `scale`, `type`, `desc`, `filename`, `datasize`, `width`, `height` FROM `photo`
WHERE `resource-id` IN (SELECT `resource-id` FROM `photo` WHERE `id` = ?) AND `photo`.`uid` = ? WHERE `resource-id` IN (SELECT `resource-id` FROM `photo` WHERE `id` = ?) AND `photo`.`uid` = ?
ORDER BY `photo`.`width` DESC LIMIT 2", $id, $uid)); ORDER BY `photo`.`width` DESC LIMIT 2", $id, $uid));
if (empty($media)) { if (empty($media)) {
continue; continue;
} }
@ -162,7 +163,7 @@ class Statuses extends BaseApi
$ressources[] = $media[0]['resource-id']; $ressources[] = $media[0]['resource-id'];
$phototypes = Images::supportedTypes(); $phototypes = Images::supportedTypes();
$ext = $phototypes[$media[0]['type']]; $ext = $phototypes[$media[0]['type']];
$attachment = ['type' => Post\Media::IMAGE, 'mimetype' => $media[0]['type'], $attachment = ['type' => Post\Media::IMAGE, 'mimetype' => $media[0]['type'],
'url' => DI::baseUrl() . '/photo/' . $media[0]['resource-id'] . '-' . $media[0]['scale'] . '.' . $ext, 'url' => DI::baseUrl() . '/photo/' . $media[0]['resource-id'] . '-' . $media[0]['scale'] . '.' . $ext,
'size' => $media[0]['datasize'], 'size' => $media[0]['datasize'],
@ -170,7 +171,7 @@ class Statuses extends BaseApi
'description' => $media[0]['desc'] ?? '', 'description' => $media[0]['desc'] ?? '',
'width' => $media[0]['width'], 'width' => $media[0]['width'],
'height' => $media[0]['height']]; 'height' => $media[0]['height']];
if (count($media) > 1) { if (count($media) > 1) {
$attachment['preview'] = DI::baseUrl() . '/photo/' . $media[1]['resource-id'] . '-' . $media[1]['scale'] . '.' . $ext; $attachment['preview'] = DI::baseUrl() . '/photo/' . $media[1]['resource-id'] . '-' . $media[1]['scale'] . '.' . $ext;
$attachment['preview-width'] = $media[1]['width']; $attachment['preview-width'] = $media[1]['width'];
@ -184,7 +185,7 @@ class Statuses extends BaseApi
if (!empty($id)) { if (!empty($id)) {
$item = Post::selectFirst(['uri-id'], ['id' => $id]); $item = Post::selectFirst(['uri-id'], ['id' => $id]);
if (!empty($item['uri-id'])) { if (!empty($item['uri-id'])) {
System::jsonExit(DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid)); System::jsonExit(DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid));
} }
} }

View file

@ -29,7 +29,7 @@ use Friendica\Database\DBA;
use Friendica\DI; use Friendica\DI;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Friendica\Util\Network; use Friendica\Util\HTTPInputData;
require_once __DIR__ . '/../../include/api.php'; require_once __DIR__ . '/../../include/api.php';
@ -129,7 +129,7 @@ class BaseApi extends BaseModule
public static function unsupported(string $method = 'all') public static function unsupported(string $method = 'all')
{ {
$path = DI::args()->getQueryString(); $path = DI::args()->getQueryString();
Logger::info('Unimplemented API call', ['method' => $method, 'path' => $path, 'agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'request' => $_REQUEST ?? []]); Logger::info('Unimplemented API call', ['method' => $method, 'path' => $path, 'agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'request' => HTTPInputData::process()]);
$error = DI::l10n()->t('API endpoint %s %s is not implemented', strtoupper($method), $path); $error = DI::l10n()->t('API endpoint %s %s is not implemented', strtoupper($method), $path);
$error_description = DI::l10n()->t('The API endpoint is currently not implemented but might be in the future.'); $error_description = DI::l10n()->t('The API endpoint is currently not implemented but might be in the future.');
$errorobj = new \Friendica\Object\Api\Mastodon\Error($error, $error_description); $errorobj = new \Friendica\Object\Api\Mastodon\Error($error, $error_description);
@ -141,26 +141,30 @@ class BaseApi extends BaseModule
* *
* @return array request data * @return array request data
*/ */
public static function getRequest(array $defaults) { public static function getRequest(array $defaults)
{
$httpinput = HTTPInputData::process();
$input = array_merge($httpinput['variables'], $httpinput['files'], $_REQUEST);
$request = []; $request = [];
foreach ($defaults as $parameter => $defaultvalue) { foreach ($defaults as $parameter => $defaultvalue) {
if (is_string($defaultvalue)) { if (is_string($defaultvalue)) {
$request[$parameter] = $_REQUEST[$parameter] ?? $defaultvalue; $request[$parameter] = $input[$parameter] ?? $defaultvalue;
} elseif (is_int($defaultvalue)) { } elseif (is_int($defaultvalue)) {
$request[$parameter] = (int)($_REQUEST[$parameter] ?? $defaultvalue); $request[$parameter] = (int)($input[$parameter] ?? $defaultvalue);
} elseif (is_float($defaultvalue)) { } elseif (is_float($defaultvalue)) {
$request[$parameter] = (float)($_REQUEST[$parameter] ?? $defaultvalue); $request[$parameter] = (float)($input[$parameter] ?? $defaultvalue);
} elseif (is_array($defaultvalue)) { } elseif (is_array($defaultvalue)) {
$request[$parameter] = $_REQUEST[$parameter] ?? []; $request[$parameter] = $input[$parameter] ?? [];
} elseif (is_bool($defaultvalue)) { } elseif (is_bool($defaultvalue)) {
$request[$parameter] = in_array(strtolower($_REQUEST[$parameter] ?? ''), ['true', '1']); $request[$parameter] = in_array(strtolower($input[$parameter] ?? ''), ['true', '1']);
} else { } else {
Logger::notice('Unhandled default value type', ['parameter' => $parameter, 'type' => gettype($defaultvalue)]); Logger::notice('Unhandled default value type', ['parameter' => $parameter, 'type' => gettype($defaultvalue)]);
} }
} }
foreach ($_REQUEST ?? [] as $parameter => $value) { foreach ($input ?? [] as $parameter => $value) {
if ($parameter == 'pagename') { if ($parameter == 'pagename') {
continue; continue;
} }
@ -173,45 +177,6 @@ class BaseApi extends BaseModule
return $request; return $request;
} }
/**
* Get post data that is transmitted as JSON
*
* @return array request data
*/
public static function getJsonPostData()
{
$postdata = Network::postdata();
if (empty($postdata)) {
return [];
}
return json_decode($postdata, true);
}
/**
* Get request data for put requests
*
* @return array request data
*/
public static function getPutData()
{
$rawdata = Network::postdata();
if (empty($rawdata)) {
return [];
}
$putdata = [];
foreach (explode('&', $rawdata) as $value) {
$data = explode('=', $value);
if (count($data) == 2) {
$putdata[$data[0]] = urldecode($data[1]);
}
}
return $putdata;
}
/** /**
* Log in user via OAuth1 or Simple HTTP Auth. * Log in user via OAuth1 or Simple HTTP Auth.
* *
@ -394,7 +359,7 @@ class BaseApi extends BaseModule
Logger::warning('Requested token scope is not allowed for the application', ['token' => $fields, 'application' => $application]); Logger::warning('Requested token scope is not allowed for the application', ['token' => $fields, 'application' => $application]);
} }
} }
if (!DBA::insert('application-token', $fields, Database::INSERT_UPDATE)) { if (!DBA::insert('application-token', $fields, Database::INSERT_UPDATE)) {
return []; return [];
} }