Servers supporting updates must support the source content query. It was falling through to the token response, so clients got a 200 with no post in it. Return the raw note as microformats2 JSON, honouring properties[]. Malformed requests could cause a 500 or worse. A replace value sent as a string overwrote the note with its first character, because $value[0] indexes the string. Validate update operations before applying any of them, and return invalid_request for wrongly shaped properties, unknown or malformed q values, and missing update URLs. Share URL resolution between updates and q=source in NoteResolver, which also rejects URLs on other hosts and invalid note IDs. Those IDs used to decode to a real note because invalid characters were skipped. Allow photo objects without alt text, and parse the published date before saving so that an invalid date doesn't leave a stray note behind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
43 lines
1.4 KiB
PHP
43 lines
1.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Micropub;
|
|
|
|
use App\Exceptions\MicropubUnsupportedModelException;
|
|
use App\Models\Note;
|
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
|
|
/**
|
|
* Finds the note a Micropub request refers to by its URL.
|
|
*/
|
|
class NoteResolver
|
|
{
|
|
/**
|
|
* @throws MicropubUnsupportedModelException if the URL is not a note on this site
|
|
* @throws ModelNotFoundException if no such note exists
|
|
*/
|
|
public function fromUrl(string $url): Note
|
|
{
|
|
$host = parse_url($url, PHP_URL_HOST);
|
|
$ourHost = parse_url((string) config('app.url'), PHP_URL_HOST);
|
|
|
|
if (! is_string($host) || ! is_string($ourHost) || strcasecmp($host, $ourHost) !== 0) {
|
|
throw new MicropubUnsupportedModelException('The URL is not a post on this site');
|
|
}
|
|
|
|
$path = (string) parse_url($url, PHP_URL_PATH);
|
|
|
|
if (preg_match('#^/notes/([0-9A-Za-z_]+)/?$#', $path, $matches) !== 1) {
|
|
throw new MicropubUnsupportedModelException('This implementation currently only supports notes');
|
|
}
|
|
|
|
try {
|
|
return Note::nb60($matches[1])->firstOrFail();
|
|
} catch (NotFoundHttpException) {
|
|
// Note::scopeNb60() aborts when the ID is too big for the database
|
|
throw (new ModelNotFoundException)->setModel(Note::class);
|
|
}
|
|
}
|
|
}
|