General tidy up

merge-requests/5/head
Terence Eden 2024-02-26 19:56:55 +00:00
rodzic cd145915be
commit 195be1afea
1 zmienionych plików z 51 dodań i 45 usunięć

Wyświetl plik

@ -18,7 +18,9 @@
*/ */
// Preamble: Set your details here // Preamble: Set your details here
// This is where you set up your account's name and bio. You also need to provide a public/private keypair. The posting page is protected with a password that also needs to be set here. // This is where you set up your account's name and bio.
// You also need to provide a public/private keypair.
// The posting page is protected with a password that also needs to be set here.
// Set up the Actor's information // Set up the Actor's information
// Edit these: // Edit these:
@ -52,13 +54,15 @@
$bodyData = print_r( $body, true ); $bodyData = print_r( $body, true );
$requestData = print_r( $_REQUEST, true ); $requestData = print_r( $_REQUEST, true );
$serverData = print_r( $_SERVER, true ); $serverData = print_r( $_SERVER, true );
!empty( $_GET["path"] ) ? $path = $_GET["path"] : $path = "/";
// Get the type of request - used in the log filename // Get the type of request - used in the log filename
if ( isset( $body["type"] ) ) { if ( isset( $body["type"] ) ) {
// Sanitise type to only include letter // Sanitise before using it in a filename
$type = " " . preg_replace( '/[^a-zA-Z]/', '', $body["type"] ); $type = " " . urlencode( $body["type"] );
} else { } else {
$type = ""; // Sanitise the path requested
$type = " " . urlencode( $path );
} }
// Create a timestamp for the filename // Create a timestamp for the filename
@ -85,7 +89,6 @@
// Routing: // Routing:
// The .htaccess changes /whatever to /?path=whatever // The .htaccess changes /whatever to /?path=whatever
// This runs the function of the path requested. // This runs the function of the path requested.
!empty( $_GET["path"] ) ? $path = $_GET["path"] : home();
switch ($path) { switch ($path) {
case ".well-known/webfinger": case ".well-known/webfinger":
webfinger(); // Mandatory. Static. webfinger(); // Mandatory. Static.
@ -94,20 +97,22 @@
case "following": case "following":
following(); // Mandatory. Static following(); // Mandatory. Static
case "followers": case "followers":
followers(); // Mandatory. Could be dynamic followers(); // Mandatory. Can be dynamic
case "inbox": case "inbox":
inbox(); // Mandatory. Only accepts follow requests. inbox(); // Mandatory. Only accepts follow requests.
case "write": case "write":
write(); // User interface for writing posts write(); // User interface for writing posts
case "send": // API for posting content to the Fediverse case "send":
send(); send(); // API for posting content to the Fediverse
case "outbox": // Optional. Dynamic. case "outbox":
outbox(); outbox(); // Optional. Dynamic.
case "/":
home(); // Optional. Can be dynamic
default: default:
die(); die();
} }
// The [WebFinger Protocol](https://docs.joinmastodon.org/spec/webfinger/) is used to identify accounts. // The WebFinger Protocol is used to identify accounts.
// It is requested with `example.com/.well-known/webfinger?resource=acct:username@example.com` // It is requested with `example.com/.well-known/webfinger?resource=acct:username@example.com`
// This server only has one user, so it ignores the query string and always returns the same details. // This server only has one user, so it ignores the query string and always returns the same details.
function webfinger() { function webfinger() {
@ -150,7 +155,7 @@
"url" => "https://{$server}/{$username}", "url" => "https://{$server}/{$username}",
"manuallyApprovesFollowers" => true, "manuallyApprovesFollowers" => true,
"discoverable" => true, "discoverable" => true,
"published" => "2024-02-12T11:51:00Z", "published" => "2024-02-29T12:34:00Z",
"icon" => [ "icon" => [
"type" => "Image", "type" => "Image",
"mediaType" => "image/png", "mediaType" => "image/png",
@ -210,8 +215,7 @@
// The `/inbox` is the main server. It receives all requests. // The `/inbox` is the main server. It receives all requests.
// This server only responds to "Follow" requests. // This server only responds to "Follow" requests.
// A remote server sends a follow request which is a JSON file saying who they are. // A remote server sends a follow request which is a JSON file saying who they are.
// This code does not cryptographically validate the headers of the received message. // The details of the remote user's server is saved to a file so that future messages can be delivered to the follower.
// The name of the remote user's server is saved to a file so that future messages can be delivered to it.
// An accept request is cryptographically signed and POST'd back to the remote server. // An accept request is cryptographically signed and POST'd back to the remote server.
function inbox() { function inbox() {
global $body, $server, $username, $key_private; global $body, $server, $username, $key_private;
@ -228,8 +232,8 @@
if ( "Follow" != $inbox_type ) { die(); } if ( "Follow" != $inbox_type ) { die(); }
// Get the parameters // Get the parameters
$follower_id = $inbox_message["id"]; $follower_id = $inbox_message["id"]; // E.g. https://mastodon.social/(unique id)
$follower_actor = $inbox_message["actor"]; $follower_actor = $inbox_message["actor"]; // E.g. https://mastodon.social/users/Edent
$follower_host = parse_url( $follower_actor, PHP_URL_HOST ); $follower_host = parse_url( $follower_actor, PHP_URL_HOST );
$follower_path = parse_url( $follower_actor, PHP_URL_PATH ); $follower_path = parse_url( $follower_actor, PHP_URL_PATH );
@ -242,7 +246,7 @@
// Request the JSON representation of the the user // Request the JSON representation of the the user
$ch = curl_init( $follower_actor ); $ch = curl_init( $follower_actor );
// Get the signed headers // Generate signed headers for this request
$headers = generate_signed_headers( null, $follower_host, $follower_path, "GET" ); $headers = generate_signed_headers( null, $follower_host, $follower_path, "GET" );
// Set cURL options // Set cURL options
@ -330,7 +334,8 @@
// Headers: // Headers:
// Every message that your server sends needs to be cryptographically signed with your Private Key. // Every message that your server sends needs to be cryptographically signed with your Private Key.
// This is a complicated process. Please read https://blog.joinmastodon.org/2018/07/how-to-make-friends-and-verify-requests/ for more information. // This is a complicated process.
// Please read https://blog.joinmastodon.org/2018/07/how-to-make-friends-and-verify-requests/ for more information.
function generate_signed_headers( $message, $host, $path, $method ) { function generate_signed_headers( $message, $host, $path, $method ) {
global $server, $username, $key_private; global $server, $username, $key_private;
@ -344,7 +349,6 @@
$date = date( "D, d M Y H:i:s \G\M\T" ); $date = date( "D, d M Y H:i:s \G\M\T" );
// There are subtly different signing requirements for POST and GET // There are subtly different signing requirements for POST and GET
// GET does not require a digest
if ( "POST" == $method ) { if ( "POST" == $method ) {
// Encode the message object to JSON // Encode the message object to JSON
$message_json = json_encode( $message ); $message_json = json_encode( $message );
@ -369,7 +373,7 @@
// Full signature header // Full signature header
$signature_header = 'keyId="' . $keyId . '",algorithm="rsa-sha256",headers="(request-target) host date digest",signature="' . $signature_b64 . '"'; $signature_header = 'keyId="' . $keyId . '",algorithm="rsa-sha256",headers="(request-target) host date digest",signature="' . $signature_b64 . '"';
// Header for POST reply // Header for POST request
$headers = array( $headers = array(
"Host: {$host}", "Host: {$host}",
"Date: {$date}", "Date: {$date}",
@ -396,7 +400,7 @@
// Full signature header // Full signature header
$signature_header = 'keyId="' . $keyId . '",algorithm="rsa-sha256",headers="(request-target) host date",signature="' . $signature_b64 . '"'; $signature_header = 'keyId="' . $keyId . '",algorithm="rsa-sha256",headers="(request-target) host date",signature="' . $signature_b64 . '"';
// Header for POST reply // Header for GET request
$headers = array( $headers = array(
"Host: {$host}", "Host: {$host}",
"Date: {$date}", "Date: {$date}",
@ -435,8 +439,10 @@ echo <<< HTML
<p>This site is a basic <a href="https://www.w3.org/TR/activitypub/">ActivityPub</a> server designed to be <a href="https://shkspr.mobi/blog/2024/02/activitypub-server-in-a-single-file/">a lightweight educational tool</a>.</p> <p>This site is a basic <a href="https://www.w3.org/TR/activitypub/">ActivityPub</a> server designed to be <a href="https://shkspr.mobi/blog/2024/02/activitypub-server-in-a-single-file/">a lightweight educational tool</a>.</p>
<ul> <ul>
HTML; HTML;
// Get all posts // Get all posts, most recent first
$posts = array_reverse( glob("posts/*.json") ); $posts = array_reverse( glob("posts/*.json") );
// Loop through the posts
foreach ($posts as $post) { foreach ($posts as $post) {
$postJSON = file_get_contents($post); $postJSON = file_get_contents($post);
$postData = json_decode($postJSON, true); $postData = json_decode($postJSON, true);
@ -449,6 +455,7 @@ HTML;
} else { } else {
$postImg = ""; $postImg = "";
} }
// Display the post
echo "<li><a href='/{$post}'><time datetime='{$postTime}'>$postTime</time></a><br>{$postHTML}<br>{$postImg}</li>"; echo "<li><a href='/{$post}'><time datetime='{$postTime}'>$postTime</time></a><br>{$postHTML}<br>{$postImg}</li>";
} }
echo <<< HTML echo <<< HTML
@ -523,9 +530,7 @@ HTML;
// Move media to the correct location // Move media to the correct location
// Create a directory if it doesn't exist // Create a directory if it doesn't exist
if( ! is_dir( $image_path ) ) { if( ! is_dir( $image_path ) ) { mkdir( $image_path ); }
mkdir( $image_path );
}
move_uploaded_file( $image, $image_full_path ); move_uploaded_file( $image, $image_full_path );
// Get the alt text // Get the alt text
@ -601,7 +606,8 @@ HTML;
foreach ( $followers as $follower ) { foreach ( $followers as $follower ) {
$follower_info = json_decode( file_get_contents( $follower ), true ); $follower_info = json_decode( file_get_contents( $follower ), true );
// Shared inboxes // Some servers have "Shared inboxes"
// If you have lots of followers on a single server, you only need to send the message once
if( isset( $follower_info["endpoints"]["sharedInbox"] ) ) { if( isset( $follower_info["endpoints"]["sharedInbox"] ) ) {
$sharedInbox = $follower_info["endpoints"]["sharedInbox"]; $sharedInbox = $follower_info["endpoints"]["sharedInbox"];
if ( !in_array( $sharedInbox, $inboxes ) ) { if ( !in_array( $sharedInbox, $inboxes ) ) {
@ -617,6 +623,7 @@ HTML;
} }
// Prepare to use the multiple cURL handle // Prepare to use the multiple cURL handle
// This makes it more efficient to send many simultaneous messages
$mh = curl_multi_init(); $mh = curl_multi_init();
// Loop through all the inboxes of the followers // Loop through all the inboxes of the followers
@ -716,7 +723,6 @@ HTML;
// Add HTML links to usernames // Add HTML links to usernames
$username_link = "<a href=\"https://{$domain}/@{$user}\">$username</a>"; $username_link = "<a href=\"https://{$domain}/@{$user}\">$username</a>";
$content = str_replace( $username, $username_link, $content ); $content = str_replace( $username, $username_link, $content );
} }
// Construct HTML breaks from carriage returns and line breaks // Construct HTML breaks from carriage returns and line breaks
@ -738,7 +744,7 @@ HTML;
global $server, $username; global $server, $username;
// Get all posts // Get all posts
$posts = array_reverse( glob("posts/" . "*.json") ); $posts = array_reverse( glob("posts/*.json") );
// Number of posts // Number of posts
$totalItems = count( $posts ); $totalItems = count( $posts );
// Create an ordered list // Create an ordered list
@ -872,7 +878,7 @@ HTML;
$signatureString = trim( $signatureString ); $signatureString = trim( $signatureString );
// Get the Public Key // Get the Public Key
// The link to the key may be sent with the body, but is always sent in the Signature header. // The link to the key might be sent with the body, but is always sent in the Signature header.
$publicKeyURL = $signatureParts["keyId"]; $publicKeyURL = $signatureParts["keyId"];
// This is usually in the form `https://example.com/user/username#main-key` // This is usually in the form `https://example.com/user/username#main-key`