diff --git a/.travis.yml b/.travis.yml
index df73f1c7..b1a6d010 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -19,6 +19,7 @@ addons:
paths:
- $(ls tests/Browser/screenshots/*.png | tr "\n" ":")
- $(ls tests/Browser/console/*.log | tr "\n" ":")
+ - $(ls storage/logs/*.log | tr "\n" ":")
- $(ls /tmp/*.log | tr "\n" ":")
services:
@@ -49,16 +50,18 @@ install:
- travis/install-nginx.sh
before_script:
+ - echo 'error_log = "/tmp/php.error.log"' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
- psql -U travis -c 'create database travis_ci_test'
- psql -U travis -d travis_ci_test -c 'create extension postgis'
- cp .env.travis .env
- php artisan key:generate
- php artisan migrate
- php artisan db:seed
+ - php artisan token:generate
- phantomjs --webdriver=127.0.0.1:9515 --webdriver-loglevel=DEBUG &
- sleep 5 # Give artisan some time to start serving
script:
- php vendor/bin/phpunit --coverage-text
- php artisan dusk
- - php artisan security:check
+ - php vendor/bin/security-checker security:check ./composer.lock --end-point=http://security.sensiolabs.org/check_lock
diff --git a/app/Console/Commands/GenerateToken.php b/app/Console/Commands/GenerateToken.php
index 72bea542..4398a757 100644
--- a/app/Console/Commands/GenerateToken.php
+++ b/app/Console/Commands/GenerateToken.php
@@ -2,9 +2,9 @@
namespace App\Console\Commands;
+use App\IndieWebUser;
use App\Services\TokenService;
use Illuminate\Console\Command;
-use Illuminate\Support\Facades\Storage;
class GenerateToken extends Command
{
@@ -49,10 +49,12 @@ class GenerateToken extends Command
$data = [
'me' => config('app.url'),
'client_id' => route('micropub-client'),
- 'scope' => 'post',
+ 'scope' => 'create update',
];
$token = $tokenService->getNewToken($data);
- Storage::disk('local')->put('dev-token', $token);
+ $user = IndieWebUser::where('me', config('app.url'))->first();
+ $user->token = $token;
+ $user->save();
$this->info('Set token');
}
diff --git a/app/Http/Controllers/IndieAuthController.php b/app/Http/Controllers/IndieAuthController.php
index 60a32200..630ae4fb 100644
--- a/app/Http/Controllers/IndieAuthController.php
+++ b/app/Http/Controllers/IndieAuthController.php
@@ -2,16 +2,17 @@
namespace App\Http\Controllers;
+use App\IndieWebUser;
+use IndieAuth\Client;
use Illuminate\Http\Request;
use App\Services\TokenService;
-use App\Services\IndieAuthService;
class IndieAuthController extends Controller
{
/**
- * This service isolates the IndieAuth Client code.
+ * The IndieAuth Client.
*/
- protected $indieAuthService;
+ protected $client;
/**
* The Token handling service.
@@ -21,15 +22,15 @@ class IndieAuthController extends Controller
/**
* Inject the dependencies.
*
- * @param \App\Services\IndieAuthService $indieAuthService
+ * @param \IndieAuth\Client $client
* @param \App\Services\TokenService $tokenService
* @return void
*/
public function __construct(
- IndieAuthService $indieAuthService = null,
+ Client $client = null,
TokenService $tokenService = null
) {
- $this->indieAuthService = $indieAuthService ?? new IndieAuthService();
+ $this->client = $client ?? new Client();
$this->tokenService = $tokenService ?? new TokenService();
}
@@ -44,25 +45,31 @@ class IndieAuthController extends Controller
*/
public function start(Request $request)
{
- $authorizationEndpoint = $this->indieAuthService->getAuthorizationEndpoint(
- $request->input('me')
- );
- if ($authorizationEndpoint !== null) {
- $authorizationURL = $this->indieAuthService->buildAuthorizationURL(
+ $url = normalize_url($request->input('me'));
+ $authorizationEndpoint = $this->client->discoverAuthorizationEndpoint($url);
+ if ($authorizationEndpoint != null) {
+ $state = bin2hex(openssl_random_pseudo_bytes(16));
+ session(['state' => $state]);
+ $authorizationURL = $this->client->buildAuthorizationURL(
$authorizationEndpoint,
- $request->input('me')
+ $url,
+ route('indieauth-callback'), //redirect_uri
+ route('micropub-client'), //client_id
+ $state
);
if ($authorizationURL) {
return redirect($authorizationURL);
}
+
+ return redirect(route('micropub-client'))->with('error', 'Error building authorization URL');
}
return redirect(route('micropub-client'))->with('error', 'Unable to determine authorisation endpoint');
}
/**
- * Once they have verified themselves through the authorisation endpint
- * the next step is retreiveing a token from the token endpoint.
+ * Once they have verified themselves through the authorisation endpoint
+ * the next step is register/login the user.
*
* @param \Illuminate\Http\Rrequest $request
* @return \Illuminate\Routing\RedirectResponse redirect
@@ -75,6 +82,15 @@ class IndieAuthController extends Controller
'Invalid state
value returned from indieauth server'
);
}
+
+ $url = normalize_url($request->input('me'));
+ $indiewebUser = IndieWebUser::firstOrCreate(['me' => $url]);
+ $request->session()->put(['me' => $url]);
+
+ return redirect(route('micropub-client'));
+ }
+
+/*
$tokenEndpoint = $this->indieAuthService->getTokenEndpoint($request->input('me'));
if ($tokenEndpoint === false) {
return redirect(route('micropub-client'))->with(
@@ -103,10 +119,10 @@ class IndieAuthController extends Controller
'error',
'Unable to get a token from the endpoint'
);
- }
+*/
/**
- * Log out the user, flush an session data, and overwrite any cookie data.
+ * Log out the user, flush the session data.
*
* @return \Illuminate\Routing\RedirectResponse redirect
*/
@@ -114,7 +130,7 @@ class IndieAuthController extends Controller
{
$request->session()->flush();
- return redirect(route('micropub-client'))->cookie('me', 'loggedout', 1);
+ return redirect(route('micropub-client'));
}
/**
diff --git a/app/Http/Controllers/MicropubClientController.php b/app/Http/Controllers/MicropubClientController.php
index bdeede26..4c97b968 100644
--- a/app/Http/Controllers/MicropubClientController.php
+++ b/app/Http/Controllers/MicropubClientController.php
@@ -2,7 +2,7 @@
namespace App\Http\Controllers;
-use App\Services\IndieAuthService;
+use App\IndieWebUser;
use IndieAuth\Client as IndieClient;
use GuzzleHttp\Client as GuzzleClient;
use Illuminate\Http\{Request, Response};
@@ -10,20 +10,13 @@ use GuzzleHttp\Exception\{ClientException, ServerException};
class MicropubClientController extends Controller
{
- /**
- * The IndieAuth service container.
- */
- protected $indieAuthService;
-
/**
* Inject the dependencies.
*/
public function __construct(
- IndieAuthService $indieAuthService = null,
IndieClient $indieClient = null,
GuzzleClient $guzzleClient = null
) {
- $this->indieAuthService = $indieAuthService ?? new IndieAuthService();
$this->guzzleClient = $guzzleClient ?? new GuzzleClient();
$this->indieClient = $indieClient ?? new IndieClient();
}
@@ -37,8 +30,11 @@ class MicropubClientController extends Controller
public function create(Request $request)
{
$url = $request->session()->get('me');
- $syndication = $request->session()->get('syndication');
- $mediaEndpoint = $request->session()->get('media-endpoint');
+ if ($url) {
+ $indiewebUser = IndieWebUser::where('me', $url)->first();
+ }
+ $syndication = $this->parseSyndicationTargets($indiewebUser->syndication);
+ $mediaEndpoint = $indiewebUser->mediaEndpoint ?? null;
$mediaURLs = $request->session()->get('media-links');
return view('micropub.create', compact('url', 'syndication', 'mediaEndpoint', 'mediaURLs'));
@@ -56,19 +52,17 @@ class MicropubClientController extends Controller
return back();
}
- $mediaEndpoint = $request->session()->get('media-endpoint');
- if ($mediaEndpoint == null) {
+ $user = IndieWebUser::where('me', $request->session()->get('me'))->firstOrFail();
+ if ($user->mediaEndpoint == null || $user->token == null) {
return back();
}
- $token = $request->session()->get('token');
-
$mediaURLs = [];
foreach ($request->file('file') as $file) {
try {
- $response = $this->guzzleClient->request('POST', $mediaEndpoint, [
+ $response = $this->guzzleClient->request('POST', $user->mediaEndpoint, [
'headers' => [
- 'Authorization' => 'Bearer ' . $token,
+ 'Authorization' => 'Bearer ' . $user->token,
],
'multipart' => [
[
@@ -109,27 +103,142 @@ class MicropubClientController extends Controller
*/
public function store(Request $request)
{
- $domain = $request->session()->get('me');
- $token = $request->session()->get('token');
+ $url = normalize_url($request->session()->get('me'));
+ $user = IndieWebUser::where('me', $url)->firstOrFail();
- $micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint(
- $domain,
- $this->indieClient
- );
+ if ($user->token == null) {
+ return redirect(route('micropub-client'))->with('error', 'You haven’t requested a token yet');
+ }
+
+ $micropubEndpoint = $this->indieClient->discoverMicropubEndpoint($url);
if (! $micropubEndpoint) {
return redirect(route('micropub-client'))->with('error', 'Unable to determine micropub API endpoint');
}
- $response = $this->postNoteRequest($request, $micropubEndpoint, $token);
+ $headers = [
+ 'Authorization' => 'Bearer ' . $user->token,
+ ];
- if ($response->getStatusCode() == 201) {
- $request->session()->forget('media-links');
- $location = $response->getHeader('Location');
- if (is_array($location)) {
- return redirect($location[0]);
+ if ($user->syntax == 'html') {
+ $multipart = [
+ [
+ 'name' => 'h',
+ 'contents' => 'entry',
+ ],
+ [
+ 'name' => 'content',
+ 'contents' => $request->input('content'),
+ ],
+ ];
+ if ($request->hasFile('photo')) {
+ $photos = $request->file('photo');
+ foreach ($photos as $photo) {
+ $multipart[] = [
+ 'name' => 'photo[]',
+ 'contents' => fopen($photo->path(), 'r'),
+ 'filename' => $photo->getClientOriginalName(),
+ ];
+ }
+ }
+ if ($request->input('in-reply-to') != '') {
+ $multipart[] = [
+ 'name' => 'in-reply-to',
+ 'contents' => $request->input('in-reply-to'),
+ ];
+ }
+ if ($request->input('mp-syndicate-to')) {
+ foreach ($request->input('mp-syndicate-to') as $syn) {
+ $multipart[] = [
+ 'name' => 'mp-syndicate-to[]',
+ 'contents' => $syn,
+ ];
+ }
+ }
+ if ($request->input('location')) {
+ if ($request->input('location') !== 'no-location') {
+ $multipart[] = [
+ 'name' => 'location',
+ 'contents' => $request->input('location'),
+ ];
+ }
+ }
+ if ($request->input('media')) {
+ foreach ($request->input('media') as $media) {
+ $multipart[] = [
+ 'name' => 'photo[]',
+ 'contents' => $media,
+ ];
+ }
+ }
+ try {
+ $response = $this->guzzleClient->post($micropubEndpoint, [
+ 'multipart' => $multipart,
+ 'headers' => $headers,
+ ]);
+ } catch (\GuzzleHttp\Exception\BadResponseException $e) {
+ return redirect(route('micropub-client'))->with(
+ 'error',
+ 'There was a bad response from the micropub endpoint.'
+ );
}
- return redirect($location);
+ if ($response->getStatusCode() == 201) {
+ $request->session()->forget('media-links');
+ $location = $response->getHeader('Location');
+ if (is_array($location)) {
+ return redirect($location[0]);
+ }
+
+ return redirect($location);
+ }
+ }
+
+ if ($user->syntax == 'json') {
+ $json = [];
+ $json['type'] = ['h-entry'];
+ $json['properties'] = ['content' => [$request->input('content')]];
+
+ if ($request->input('in-reply-to') != '') {
+ $json['properties']['in-reply-to'] = [$request->input('in-reply-to')];
+ }
+ if ($request->input('mp-syndicate-to')) {
+ foreach ($request->input('mp-syndicate-to') as $syn) {
+ $json['properties']['mp-syndicate-to'] = [$syn];
+ }
+ }
+ if ($request->input('location')) {
+ if ($request->input('location') !== 'no-location') {
+ $json['properties']['location'] = [$request->input('location')];
+ }
+ }
+ if ($request->input('media')) {
+ $json['properties']['photo'] = [];
+ foreach ($request->input('media') as $media) {
+ $json['properties']['photo'][] = $media;
+ }
+ }
+
+ try {
+ $response = $this->guzzleClient->post($micropubEndpoint, [
+ 'json' => $json,
+ 'headers' => $headers,
+ ]);
+ } catch (\GuzzleHttp\Exception\BadResponseException $e) {
+ return redirect(route('micropub-client'))->with(
+ 'error',
+ 'There was a bad response from the micropub endpoint.'
+ );
+ }
+
+ if ($response->getStatusCode() == 201) {
+ $request->session()->forget('media-links');
+ $location = $response->getHeader('Location');
+ if (is_array($location)) {
+ return redirect($location[0]);
+ }
+
+ return redirect($location);
+ }
}
return redirect(route('micropub-client'))->with('error', 'Endpoint didn’t create the note.');
@@ -143,26 +252,100 @@ class MicropubClientController extends Controller
*/
public function config(Request $request)
{
- $data['me'] = $request->session()->get('me');
- $data['token'] = $request->session()->get('token');
- $data['syndication'] = $request->session()->get('syndication') ?? 'none defined';
- $data['media-endpoint'] = $request->session()->get('media-endpoint') ?? 'none defined';
+ //default values
+ $data = [
+ 'me' => '',
+ 'token' => 'none',
+ 'syndication' => 'none defined',
+ 'media-endpoint' => 'none defined',
+ 'syntax' => 'html',
+ ];
+ if ($request->session()->has('me')) {
+ $data['me'] = normalize_url($request->session()->get('me'));
+ $user = IndieWebUser::where('me', $request->session()->get('me'))->first();
+ $data['token'] = $user->token ?? 'none defined';
+ $data['syndication'] = $user->syndication ?? 'none defined';
+ $data['media-endpoint'] = $user->mediaEndpoint ?? 'none defined';
+ $data['syntax'] = $user->syntax;
+ }
return view('micropub.config', compact('data'));
}
/**
- * Query the micropub endpoint and store response in the session.
+ * Get a new token.
+ *
+ * @param Illuminate\Http\Request $request
+ * @return view
+ */
+ public function getNewToken(Request $request)
+ {
+ if ($request->session()->has('me')) {
+ $url = normalize_url($request->session()->get('me'));
+ $authozationEndpoint = $this->indieClient->discoverAuthorizationEndpoint($url);
+ if ($authozationEndpoint) {
+ $state = bin2hex(random_bytes(16));
+ $request->session()->put('state', $state);
+ $authorizationURL = $this->indieClient->buildAuthorizationURL(
+ $authozationEndpoint,
+ $url,
+ route('micropub-client-get-new-token-callback'), // redirect_uri
+ route('micropub-client'), //client_id
+ $state,
+ 'create update' // scope needs to be a setting
+ );
+
+ return redirect($authorizationURL);
+ }
+
+ return back();
+ }
+
+ return back();
+ }
+
+ /**
+ * The callback for getting a token.
+ */
+ public function getNewTokenCallback(Request $request)
+ {
+ if ($request->input('state') !== $request->session()->get('state')) {
+ return route('micropub-client')->with('error', 'The state
didn’t match.');
+ }
+ $tokenEndpoint = $this->indieClient->discoverTokenEndpoint(normalize_url($request->input('me')));
+ if ($tokenEndpoint) {
+ $token = $this->indieClient->getAccessToken(
+ $tokenEndpoint,
+ $request->input('code'),
+ $request->input('me'),
+ route('micropub-client-get-new-token-callback'), // redirect_uri
+ route('micropub-client'), // client_id
+ $request->input('state')
+ );
+ if (array_key_exists('access_token', $token)) {
+ $url = normalize_url($token['me']);
+ $user = IndieWebUser::where('me', $url)->firstOrFail();
+ $user->token = $token['access_token'];
+ $user->save();
+
+ return redirect('micropub-config');
+ }
+ }
+ }
+
+ /**
+ * Query the micropub endpoint and store response.
*
* @param Illuminate\Http\Request $request
* @return redirect
*/
public function queryEndpoint(Request $request)
{
- $domain = $request->session()->get('me');
- $token = $request->session()->get('token');
- $micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint($domain);
- if ($micropubEndpoint !== null) {
+ $url = normalize_url($request->session()->get('me'));
+ $user = IndieWebUser::where('me', $url)->firstOrFail();
+ $token = $user->token;
+ $micropubEndpoint = $this->indieClient->discoverMicropubEndpoint($url);
+ if ($micropubEndpoint) {
try {
$response = $this->guzzleClient->get($micropubEndpoint, [
'headers' => ['Authorization' => 'Bearer ' . $token],
@@ -172,96 +355,35 @@ class MicropubClientController extends Controller
return back();
}
$body = (string) $response->getBody();
+ $data = json_decode($body, true);
- $syndication = $this->parseSyndicationTargets($body);
- $request->session()->put('syndication', $syndication);
+ if (array_key_exists('syndicate-to', $data)) {
+ $user->syndication = json_encode($data['syndicate-to']);
+ }
- $mediaEndpoint = $this->parseMediaEndpoint($body);
- $request->session()->put('media-endpoint', $mediaEndpoint);
+ if (array_key_exists('media-endpoint', $data)) {
+ $user->mediaEndpoint = $data['media-endpoint'];
+ }
+ $user->save();
return back();
}
}
/**
- * This method performs the actual POST request.
+ * Update the syntax setting.
*
- * @param \Illuminate\Http\Request $request
- * @param string The Micropub endpoint to post to
- * @param string The token to authenticate the request with
- * @return \GuzzleHttp\Response $response | \Illuminate\RedirectFactory redirect
+ * @param Illuminate\Http\Request $request
+ * @return Illuminate\Http\RedirectResponse
+ * @todo validate input
*/
- private function postNoteRequest(
- Request $request,
- $micropubEndpoint,
- $token
- ) {
- $multipart = [
- [
- 'name' => 'h',
- 'contents' => 'entry',
- ],
- [
- 'name' => 'content',
- 'contents' => $request->input('content'),
- ],
- ];
- if ($request->hasFile('photo')) {
- $photos = $request->file('photo');
- foreach ($photos as $photo) {
- $multipart[] = [
- 'name' => 'photo[]',
- 'contents' => fopen($photo->path(), 'r'),
- 'filename' => $photo->getClientOriginalName(),
- ];
- }
- }
- if ($request->input('in-reply-to') != '') {
- $multipart[] = [
- 'name' => 'in-reply-to',
- 'contents' => $request->input('in-reply-to'),
- ];
- }
- if ($request->input('mp-syndicate-to')) {
- foreach ($request->input('mp-syndicate-to') as $syn) {
- $multipart[] = [
- 'name' => 'mp-syndicate-to[]',
- 'contents' => $syn,
- ];
- }
- }
- if ($request->input('location')) {
- if ($request->input('location') !== 'no-location') {
- $multipart[] = [
- 'name' => 'location',
- 'contents' => $request->input('location'),
- ];
- }
- }
- if ($request->input('media')) {
- foreach ($request->input('media') as $media) {
- $multipart[] = [
- 'name' => 'photo[]',
- 'contents' => $media,
- ];
- }
- }
- $headers = [
- 'Authorization' => 'Bearer ' . $token,
- ];
- try {
- $response = $this->guzzleClient->post($micropubEndpoint, [
- 'multipart' => $multipart,
- 'headers' => $headers,
- ]);
- } catch (\GuzzleHttp\Exception\BadResponseException $e) {
- return redirect(route('micropub-client'))->with(
- 'error',
- 'There was a bad response from the micropub endpoint.'
- );
- }
+ public function updateSyntax(Request $request)
+ {
+ $user = IndieWebUser::where('me', $request->session()->get('me'))->firstOrFail();
+ $user->syntax = $request->syntax;
+ $user->save();
- return $response;
+ return redirect(route('micropub-config'));
}
/**
@@ -272,16 +394,17 @@ class MicropubClientController extends Controller
*/
public function newPlace(Request $request)
{
- if ($request->session()->has('token') === false) {
+ $url = normalize_url($request->session()->get('me'));
+ $user = IndieWebUser::where('me', $url)->firstOrFail();
+
+ if ($user->token === null) {
return response()->json([
'error' => true,
'error_description' => 'No known token',
], 400);
}
- $domain = $request->session()->get('me');
- $token = $request->session()->get('token');
- $micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint($domain, $this->indieClient);
+ $micropubEndpoint = $this->indieClient->discoverMicropubEndpoint($url);
if (! $micropubEndpoint) {
return response()->json([
'error' => true,
@@ -289,13 +412,27 @@ class MicropubClientController extends Controller
], 400);
}
- $place = $this->postPlaceRequest($request, $micropubEndpoint, $token);
- if ($place === false) {
+ $formParams = [
+ 'h' => 'card',
+ 'name' => $request->input('place-name'),
+ 'description' => $request->input('place-description'),
+ 'geo' => 'geo:' . $request->input('place-latitude') . ',' . $request->input('place-longitude'),
+ ];
+ $headers = [
+ 'Authorization' => 'Bearer ' . $user->token,
+ ];
+ try {
+ $response = $this->guzzleClient->request('POST', $micropubEndpoint, [
+ 'form_params' => $formParams,
+ 'headers' => $headers,
+ ]);
+ } catch (ClientException $e) {
return response()->json([
'error' => true,
'error_description' => 'Unable to create the new place',
], 400);
}
+ $place = $response->getHeader('Location')[0];
return response()->json([
'uri' => $place,
@@ -305,44 +442,6 @@ class MicropubClientController extends Controller
]);
}
- /**
- * Actually make a micropub request to make a new place.
- *
- * @param \Illuminate\Http\Request $request
- * @param string The Micropub endpoint to post to
- * @param string The token to authenticate the request with
- * @param \GuzzleHttp\Client $client
- * @return \GuzzleHttp\Response $response | \Illuminate\RedirectFactory redirect
- */
- private function postPlaceRequest(
- Request $request,
- $micropubEndpoint,
- $token
- ) {
- $formParams = [
- 'h' => 'card',
- 'name' => $request->input('place-name'),
- 'description' => $request->input('place-description'),
- 'geo' => 'geo:' . $request->input('place-latitude') . ',' . $request->input('place-longitude'),
- ];
- $headers = [
- 'Authorization' => 'Bearer ' . $token,
- ];
- try {
- $response = $this->guzzleClient->request('POST', $micropubEndpoint, [
- 'form_params' => $formParams,
- 'headers' => $headers,
- ]);
- } catch (ClientException $e) {
- return false;
- }
- if ($response->getStatusCode() == 201) {
- return $response->getHeader('Location')[0];
- }
-
- return false;
- }
-
/**
* Make a request to the micropub endpoint requesting any nearby places.
*
@@ -351,16 +450,17 @@ class MicropubClientController extends Controller
*/
public function nearbyPlaces(Request $request)
{
- if ($request->session()->has('token') === false) {
+ $url = normalize_url($request->session()->get('me'));
+ $user = IndieWebUser::where('me', $url)->firstOrFail();
+
+ if ($user->token === null) {
return response()->json([
'error' => true,
'error_description' => 'No known token',
], 400);
}
- $domain = $request->session()->get('me');
- $token = $request->session()->get('token');
- $micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint($domain, $this->indieClient);
+ $micropubEndpoint = $this->indieClient->discoverMicropubEndpoint($url);
if (! $micropubEndpoint) {
return response()->json([
@@ -375,7 +475,7 @@ class MicropubClientController extends Controller
$query .= ';u=' . $request->input('u');
}
$response = $this->guzzleClient->get($micropubEndpoint, [
- 'headers' => ['Authorization' => 'Bearer ' . $token],
+ 'headers' => ['Authorization' => 'Bearer ' . $user->token],
'query' => ['q' => $query],
]);
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
@@ -390,30 +490,34 @@ class MicropubClientController extends Controller
}
/**
- * Parse the syndication targets retreived from a cookie, to a form that can
- * be used in a view.
+ * Parse the syndication targets JSON into a an array.
*
- * @param string $syndicationTargets
+ * @param string|null
* @return array|null
*/
private function parseSyndicationTargets($syndicationTargets = null)
{
- if ($syndicationTargets === null) {
+ if ($syndicationTargets === null || $syndicationTargets === '') {
return;
}
$syndicateTo = [];
$data = json_decode($syndicationTargets, true);
- if (array_key_exists('syndicate-to', $data)) {
- foreach ($data['syndicate-to'] as $syn) {
+ if (array_key_exists('uid', $data)) {
+ $syndicateTo[] = [
+ 'target' => $data['uid'],
+ 'name' => $data['name'],
+ ];
+ }
+ foreach ($data as $syn) {
+ if (array_key_exists('uid', $syn)) {
$syndicateTo[] = [
'target' => $syn['uid'],
'name' => $syn['name'],
];
}
}
- if (count($syndicateTo) > 0) {
- return $syndicateTo;
- }
+
+ return $syndicateTo;
}
/**
diff --git a/app/Http/Controllers/MicropubController.php b/app/Http/Controllers/MicropubController.php
index 282253b7..65ddb982 100644
--- a/app/Http/Controllers/MicropubController.php
+++ b/app/Http/Controllers/MicropubController.php
@@ -47,10 +47,18 @@ class MicropubController extends Controller
*/
public function post(Request $request)
{
- $tokenData = $this->tokenService->validateToken($request->bearerToken());
+ try {
+ $tokenData = $this->tokenService->validateToken($request->bearerToken());
+ } catch (\Exception $e) {
+ return response()->json([
+ 'response' => 'error',
+ 'error' => 'invalid_token',
+ 'error_description' => 'The provided token did not pass validation',
+ ], 400);
+ }
if ($tokenData->hasClaim('scope')) {
$scopes = explode(' ', $tokenData->getClaim('scope'));
- if (array_search('post', $scopes) !== false) {
+ if (array_search('create', $scopes) !== false) {
$clientId = $tokenData->getClaim('client_id');
if (($request->input('h') == 'entry') || ($request->input('type')[0] == 'h-entry')) {
$data = [];
@@ -162,8 +170,9 @@ class MicropubController extends Controller
*/
public function get(Request $request)
{
- $tokenData = $this->tokenService->validateToken($request->bearerToken());
- if ($tokenData === null) {
+ try {
+ $tokenData = $this->tokenService->validateToken($request->bearerToken());
+ } catch (\Exception $e) {
return response()->json([
'response' => 'error',
'error' => 'invalid_token',
@@ -258,7 +267,7 @@ class MicropubController extends Controller
], 503);
}
$media = new Media();
- $media->token = $token;
+ $media->token = $request->bearerToken();
$media->path = $path;
$media->type = $type;
$media->save();
diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php
index 63f01def..1fad4108 100644
--- a/app/Http/Kernel.php
+++ b/app/Http/Kernel.php
@@ -34,7 +34,8 @@ class Kernel extends HttpKernel
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\LinkHeadersMiddleware::class,
- \App\Http\Middleware\DevTokenMiddleware::class,
+ //\App\Http\Middleware\DevTokenMiddleware::class,
+ \App\Http\Middleware\LocalhostSessionMiddleware::class,
],
'api' => [
diff --git a/app/Http/Middleware/LocalhostSessionMiddleware.php b/app/Http/Middleware/LocalhostSessionMiddleware.php
new file mode 100644
index 00000000..ded4f25a
--- /dev/null
+++ b/app/Http/Middleware/LocalhostSessionMiddleware.php
@@ -0,0 +1,26 @@
+ config('app.url')]` as I can’t manually log in as
+ * a .localhost domain.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param \Closure $next
+ * @return mixed
+ */
+ public function handle($request, Closure $next)
+ {
+ if (config('app.env') !== 'production') {
+ session(['me' => config('app.url')]);
+ }
+
+ return $next($request);
+ }
+}
diff --git a/app/IndieWebUser.php b/app/IndieWebUser.php
new file mode 100644
index 00000000..d35fa1b5
--- /dev/null
+++ b/app/IndieWebUser.php
@@ -0,0 +1,15 @@
+ $state]);
$redirectURL = route('indieauth-callback');
$clientId = route('micropub-client');
- $scope = 'post';
$authorizationURL = $this->client->buildAuthorizationURL(
$authEndpoint,
$this->client->normalizeMeURL($domain),
$redirectURL,
$clientId,
- $state,
- $scope
+ $state
);
return $authorizationURL;
diff --git a/app/Services/TokenService.php b/app/Services/TokenService.php
index fd4cd1cb..fecb8ded 100644
--- a/app/Services/TokenService.php
+++ b/app/Services/TokenService.php
@@ -4,11 +4,9 @@ declare(strict_types=1);
namespace App\Services;
-use RuntimeException;
use Lcobucci\JWT\Token;
use Lcobucci\JWT\Parser;
use Lcobucci\JWT\Builder;
-use InvalidArgumentException;
use Lcobucci\JWT\Signer\Hmac\Sha256;
class TokenService
@@ -39,17 +37,14 @@ class TokenService
* @param string The token
* @return mixed
*/
- public function validateToken(string $token): ?Token
+ public function validateToken(string $bearerToken): ?Token
{
$signer = new Sha256();
- try {
- $token = (new Parser())->parse((string) $token);
- } catch (InvalidArgumentException | RuntimeException $e) {
- return null;
- }
- if ($token->verify($signer, config('app.key'))) {
- //signuture valid
- return $token;
+ $token = (new Parser())->parse((string) $bearerToken);
+ if (! $token->verify($signer, config('app.key'))) {
+ throw new \Exception('Token not verified');
}
+
+ return $token;
}
}
diff --git a/app/helpers.php b/app/helpers.php
new file mode 100644
index 00000000..6dcad34d
--- /dev/null
+++ b/app/helpers.php
@@ -0,0 +1,191 @@
+ 80, 'https' => 443];
+ if (isset($url['scheme'])) {
+ $url['scheme'] = strtolower($url['scheme']);
+ // Strip scheme default ports
+ if (
+ isset($defaultSchemes[$url['scheme']]) &&
+ isset($url['port']) &&
+ $defaultSchemes[$url['scheme']] == $url['port']
+ ) {
+ unset($url['port']);
+ }
+ $newUrl .= "{$url['scheme']}://";
+ }
+ if (isset($url['host'])) {
+ $url['host'] = mb_strtolower($url['host']);
+ $newUrl .= $url['host'];
+ }
+ if (isset($url['port'])) {
+ $newUrl .= ":{$url['port']}";
+ }
+ // here we only want to drop a slash for the root domain
+ // e.g. http://example.com/ -> http://example.com
+ // but http://example.com/path/ -/-> http://example.com/path
+ if (isset($url['path']) && $url['path'] == '/') {
+ unset($url['path']);
+ }
+ if (isset($url['path'])) {
+ // Case normalization
+ $url['path'] = normalizer_normalize($url['path'], Normalizer::FORM_C);
+ // Strip duplicate slashes
+ while (preg_match("/\/\//", $url['path'])) {
+ $url['path'] = preg_replace('/\/\//', '/', $url['path']);
+ }
+
+ /*
+ * Decode unreserved characters, http://www.apps.ietf.org/rfc/rfc3986.html#sec-2.3
+ * Heavily rewritten version of urlDecodeUnreservedChars() in Glen Scott's url-normalizer.
+ */
+ $u = [];
+ for ($o = 65; $o <= 90; $o++) {
+ $u[] = dechex($o);
+ }
+ for ($o = 97; $o <= 122; $o++) {
+ $u[] = dechex($o);
+ }
+ for ($o = 48; $o <= 57; $o++) {
+ $u[] = dechex($o);
+ }
+ $chrs = ['-', '.', '_', '~'];
+ foreach ($chrs as $chr) {
+ $u[] = dechex(ord($chr));
+ }
+ $url['path'] = preg_replace_callback(
+ array_map(
+ create_function('$str', 'return "/%" . strtoupper($str) . "/x";'),
+ $u
+ ),
+ create_function('$matches', 'return chr(hexdec($matches[0]));'),
+ $url['path']
+ );
+ // Remove directory index
+ $defaultIndexes = ["/default\.aspx/" => 'default.aspx', "/default\.asp/" => 'default.asp',
+ "/index\.html/" => 'index.html', "/index\.htm/" => 'index.htm',
+ "/default\.html/" => 'default.html', "/default\.htm/" => 'default.htm',
+ "/index\.php/" => 'index.php', "/index\.jsp/" => 'index.jsp', ];
+ foreach ($defaultIndexes as $index => $strip) {
+ if (preg_match($index, $url['path'])) {
+ $url['path'] = str_replace($strip, '', $url['path']);
+ }
+ }
+
+ /**
+ * Path segment normalization, http://www.apps.ietf.org/rfc/rfc3986.html#sec-5.2.4
+ * Heavily rewritten version of removeDotSegments() in Glen Scott's url-normalizer.
+ */
+ $new_path = '';
+ while (! empty($url['path'])) {
+ if (preg_match('!^(\.\./|\./)!x', $url['path'])) {
+ $url['path'] = preg_replace('!^(\.\./|\./)!x', '', $url['path']);
+ } elseif (preg_match('!^(/\./)!x', $url['path'], $matches) || preg_match('!^(/\.)$!x', $url['path'], $matches)) {
+ $url['path'] = preg_replace('!^' . $matches[1] . '!', '/', $url['path']);
+ } elseif (preg_match('!^(/\.\./|/\.\.)!x', $url['path'], $matches)) {
+ $url['path'] = preg_replace('!^' . preg_quote($matches[1], '!') . '!x', '/', $url['path']);
+ $new_path = preg_replace('!/([^/]+)$!x', '', $new_path);
+ } elseif (preg_match('!^(\.|\.\.)$!x', $url['path'])) {
+ $url['path'] = preg_replace('!^(\.|\.\.)$!x', '', $url['path']);
+ } else {
+ if (preg_match('!(/*[^/]*)!x', $url['path'], $matches)) {
+ $first_path_segment = $matches[1];
+ $url['path'] = preg_replace('/^' . preg_quote($first_path_segment, '/') . '/', '', $url['path'], 1);
+ $new_path .= $first_path_segment;
+ }
+ }
+ }
+ $newUrl .= $new_path;
+ }
+
+ if (isset($url['fragment'])) {
+ unset($url['fragment']);
+ }
+
+ // Sort GET params alphabetically, not because the RFC requires it but because it's cool!
+ if (isset($url['query'])) {
+ if (preg_match('/&/', $url['query'])) {
+ $s = explode('&', $url['query']);
+ $url['query'] = '';
+ sort($s);
+ foreach ($s as $z) {
+ $url['query'] .= "{$z}&";
+ }
+ $url['query'] = preg_replace('/&\Z/', '', $url['query']);
+ }
+ $newUrl .= "?{$url['query']}";
+ }
+
+ return $newUrl;
+}
+
+// sourced from https://stackoverflow.com/a/9776726
+function prettyPrintJson(string $json): string
+{
+ $result = '';
+ $level = 0;
+ $in_quotes = false;
+ $in_escape = false;
+ $ends_line_level = null;
+ $json_length = strlen($json);
+
+ for ($i = 0; $i < $json_length; $i++) {
+ $char = $json[$i];
+ $new_line_level = null;
+ $post = '';
+ if ($ends_line_level !== null) {
+ $new_line_level = $ends_line_level;
+ $ends_line_level = null;
+ }
+ if ($in_escape) {
+ $in_escape = false;
+ } elseif ($char === '"') {
+ $in_quotes = ! $in_quotes;
+ } elseif (! $in_quotes) {
+ switch ($char) {
+ case '}': case ']':
+ $level--;
+ $ends_line_level = null;
+ $new_line_level = $level;
+ break;
+
+ case '{': case '[':
+ $level++;
+ case ',':
+ $ends_line_level = $level;
+ break;
+
+ case ':':
+ $post = ' ';
+ break;
+
+ case ' ': case "\t": case "\n": case "\r":
+ $char = '';
+ $ends_line_level = $new_line_level;
+ $new_line_level = null;
+ break;
+ }
+ } elseif ($char === '\\') {
+ $in_escape = true;
+ }
+ if ($new_line_level !== null) {
+ $result .= "\n".str_repeat("\t", $new_line_level);
+ }
+ $result .= $char.$post;
+ }
+
+ return str_replace("\t", ' ', $result);
+}
diff --git a/composer.json b/composer.json
index e742f29d..b5531f9a 100644
--- a/composer.json
+++ b/composer.json
@@ -42,7 +42,10 @@
],
"psr-4": {
"App\\": "app/"
- }
+ },
+ "files": [
+ "app/helpers.php"
+ ]
},
"autoload-dev": {
"psr-4": {
diff --git a/composer.lock b/composer.lock
index 45e7bdf5..2188bc12 100644
--- a/composer.lock
+++ b/composer.lock
@@ -8,16 +8,16 @@
"packages": [
{
"name": "aws/aws-sdk-php",
- "version": "3.24.7",
+ "version": "3.25.7",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
- "reference": "f062d7ea2123fe2aefef91da855c10ef8ff3af1c"
+ "reference": "d4f1104f5ac9c755875c5e6e9bade2c70708219a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f062d7ea2123fe2aefef91da855c10ef8ff3af1c",
- "reference": "f062d7ea2123fe2aefef91da855c10ef8ff3af1c",
+ "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/d4f1104f5ac9c755875c5e6e9bade2c70708219a",
+ "reference": "d4f1104f5ac9c755875c5e6e9bade2c70708219a",
"shasum": ""
},
"require": {
@@ -84,7 +84,7 @@
"s3",
"sdk"
],
- "time": "2017-03-23T22:17:20+00:00"
+ "time": "2017-04-11T22:31:27+00:00"
},
{
"name": "barnabywalters/mf-cleaner",
@@ -180,6 +180,65 @@
],
"time": "2016-08-19T16:43:44+00:00"
},
+ {
+ "name": "composer/ca-bundle",
+ "version": "1.0.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/ca-bundle.git",
+ "reference": "b17e6153cb7f33c7e44eb59578dc12eee5dc8e12"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/ca-bundle/zipball/b17e6153cb7f33c7e44eb59578dc12eee5dc8e12",
+ "reference": "b17e6153cb7f33c7e44eb59578dc12eee5dc8e12",
+ "shasum": ""
+ },
+ "require": {
+ "ext-openssl": "*",
+ "ext-pcre": "*",
+ "php": "^5.3.2 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.5",
+ "psr/log": "^1.0",
+ "symfony/process": "^2.5 || ^3.0"
+ },
+ "suggest": {
+ "symfony/process": "This is necessary to reliably check whether openssl_x509_parse is vulnerable on older php versions, but can be ignored on PHP 5.5.6+"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Composer\\CaBundle\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ }
+ ],
+ "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.",
+ "keywords": [
+ "cabundle",
+ "cacert",
+ "certificate",
+ "ssl",
+ "tls"
+ ],
+ "time": "2017-03-06T11:59:08+00:00"
+ },
{
"name": "dnoegel/php-xdg-base-dir",
"version": "0.1",
@@ -685,16 +744,16 @@
},
{
"name": "erusev/parsedown",
- "version": "1.6.1",
+ "version": "1.6.2",
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown.git",
- "reference": "20ff8bbb57205368b4b42d094642a3e52dac85fb"
+ "reference": "1bf24f7334fe16c88bf9d467863309ceaf285b01"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/erusev/parsedown/zipball/20ff8bbb57205368b4b42d094642a3e52dac85fb",
- "reference": "20ff8bbb57205368b4b42d094642a3e52dac85fb",
+ "url": "https://api.github.com/repos/erusev/parsedown/zipball/1bf24f7334fe16c88bf9d467863309ceaf285b01",
+ "reference": "1bf24f7334fe16c88bf9d467863309ceaf285b01",
"shasum": ""
},
"require": {
@@ -723,7 +782,7 @@
"markdown",
"parser"
],
- "time": "2016-11-02T15:56:58+00:00"
+ "time": "2017-03-29T16:04:15+00:00"
},
{
"name": "ezyang/htmlpurifier",
@@ -1405,16 +1464,16 @@
},
{
"name": "laravel/framework",
- "version": "v5.4.16",
+ "version": "v5.4.19",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
- "reference": "6cf379ec34d08bcdc9c7183e369a8fdf04ade80d"
+ "reference": "02444b7450350db17a7607c8a52f7268ebdb0dad"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/framework/zipball/6cf379ec34d08bcdc9c7183e369a8fdf04ade80d",
- "reference": "6cf379ec34d08bcdc9c7183e369a8fdf04ade80d",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/02444b7450350db17a7607c8a52f7268ebdb0dad",
+ "reference": "02444b7450350db17a7607c8a52f7268ebdb0dad",
"shasum": ""
},
"require": {
@@ -1530,20 +1589,20 @@
"framework",
"laravel"
],
- "time": "2017-03-21T19:34:41+00:00"
+ "time": "2017-04-16T13:33:34+00:00"
},
{
"name": "laravel/scout",
- "version": "v3.0.2",
+ "version": "v3.0.3",
"source": {
"type": "git",
"url": "https://github.com/laravel/scout.git",
- "reference": "1ddb0fa6f165bf6a69864960102062e7cf3f989d"
+ "reference": "64d28db58a054174eadf1d4df38dad81ff7e68dd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/scout/zipball/1ddb0fa6f165bf6a69864960102062e7cf3f989d",
- "reference": "1ddb0fa6f165bf6a69864960102062e7cf3f989d",
+ "url": "https://api.github.com/repos/laravel/scout/zipball/64d28db58a054174eadf1d4df38dad81ff7e68dd",
+ "reference": "64d28db58a054174eadf1d4df38dad81ff7e68dd",
"shasum": ""
},
"require": {
@@ -1590,7 +1649,7 @@
"laravel",
"search"
],
- "time": "2017-03-01T14:37:40+00:00"
+ "time": "2017-04-09T00:54:26+00:00"
},
{
"name": "laravel/tinker",
@@ -2740,19 +2799,20 @@
},
{
"name": "sensiolabs/security-checker",
- "version": "v4.0.2",
+ "version": "v4.0.4",
"source": {
"type": "git",
"url": "https://github.com/sensiolabs/security-checker.git",
- "reference": "56bded66985e22f6eac2cf86735fd21c625bff2f"
+ "reference": "9e69eddf3bc49d1ee5c7908564da3141796d4bbc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sensiolabs/security-checker/zipball/56bded66985e22f6eac2cf86735fd21c625bff2f",
- "reference": "56bded66985e22f6eac2cf86735fd21c625bff2f",
+ "url": "https://api.github.com/repos/sensiolabs/security-checker/zipball/9e69eddf3bc49d1ee5c7908564da3141796d4bbc",
+ "reference": "9e69eddf3bc49d1ee5c7908564da3141796d4bbc",
"shasum": ""
},
"require": {
+ "composer/ca-bundle": "^1.0",
"symfony/console": "~2.7|~3.0"
},
"bin": [
@@ -2780,7 +2840,7 @@
}
],
"description": "A security checker for your composer.lock",
- "time": "2017-03-09T17:33:20+00:00"
+ "time": "2017-03-31T14:50:32+00:00"
},
{
"name": "swiftmailer/swiftmailer",
@@ -2838,16 +2898,16 @@
},
{
"name": "symfony/console",
- "version": "v3.2.6",
+ "version": "v3.2.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "28fb243a2b5727774ca309ec2d92da240f1af0dd"
+ "reference": "c30243cc51f726812be3551316b109a2f5deaf8d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/28fb243a2b5727774ca309ec2d92da240f1af0dd",
- "reference": "28fb243a2b5727774ca309ec2d92da240f1af0dd",
+ "url": "https://api.github.com/repos/symfony/console/zipball/c30243cc51f726812be3551316b109a2f5deaf8d",
+ "reference": "c30243cc51f726812be3551316b109a2f5deaf8d",
"shasum": ""
},
"require": {
@@ -2897,11 +2957,11 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
- "time": "2017-03-06T19:30:27+00:00"
+ "time": "2017-04-04T14:33:42+00:00"
},
{
"name": "symfony/css-selector",
- "version": "v3.2.6",
+ "version": "v3.2.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
@@ -2954,16 +3014,16 @@
},
{
"name": "symfony/debug",
- "version": "v3.2.6",
+ "version": "v3.2.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug.git",
- "reference": "b90c9f91ad8ac37d9f114e369042d3226b34dc1a"
+ "reference": "56f613406446a4a0a031475cfd0a01751de22659"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/debug/zipball/b90c9f91ad8ac37d9f114e369042d3226b34dc1a",
- "reference": "b90c9f91ad8ac37d9f114e369042d3226b34dc1a",
+ "url": "https://api.github.com/repos/symfony/debug/zipball/56f613406446a4a0a031475cfd0a01751de22659",
+ "reference": "56f613406446a4a0a031475cfd0a01751de22659",
"shasum": ""
},
"require": {
@@ -3007,20 +3067,20 @@
],
"description": "Symfony Debug Component",
"homepage": "https://symfony.com",
- "time": "2017-02-18T17:28:00+00:00"
+ "time": "2017-03-28T21:38:24+00:00"
},
{
"name": "symfony/event-dispatcher",
- "version": "v3.2.6",
+ "version": "v3.2.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "b7a1b9e0a0f623ce43b4c8d775eb138f190c9d8d"
+ "reference": "154bb1ef7b0e42ccc792bd53edbce18ed73440ca"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b7a1b9e0a0f623ce43b4c8d775eb138f190c9d8d",
- "reference": "b7a1b9e0a0f623ce43b4c8d775eb138f190c9d8d",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/154bb1ef7b0e42ccc792bd53edbce18ed73440ca",
+ "reference": "154bb1ef7b0e42ccc792bd53edbce18ed73440ca",
"shasum": ""
},
"require": {
@@ -3067,20 +3127,20 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
- "time": "2017-02-21T09:12:04+00:00"
+ "time": "2017-04-04T07:26:27+00:00"
},
{
"name": "symfony/finder",
- "version": "v3.2.6",
+ "version": "v3.2.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "92d7476d2df60cd851a3e13e078664b1deb8ce10"
+ "reference": "b20900ce5ea164cd9314af52725b0bb5a758217a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/92d7476d2df60cd851a3e13e078664b1deb8ce10",
- "reference": "92d7476d2df60cd851a3e13e078664b1deb8ce10",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/b20900ce5ea164cd9314af52725b0bb5a758217a",
+ "reference": "b20900ce5ea164cd9314af52725b0bb5a758217a",
"shasum": ""
},
"require": {
@@ -3116,20 +3176,20 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
- "time": "2017-02-21T09:12:04+00:00"
+ "time": "2017-03-20T09:32:19+00:00"
},
{
"name": "symfony/http-foundation",
- "version": "v3.2.6",
+ "version": "v3.2.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "c57009887010eb4e58bfca2970314a5b820b24b9"
+ "reference": "cb0b6418f588952c9290b3df4ca650f1b7ab570a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/c57009887010eb4e58bfca2970314a5b820b24b9",
- "reference": "c57009887010eb4e58bfca2970314a5b820b24b9",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/cb0b6418f588952c9290b3df4ca650f1b7ab570a",
+ "reference": "cb0b6418f588952c9290b3df4ca650f1b7ab570a",
"shasum": ""
},
"require": {
@@ -3169,20 +3229,20 @@
],
"description": "Symfony HttpFoundation Component",
"homepage": "https://symfony.com",
- "time": "2017-03-04T12:23:14+00:00"
+ "time": "2017-04-04T15:30:56+00:00"
},
{
"name": "symfony/http-kernel",
- "version": "v3.2.6",
+ "version": "v3.2.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "bc909e85b8585c9edf043d0fca871308c41bb9b4"
+ "reference": "8285ab5faf1306b1a5ebcf287fe91c231a6de88e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/bc909e85b8585c9edf043d0fca871308c41bb9b4",
- "reference": "bc909e85b8585c9edf043d0fca871308c41bb9b4",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/8285ab5faf1306b1a5ebcf287fe91c231a6de88e",
+ "reference": "8285ab5faf1306b1a5ebcf287fe91c231a6de88e",
"shasum": ""
},
"require": {
@@ -3251,7 +3311,7 @@
],
"description": "Symfony HttpKernel Component",
"homepage": "https://symfony.com",
- "time": "2017-03-10T18:35:31+00:00"
+ "time": "2017-04-05T12:52:03+00:00"
},
{
"name": "symfony/polyfill-mbstring",
@@ -3314,16 +3374,16 @@
},
{
"name": "symfony/process",
- "version": "v3.2.6",
+ "version": "v3.2.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "68bfa8c83f24c0ac04ea7193bcdcda4519f41892"
+ "reference": "57fdaa55827ae14d617550ebe71a820f0a5e2282"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/68bfa8c83f24c0ac04ea7193bcdcda4519f41892",
- "reference": "68bfa8c83f24c0ac04ea7193bcdcda4519f41892",
+ "url": "https://api.github.com/repos/symfony/process/zipball/57fdaa55827ae14d617550ebe71a820f0a5e2282",
+ "reference": "57fdaa55827ae14d617550ebe71a820f0a5e2282",
"shasum": ""
},
"require": {
@@ -3359,11 +3419,11 @@
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
- "time": "2017-03-04T12:23:14+00:00"
+ "time": "2017-03-27T18:07:02+00:00"
},
{
"name": "symfony/routing",
- "version": "v3.2.6",
+ "version": "v3.2.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
@@ -3438,16 +3498,16 @@
},
{
"name": "symfony/translation",
- "version": "v3.2.6",
+ "version": "v3.2.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "0e1b15ce8fbf3890f4ccdac430ed5e07fdfe0690"
+ "reference": "c740eee70783d2af4d3d6b70d5146f209e6b4d13"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/0e1b15ce8fbf3890f4ccdac430ed5e07fdfe0690",
- "reference": "0e1b15ce8fbf3890f4ccdac430ed5e07fdfe0690",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/c740eee70783d2af4d3d6b70d5146f209e6b4d13",
+ "reference": "c740eee70783d2af4d3d6b70d5146f209e6b4d13",
"shasum": ""
},
"require": {
@@ -3498,20 +3558,20 @@
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
- "time": "2017-03-04T12:23:14+00:00"
+ "time": "2017-03-21T21:44:32+00:00"
},
{
"name": "symfony/var-dumper",
- "version": "v3.2.6",
+ "version": "v3.2.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "4100f347aff890bc16b0b4b42843b599db257b2d"
+ "reference": "81dce20f69a8b40427e1f4e6462178df87cafc03"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/4100f347aff890bc16b0b4b42843b599db257b2d",
- "reference": "4100f347aff890bc16b0b4b42843b599db257b2d",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/81dce20f69a8b40427e1f4e6462178df87cafc03",
+ "reference": "81dce20f69a8b40427e1f4e6462178df87cafc03",
"shasum": ""
},
"require": {
@@ -3564,7 +3624,7 @@
"debug",
"dump"
],
- "time": "2017-02-20T13:45:48+00:00"
+ "time": "2017-03-12T16:07:05+00:00"
},
{
"name": "themattharris/tmhoauth",
@@ -4053,16 +4113,16 @@
},
{
"name": "laravel/dusk",
- "version": "v1.0.10",
+ "version": "v1.0.12",
"source": {
"type": "git",
"url": "https://github.com/laravel/dusk.git",
- "reference": "11537ac1a939a2194e9e3cdc2536e6e34eff9ea6"
+ "reference": "9a150bedc3ae6566d05f0a8d1657c658ce98dcad"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/dusk/zipball/11537ac1a939a2194e9e3cdc2536e6e34eff9ea6",
- "reference": "11537ac1a939a2194e9e3cdc2536e6e34eff9ea6",
+ "url": "https://api.github.com/repos/laravel/dusk/zipball/9a150bedc3ae6566d05f0a8d1657c658ce98dcad",
+ "reference": "9a150bedc3ae6566d05f0a8d1657c658ce98dcad",
"shasum": ""
},
"require": {
@@ -4104,7 +4164,7 @@
"testing",
"webdriver"
],
- "time": "2017-03-03T14:36:19+00:00"
+ "time": "2017-04-07T21:47:06+00:00"
},
{
"name": "maximebf/debugbar",
@@ -4234,16 +4294,16 @@
},
{
"name": "myclabs/deep-copy",
- "version": "1.6.0",
+ "version": "1.6.1",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
- "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe"
+ "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/5a5a9fc8025a08d8919be87d6884d5a92520cefe",
- "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8e6e04167378abf1ddb4d3522d8755c5fd90d102",
+ "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102",
"shasum": ""
},
"require": {
@@ -4272,7 +4332,7 @@
"object",
"object graph"
],
- "time": "2017-01-26T22:05:40+00:00"
+ "time": "2017-04-12T18:52:22+00:00"
},
{
"name": "phpdocumentor/reflection-common",
@@ -4485,16 +4545,16 @@
},
{
"name": "phpunit/php-code-coverage",
- "version": "4.0.7",
+ "version": "4.0.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "09e2277d14ea467e5a984010f501343ef29ffc69"
+ "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/09e2277d14ea467e5a984010f501343ef29ffc69",
- "reference": "09e2277d14ea467e5a984010f501343ef29ffc69",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d",
+ "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d",
"shasum": ""
},
"require": {
@@ -4544,7 +4604,7 @@
"testing",
"xunit"
],
- "time": "2017-03-01T09:12:17+00:00"
+ "time": "2017-04-02T07:44:40+00:00"
},
{
"name": "phpunit/php-file-iterator",
@@ -4734,16 +4794,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "5.7.17",
+ "version": "5.7.19",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "68752b665d3875f9a38a357e3ecb35c79f8673bf"
+ "reference": "69c4f49ff376af2692bad9cebd883d17ebaa98a1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/68752b665d3875f9a38a357e3ecb35c79f8673bf",
- "reference": "68752b665d3875f9a38a357e3ecb35c79f8673bf",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/69c4f49ff376af2692bad9cebd883d17ebaa98a1",
+ "reference": "69c4f49ff376af2692bad9cebd883d17ebaa98a1",
"shasum": ""
},
"require": {
@@ -4812,7 +4872,7 @@
"testing",
"xunit"
],
- "time": "2017-03-19T16:52:12+00:00"
+ "time": "2017-04-03T02:22:27+00:00"
},
{
"name": "phpunit/phpunit-mock-objects",
@@ -5477,16 +5537,16 @@
},
{
"name": "symfony/yaml",
- "version": "v3.2.6",
+ "version": "v3.2.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "093e416ad096355149e265ea2e4cc1f9ee40ab1a"
+ "reference": "62b4cdb99d52cb1ff253c465eb1532a80cebb621"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/093e416ad096355149e265ea2e4cc1f9ee40ab1a",
- "reference": "093e416ad096355149e265ea2e4cc1f9ee40ab1a",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/62b4cdb99d52cb1ff253c465eb1532a80cebb621",
+ "reference": "62b4cdb99d52cb1ff253c465eb1532a80cebb621",
"shasum": ""
},
"require": {
@@ -5528,20 +5588,20 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
- "time": "2017-03-07T16:47:02+00:00"
+ "time": "2017-03-20T09:45:15+00:00"
},
{
"name": "theseer/fdomdocument",
- "version": "1.6.1",
+ "version": "1.6.4",
"source": {
"type": "git",
"url": "https://github.com/theseer/fDOMDocument.git",
- "reference": "d9ad139d6c2e8edf5e313ffbe37ff13344cf0684"
+ "reference": "cf219ede922fb47956726f35e2127277ebd302ca"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/d9ad139d6c2e8edf5e313ffbe37ff13344cf0684",
- "reference": "d9ad139d6c2e8edf5e313ffbe37ff13344cf0684",
+ "url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/cf219ede922fb47956726f35e2127277ebd302ca",
+ "reference": "cf219ede922fb47956726f35e2127277ebd302ca",
"shasum": ""
},
"require": {
@@ -5568,7 +5628,7 @@
],
"description": "The classes contained within this repository extend the standard DOM to use exceptions at all occasions of errors instead of PHP warnings or notices. They also add various custom methods and shortcuts for convenience and to simplify the usage of DOM.",
"homepage": "https://github.com/theseer/fDOMDocument",
- "time": "2015-05-27T22:58:02+00:00"
+ "time": "2017-04-17T09:08:13+00:00"
},
{
"name": "webmozart/assert",
diff --git a/database/migrations/2017_03_28_130855_create_indie_web_users_table.php b/database/migrations/2017_03_28_130855_create_indie_web_users_table.php
new file mode 100644
index 00000000..2fec7645
--- /dev/null
+++ b/database/migrations/2017_03_28_130855_create_indie_web_users_table.php
@@ -0,0 +1,36 @@
+increments('id');
+ $table->string('me')->unique();
+ $table->text('token')->nullable();
+ $table->string('syntax')->default('json');
+ $table->jsonb('syndication')->nullable();
+ $table->string('mediaEndpoint')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('indie_web_users');
+ }
+}
diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php
index 7decf629..8e906692 100644
--- a/database/seeds/DatabaseSeeder.php
+++ b/database/seeds/DatabaseSeeder.php
@@ -17,5 +17,6 @@ class DatabaseSeeder extends Seeder
$this->call(PlacesTableSeeder::class);
$this->call(NotesTableSeeder::class);
$this->call(WebMentionsTableSeeder::class);
+ $this->call(IndieWebUserTableSeeder::class);
}
}
diff --git a/database/seeds/IndieWebUserTableSeeder.php b/database/seeds/IndieWebUserTableSeeder.php
new file mode 100644
index 00000000..1f6d11c7
--- /dev/null
+++ b/database/seeds/IndieWebUserTableSeeder.php
@@ -0,0 +1,16 @@
+ config('app.url')]);
+ }
+}
diff --git a/public/assets/js/maps.js.br b/public/assets/js/maps.js.br
index e33fccb9..322ca078 100644
Binary files a/public/assets/js/maps.js.br and b/public/assets/js/maps.js.br differ
diff --git a/public/assets/js/maps.js.gz b/public/assets/js/maps.js.gz
index 9d1006d8..07aa158d 100644
Binary files a/public/assets/js/maps.js.gz and b/public/assets/js/maps.js.gz differ
diff --git a/public/assets/js/newnote.js.br b/public/assets/js/newnote.js.br
index d0640178..e50fcb08 100644
Binary files a/public/assets/js/newnote.js.br and b/public/assets/js/newnote.js.br differ
diff --git a/public/assets/js/newnote.js.gz b/public/assets/js/newnote.js.gz
index a22d5ee3..8d481a8e 100644
Binary files a/public/assets/js/newnote.js.gz and b/public/assets/js/newnote.js.gz differ
diff --git a/public/assets/prism/prism.css b/public/assets/prism/prism.css
index 86122e46..eec8b864 100644
--- a/public/assets/prism/prism.css
+++ b/public/assets/prism/prism.css
@@ -1,84 +1,79 @@
-/* http://prismjs.com/download.html?themes=prism-dark&languages=markup+css+clike+javascript+git+http+markdown+php+php-extras+scss+sql&plugins=line-numbers+show-invisibles */
+/* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript+bash+c+csharp+cpp+ruby+css-extras+diff+git+go+http+ini+json+latex+lua+makefile+markdown+nginx+objectivec+php+php-extras+python+rust+sass+scss+sql+swift+vim+wiki+yaml&plugins=line-numbers+autolinker */
/**
- * prism.js Dark theme for JavaScript, CSS and HTML
- * Based on the slides of the talk “/Reg(exp){2}lained/”
- * @author Lea Verou
+ * okaidia theme for JavaScript, CSS and HTML
+ * Loosely based on Monokai textmate theme by http://www.monokai.nl/
+ * @author ocodia
*/
code[class*="language-"],
pre[class*="language-"] {
- color: white;
- text-shadow: 0 -.1em .2em black;
- font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
- direction: ltr;
- text-align: left;
- white-space: pre;
- word-spacing: normal;
- word-break: normal;
- line-height: 1.5;
+ color: #f8f8f2;
+ background: none;
+ text-shadow: 0 1px rgba(0, 0, 0, 0.3);
+ font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+ text-align: left;
+ white-space: pre;
+ word-spacing: normal;
+ word-break: normal;
+ word-wrap: normal;
+ line-height: 1.5;
- -moz-tab-size: 4;
- -o-tab-size: 4;
- tab-size: 4;
+ -moz-tab-size: 4;
+ -o-tab-size: 4;
+ tab-size: 4;
- -webkit-hyphens: none;
- -moz-hyphens: none;
- -ms-hyphens: none;
- hyphens: none;
-}
-
-@media print {
- code[class*="language-"],
- pre[class*="language-"] {
- text-shadow: none;
- }
-}
-
-pre[class*="language-"],
-:not(pre) > code[class*="language-"] {
- background: hsl(30, 20%, 25%);
+ -webkit-hyphens: none;
+ -moz-hyphens: none;
+ -ms-hyphens: none;
+ hyphens: none;
}
/* Code blocks */
pre[class*="language-"] {
- padding: 1em;
- margin: .5em 0;
- overflow: auto;
- border: .3em solid hsl(30, 20%, 40%);
- border-radius: .5em;
- box-shadow: 1px 1px .5em black inset;
+ padding: 1em;
+ margin: .5em 0;
+ overflow: auto;
+ border-radius: 0.3em;
+}
+
+:not(pre) > code[class*="language-"],
+pre[class*="language-"] {
+ background: #272822;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
- padding: .15em .2em .05em;
- border-radius: .3em;
- border: .13em solid hsl(30, 20%, 40%);
- box-shadow: 1px 1px .3em -.1em black inset;
+ padding: .1em;
+ border-radius: .3em;
+ white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
- color: hsl(30, 20%, 50%);
+ color: slategray;
}
.token.punctuation {
- opacity: .7;
+ color: #f8f8f2;
}
.namespace {
- opacity: .7;
+ opacity: .7;
}
.token.property,
.token.tag,
-.token.boolean,
-.token.number,
.token.constant,
-.token.symbol {
- color: hsl(350, 40%, 70%);
+.token.symbol,
+.token.deleted {
+ color: #f92672;
+}
+
+.token.boolean,
+.token.number {
+ color: #ae81ff;
}
.token.selector,
@@ -87,7 +82,7 @@ pre[class*="language-"] {
.token.char,
.token.builtin,
.token.inserted {
- color: hsl(75, 70%, 60%);
+ color: #a6e22e;
}
.token.operator,
@@ -96,93 +91,76 @@ pre[class*="language-"] {
.language-css .token.string,
.style .token.string,
.token.variable {
- color: hsl(40, 90%, 60%);
+ color: #f8f8f2;
}
.token.atrule,
.token.attr-value,
+.token.function {
+ color: #e6db74;
+}
+
.token.keyword {
- color: hsl(350, 40%, 70%);
+ color: #66d9ef;
}
.token.regex,
.token.important {
- color: #e90;
+ color: #fd971f;
}
.token.important,
.token.bold {
- font-weight: bold;
+ font-weight: bold;
}
.token.italic {
- font-style: italic;
+ font-style: italic;
}
.token.entity {
- cursor: help;
-}
-
-.token.deleted {
- color: red;
+ cursor: help;
}
pre.line-numbers {
- position: relative;
- padding-left: 3.8em;
- counter-reset: linenumber;
+ position: relative;
+ padding-left: 3.8em;
+ counter-reset: linenumber;
}
pre.line-numbers > code {
- position: relative;
+ position: relative;
}
.line-numbers .line-numbers-rows {
- position: absolute;
- pointer-events: none;
- top: 0;
- font-size: 100%;
- left: -3.8em;
- width: 3em; /* works for line-numbers below 1000 lines */
- letter-spacing: -1px;
- border-right: 1px solid #999;
+ position: absolute;
+ pointer-events: none;
+ top: 0;
+ font-size: 100%;
+ left: -3.8em;
+ width: 3em; /* works for line-numbers below 1000 lines */
+ letter-spacing: -1px;
+ border-right: 1px solid #999;
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
}
- .line-numbers-rows > span {
- pointer-events: none;
- display: block;
- counter-increment: linenumber;
- }
+ .line-numbers-rows > span {
+ pointer-events: none;
+ display: block;
+ counter-increment: linenumber;
+ }
- .line-numbers-rows > span:before {
- content: counter(linenumber);
- color: #999;
- display: block;
- padding-right: 0.8em;
- text-align: right;
- }
-.token.tab:not(:empty):before,
-.token.cr:before,
-.token.lf:before {
- color: hsl(24, 20%, 85%);
-}
-
-.token.tab:not(:empty):before {
- content: '\21E5';
-}
-
-.token.cr:before {
- content: '\240D';
-}
-
-.token.crlf:before {
- content: '\240D\240A';
-}
-.token.lf:before {
- content: '\240A';
+ .line-numbers-rows > span:before {
+ content: counter(linenumber);
+ color: #999;
+ display: block;
+ padding-right: 0.8em;
+ text-align: right;
+ }
+.token a {
+ color: inherit;
}
diff --git a/public/assets/prism/prism.css.br b/public/assets/prism/prism.css.br
index f381a5e3..76b56929 100644
Binary files a/public/assets/prism/prism.css.br and b/public/assets/prism/prism.css.br differ
diff --git a/public/assets/prism/prism.css.gz b/public/assets/prism/prism.css.gz
index af19bb4c..156b0d07 100644
Binary files a/public/assets/prism/prism.css.gz and b/public/assets/prism/prism.css.gz differ
diff --git a/public/assets/prism/prism.js b/public/assets/prism/prism.js
index b00d099e..8510bb39 100644
--- a/public/assets/prism/prism.js
+++ b/public/assets/prism/prism.js
@@ -1,15 +1,37 @@
-/* http://prismjs.com/download.html?themes=prism-dark&languages=markup+css+clike+javascript+git+http+markdown+php+php-extras+scss+sql&plugins=line-numbers+show-invisibles */
-var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=_self.Prism={util:{encode:function(e){return e instanceof n?new n(e.type,t.util.encode(e.content),e.alias):"Array"===t.util.type(e)?e.map(t.util.encode):e.replace(/&/g,"&").replace(/e.length)break e;if(!(d instanceof a)){u.lastIndex=0;var m=u.exec(d);if(m){c&&(f=m[1].length);var y=m.index-1+f,m=m[0].slice(f),v=m.length,k=y+v,b=d.slice(0,y+1),w=d.slice(k+1),N=[p,1];b&&N.push(b);var O=new a(l,g?t.tokenize(m,g):m,h);N.push(O),w&&N.push(w),Array.prototype.splice.apply(r,N)}}}}}return r},hooks:{all:{},add:function(e,n){var a=t.hooks.all;a[e]=a[e]||[],a[e].push(n)},run:function(e,n){var a=t.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(n)}}},n=t.Token=function(e,t,n){this.type=e,this.content=t,this.alias=n};if(n.stringify=function(e,a,r){if("string"==typeof e)return e;if("Array"===t.util.type(e))return e.map(function(t){return n.stringify(t,a,e)}).join("");var i={type:e.type,content:n.stringify(e.content,a,r),tag:"span",classes:["token",e.type],attributes:{},language:a,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===t.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}t.hooks.run("wrap",i);var o="";for(var s in i.attributes)o+=s+'="'+(i.attributes[s]||"")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'" '+o+">"+i.content+""+i.tag+">"},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var n=JSON.parse(e.data),a=n.language,r=n.code;_self.postMessage(JSON.stringify(t.util.encode(t.tokenize(r,t.languages[a])))),_self.close()},!1),_self.Prism):_self.Prism;var a=document.getElementsByTagName("script");return a=a[a.length-1],a&&(t.filename=a.src,document.addEventListener&&!a.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism);;
-Prism.languages.markup={comment://,prolog:/<\?[\w\W]+?\?>/,doctype://,cdata://i,tag:{pattern:/<\/?[^\s>\/]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,inside:{punctuation:/[=>"']/}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/?[\da-z]{1,8};/i},Prism.hooks.add("wrap",function(t){"entity"===t.type&&(t.attributes.title=t.content.replace(/&/,"&"))});;
-Prism.languages.css={comment:/\/\*[\w\W]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*?(?=\s*\{)/,string:/("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/,property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},Prism.languages.css.atrule.inside.rest=Prism.util.clone(Prism.languages.css),Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/