Initial upload
rodzic
d9bad68be1
commit
911ba84220
|
@ -0,0 +1,12 @@
|
|||
Options -Indexes
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteBase /
|
||||
|
||||
# Redirect all paths to queries
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^(.*)$ index.php?path=$1 [QSA,L]
|
||||
</IfModule>
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
THE CRAPL v0 BETA 1
|
||||
|
||||
|
||||
0. Information about the CRAPL
|
||||
|
||||
If you have questions or concerns about the CRAPL, or you need more
|
||||
information about this license, please contact:
|
||||
|
||||
Matthew Might
|
||||
http://matt.might.net/
|
||||
|
||||
|
||||
I. Preamble
|
||||
|
||||
Science thrives on openness.
|
||||
|
||||
In modern science, it is often infeasible to replicate claims without
|
||||
access to the software underlying those claims.
|
||||
|
||||
Let's all be honest: when scientists write code, aesthetics and
|
||||
software engineering principles take a back seat to having running,
|
||||
working code before a deadline.
|
||||
|
||||
So, let's release the ugly. And, let's be proud of that.
|
||||
|
||||
|
||||
II. Definitions
|
||||
|
||||
1. "This License" refers to version 0 beta 1 of the Community
|
||||
Research and Academic Programming License (the CRAPL).
|
||||
|
||||
2. "The Program" refers to the medley of source code, shell scripts,
|
||||
executables, objects, libraries and build files supplied to You,
|
||||
or these files as modified by You.
|
||||
|
||||
[Any appearance of design in the Program is purely coincidental and
|
||||
should not in any way be mistaken for evidence of thoughtful
|
||||
software construction.]
|
||||
|
||||
3. "You" refers to the person or persons brave and daft enough to use
|
||||
the Program.
|
||||
|
||||
4. "The Documentation" refers to the Program.
|
||||
|
||||
5. "The Author" probably refers to the caffeine-addled graduate
|
||||
student that got the Program to work moments before a submission
|
||||
deadline.
|
||||
|
||||
|
||||
III. Terms
|
||||
|
||||
1. By reading this sentence, You have agreed to the terms and
|
||||
conditions of this License.
|
||||
|
||||
2. If the Program shows any evidence of having been properly tested
|
||||
or verified, You will disregard this evidence.
|
||||
|
||||
3. You agree to hold the Author free from shame, embarrassment or
|
||||
ridicule for any hacks, kludges or leaps of faith found within the
|
||||
Program.
|
||||
|
||||
4. You recognize that any request for support for the Program will be
|
||||
discarded with extreme prejudice.
|
||||
|
||||
5. The Author reserves all rights to the Program, except for any
|
||||
rights granted under any additional licenses attached to the
|
||||
Program.
|
||||
|
||||
|
||||
IV. Permissions
|
||||
|
||||
1. You are permitted to use the Program to validate published
|
||||
scientific claims.
|
||||
|
||||
2. You are permitted to use the Program to validate scientific claims
|
||||
submitted for peer review, under the condition that You keep
|
||||
modifications to the Program confidential until those claims have
|
||||
been published.
|
||||
|
||||
3. You are permitted to use and/or modify the Program for the
|
||||
validation of novel scientific claims if You make a good-faith
|
||||
attempt to notify the Author of Your work and Your claims prior to
|
||||
submission for publication.
|
||||
|
||||
4. If You publicly release any claims or data that were supported or
|
||||
generated by the Program or a modification thereof, in whole or in
|
||||
part, You will release any inputs supplied to the Program and any
|
||||
modifications You made to the Progam. This License will be in
|
||||
effect for the modified program.
|
||||
|
||||
|
||||
V. Disclaimer of Warranty
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
|
||||
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
|
||||
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
|
||||
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
|
||||
CORRECTION.
|
||||
|
||||
|
||||
VI. Limitation of Liability
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
|
||||
CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
|
||||
ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
|
||||
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
|
||||
LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
|
||||
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
|
||||
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
95
README.md
95
README.md
|
@ -1,93 +1,22 @@
|
|||
# ActivityPub-Single-PHP-File
|
||||
|
||||
# ActivityPub Server in a Single PHP File
|
||||
|
||||
This is a single PHP file - and an `.htaccess file` - which acts as an extremely basic ActivityPub server.
|
||||
|
||||
## Getting started
|
||||
|
||||
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
|
||||
This is designed to be a lightweight educational tool to show you the basics of how ActivityPub works.
|
||||
|
||||
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
|
||||
There are no tests, no checks, no security features, no header verifications, no containers, no gods, no masters.
|
||||
|
||||
## Add your files
|
||||
Edit the `.php` file to add a username, password, and keypair.
|
||||
|
||||
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
|
||||
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
|
||||
Upload the `.php` and `.htaccess` file to the *root* directory of your domain. For example `test.example.com/`. It will not work in a subdirectory.
|
||||
|
||||
```
|
||||
cd existing_repo
|
||||
git remote add origin https://gitlab.com/edent/activitypub-single-php-file.git
|
||||
git branch -M main
|
||||
git push -uf origin main
|
||||
```
|
||||
Optionally, upload an `icon.png` as well to make the account look nice.
|
||||
|
||||
## Integrate with your tools
|
||||
## How this works
|
||||
|
||||
- [ ] [Set up project integrations](https://gitlab.com/edent/activitypub-single-php-file/-/settings/integrations)
|
||||
|
||||
## Collaborate with your team
|
||||
|
||||
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
|
||||
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
|
||||
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
|
||||
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
|
||||
- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
|
||||
|
||||
## Test and Deploy
|
||||
|
||||
Use the built-in continuous integration in GitLab.
|
||||
|
||||
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
|
||||
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
|
||||
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
|
||||
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
|
||||
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
|
||||
|
||||
***
|
||||
|
||||
# Editing this README
|
||||
|
||||
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
|
||||
|
||||
## Suggestions for a good README
|
||||
|
||||
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
|
||||
|
||||
## Name
|
||||
Choose a self-explaining name for your project.
|
||||
|
||||
## Description
|
||||
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
|
||||
|
||||
## Badges
|
||||
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
|
||||
|
||||
## Visuals
|
||||
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
|
||||
|
||||
## Installation
|
||||
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
|
||||
|
||||
## Usage
|
||||
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
|
||||
|
||||
## Support
|
||||
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
|
||||
|
||||
## Roadmap
|
||||
If you have ideas for releases in the future, it is a good idea to list them in the README.
|
||||
|
||||
## Contributing
|
||||
State if you are open to contributions and what your requirements are for accepting them.
|
||||
|
||||
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
|
||||
|
||||
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
|
||||
|
||||
## Authors and acknowledgment
|
||||
Show your appreciation to those who have contributed to the project.
|
||||
|
||||
## License
|
||||
For open source projects, say how it is licensed.
|
||||
|
||||
## Project status
|
||||
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
|
||||
* The `.htaccess` file transforms requests from `example.com/whatever` to `example.com/index.php?path=whatever`.
|
||||
* The `index.php` file performs a specific action depending on the path requested.
|
||||
* Log files are saved as .txt in the root directory.
|
||||
* Post files are saved as .json in the `/posts` directory.
|
||||
|
|
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 44 KiB |
|
@ -0,0 +1,435 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* "This code is not a code of honour... no highly esteemed code is commemorated here... nothing valued is here."
|
||||
* "What is here is dangerous and repulsive to us. This message is a warning about danger."
|
||||
* This is a rudimentary, single-file, low complexity, minimum functionality, ActivityPub server.
|
||||
* For educational purposes only.
|
||||
* It produces an Actor who can be followed.
|
||||
* It can send messages to followers.
|
||||
* It saves logs about requests it receives and sends.
|
||||
* It is NOT suitable for production use.
|
||||
* This code is "licenced" under CRAPL v0 - https://matt.might.net/articles/crapl/
|
||||
* "Any appearance of design in the Program is purely coincidental and should not in any way be mistaken for evidence of thoughtful software construction."
|
||||
*/
|
||||
|
||||
// Set up the Actor's information
|
||||
$username = rawurlencode("example"); // Encoded as it is often used as part of a URl
|
||||
$realName = "E. Xample. Jr.";
|
||||
$server = $_SERVER['SERVER_NAME']; // Domain name this is hosted on
|
||||
|
||||
// Generate locally or from https://cryptotools.net/rsagen
|
||||
// Newlines must be replaced with "\n"
|
||||
$key_private = "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----";
|
||||
$key_public = "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----";
|
||||
|
||||
// Password for sending messages
|
||||
$password = "P4ssW0rd";
|
||||
|
||||
// Get all headers and requests sent to this server
|
||||
$headers = print_r( getallheaders(), true );
|
||||
$postData = print_r( $_POST, true );
|
||||
$getData = print_r( $_GET, true );
|
||||
$filesData = print_r( $_FILES, true );
|
||||
$body = json_decode( file_get_contents( "php://input"), true );
|
||||
$bodyData = print_r( $input, true );
|
||||
$requestData = print_r( $_REQUEST, true );
|
||||
$serverData = print_r( $_SERVER, true );
|
||||
|
||||
// Get the type of request
|
||||
if ( isset( $body["type"] ) ) {
|
||||
$type = " " . $body["type"];
|
||||
} else {
|
||||
$type = "";
|
||||
}
|
||||
|
||||
// Create a timestamp in ISO 8601 format for the filename
|
||||
$timestamp = date('c');
|
||||
// Filename for the log
|
||||
$filename = "{$timestamp}{$type}.txt";
|
||||
|
||||
// Save headers and request data to the timestamped file
|
||||
file_put_contents( $filename,
|
||||
"Headers: \n$headers \n\n" .
|
||||
"Body Data: \n$bodyData \n\n" .
|
||||
"POST Data: \n$postData \n\n" .
|
||||
"GET Data: \n$getData \n\n" .
|
||||
"Files Data: \n$filesData \n\n" .
|
||||
"Request Data:\n$requestData\n\n" .
|
||||
"Server Data: \n$serverData \n\n"
|
||||
);
|
||||
|
||||
// The .htaccess changes /whatever to /?path=whatever
|
||||
// What path was requested?
|
||||
$path = $_GET["path"];
|
||||
switch ($path) {
|
||||
case "":
|
||||
echo "Silence";
|
||||
case ".well-known/webfinger":
|
||||
webfinger();
|
||||
case "{$username}":
|
||||
username();
|
||||
case "following":
|
||||
following();
|
||||
case "followers":
|
||||
followers();
|
||||
case "inbox":
|
||||
inbox();
|
||||
case "write":
|
||||
write();
|
||||
case "send":
|
||||
send();
|
||||
default:
|
||||
die();
|
||||
}
|
||||
|
||||
function webfinger() {
|
||||
// Display the WebFinger JSON
|
||||
global $username, $server;
|
||||
|
||||
$webfinger = array(
|
||||
"subject" => "acct:{$username}@{$server}",
|
||||
"links" => array(
|
||||
array(
|
||||
"rel" => "self",
|
||||
"type" => "application/activity+json",
|
||||
"href" => "https://{$server}/{$username}"
|
||||
)
|
||||
)
|
||||
);
|
||||
header("Content-Type: application/json");
|
||||
echo json_encode( $webfinger );
|
||||
die();
|
||||
}
|
||||
|
||||
function username() {
|
||||
// Display the username JSON
|
||||
global $username, $realName, $server, $key_public;
|
||||
|
||||
$user = array(
|
||||
"@context" => [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1"
|
||||
],
|
||||
"id" => "https://{$server}/{$username}",
|
||||
"type" => "Person",
|
||||
"following" => "https://{$server}/following",
|
||||
"followers" => "https://{$server}/followers",
|
||||
"inbox" => "https://{$server}/inbox",
|
||||
"preferredUsername" => rawurldecode($username),
|
||||
"name" => "{$realName}",
|
||||
"summary" => "A single file ActivityPub server.",
|
||||
"url" => "https://{$server}",
|
||||
"manuallyApprovesFollowers" => true,
|
||||
"discoverable" => true,
|
||||
"published" => "2024-02-12T11:51:00Z",
|
||||
"icon" => [
|
||||
"type" => "Image",
|
||||
"mediaType" => "image/png",
|
||||
"url" => "https://{$server}/icon.png"
|
||||
],
|
||||
"publicKey" => [
|
||||
"id" => "https://{$server}/{$username}#main-key",
|
||||
"owner" => "https://{$server}/{$username}",
|
||||
"publicKeyPem" => $key_public
|
||||
]
|
||||
);
|
||||
header("Content-Type: application/activity+json");
|
||||
echo json_encode( $user );
|
||||
die();
|
||||
}
|
||||
|
||||
function following() {
|
||||
// Display the following JSON
|
||||
global $server;
|
||||
|
||||
$following = array(
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => "https://{$server}/following",
|
||||
"type" => "Collection",
|
||||
"totalItems" => 0,
|
||||
"items" => []
|
||||
);
|
||||
header("Content-Type: application/activity+json");
|
||||
echo json_encode( $following );
|
||||
die();
|
||||
}
|
||||
|
||||
function followers() {
|
||||
// Display the followers JSON
|
||||
global $server;
|
||||
$followers = array(
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => "https://{$server}/followers",
|
||||
"type" => "Collection",
|
||||
"totalItems" => 0,
|
||||
"items" => []
|
||||
);
|
||||
header("Content-Type: application/activity+json");
|
||||
echo json_encode( $followers );
|
||||
die();
|
||||
}
|
||||
|
||||
function inbox() {
|
||||
// Respond to InBox requests
|
||||
global $body, $server, $username, $key_private;
|
||||
|
||||
// Get the message and type
|
||||
$inbox_message = $body;
|
||||
$inbox_type = $inbox_message["type"];
|
||||
|
||||
// This inbox only responds to follow requests
|
||||
if ( "Follow" != $inbox_type ) { die(); }
|
||||
|
||||
// Get the parameters
|
||||
$inbox_id = $inbox_message["id"];
|
||||
$inbox_actor = $inbox_message["actor"];
|
||||
$inbox_url = parse_url($inbox_actor, PHP_URL_SCHEME) . "://" . parse_url($inbox_actor, PHP_URL_HOST);
|
||||
$inbox_host = parse_url($inbox_actor, PHP_URL_HOST);
|
||||
|
||||
// Does this account have any followers?
|
||||
if( file_exists( "followers.json" ) ) {
|
||||
$followers_file = file_get_contents( "followers.json" );
|
||||
$followers_json = json_decode( $followers_file, true );
|
||||
} else {
|
||||
$followers_json = array();
|
||||
}
|
||||
|
||||
// Add user to list. Don't care about duplicate users, server is what's important
|
||||
$followers_json[$inbox_host]["users"][] = $inbox_actor;
|
||||
|
||||
// Save the new followers file
|
||||
file_put_contents( "followers.json", print_r( json_encode( $followers_json ), true ) );
|
||||
|
||||
// Response Message ID
|
||||
// This isn't used for anything important so could just be a random number
|
||||
$guid = uuid();
|
||||
|
||||
// Create the Accept message
|
||||
$message = [
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => "https://{$server}/{$guid}",
|
||||
"type" => "Accept",
|
||||
"actor" => "https://{$server}/{$username}",
|
||||
"object" => [
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => $inbox_id,
|
||||
"type" => $inbox_type,
|
||||
"actor" => $inbox_actor,
|
||||
"object" => "https://{$server}/{$username}",
|
||||
]
|
||||
];
|
||||
$message_json = json_encode( $message );
|
||||
|
||||
// The Accept is sent to the server of the user who requested the follow
|
||||
// TODO: The path doesn't *always* end with/inbox
|
||||
$host = $inbox_host;
|
||||
$path = parse_url($inbox_actor, PHP_URL_PATH) . "/inbox";
|
||||
|
||||
// Set up signing
|
||||
$keyId = "https://{$server}/{$username}#main-key";
|
||||
|
||||
// Generate signing variables
|
||||
$hash = hash('sha256', $message_json, true);
|
||||
$digest = base64_encode($hash);
|
||||
$date = date('D, d M Y H:i:s \G\M\T');
|
||||
|
||||
$signer = openssl_get_privatekey($key_private);
|
||||
$stringToSign = "(request-target): post $path\nhost: $host\ndate: $date\ndigest: SHA-256=$digest";
|
||||
openssl_sign($stringToSign, $signature, $signer, OPENSSL_ALGO_SHA256);
|
||||
$signature_b64 = base64_encode($signature);
|
||||
|
||||
$header = 'keyId="' . $keyId . '",algorithm="rsa-sha256",headers="(request-target) host date digest",signature="' . $signature_b64 . '"';
|
||||
|
||||
// Header for POST reply
|
||||
$headers = array(
|
||||
"Host: {$host}",
|
||||
"Date: {$date}",
|
||||
"Digest: SHA-256={$digest}",
|
||||
"Signature: {$header}",
|
||||
"Content-Type: application/activity+json",
|
||||
"Accept: application/activity+json",
|
||||
);
|
||||
|
||||
// Specify the URL of the remote server's inbox
|
||||
// TODO: The path doesn't *always* end with /inbox
|
||||
$remoteServerUrl = $inbox_actor . "/inbox";
|
||||
|
||||
// POST the message and header to the requester's inbox
|
||||
$ch = curl_init( $remoteServerUrl );
|
||||
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
|
||||
curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, "POST" );
|
||||
curl_setopt( $ch, CURLOPT_POSTFIELDS, $message_json );
|
||||
curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
|
||||
$response = curl_exec( $ch );
|
||||
|
||||
// Check for errors
|
||||
if( curl_errno( $ch ) ) {
|
||||
file_put_contents( "error.txt", curl_error( $ch ) );
|
||||
}
|
||||
curl_close($ch);
|
||||
die();
|
||||
}
|
||||
|
||||
function uuid() {
|
||||
// Date sortable UUID
|
||||
return sprintf('%08x-%04x-%04x-%04x-%012x',
|
||||
time(),
|
||||
mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0x3fff) | 0x8000,
|
||||
mt_rand(0, 0xffffffffffff)
|
||||
);
|
||||
}
|
||||
|
||||
function write() {
|
||||
// Display an HTML form for the user to enter a message.
|
||||
echo <<< HTML
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-GB">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Send Message</title>
|
||||
<style>
|
||||
*{font-family:sans-serif;font-size:1.1em;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/send" method="post" enctype="multipart/form-data">
|
||||
<label for="content">Your message:</label><br>
|
||||
<textarea id="content" name="content" rows="5" cols="32"></textarea><br>
|
||||
<label for="password">Password</label><br>
|
||||
<input type="password" name="password" id="password" size="32"><br>
|
||||
<input type="submit" value="Post Message">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
HTML;
|
||||
die();
|
||||
}
|
||||
|
||||
function send() {
|
||||
global $password, $server, $username, $key_private;
|
||||
|
||||
// Does the posted password match the stored password?
|
||||
if( $password != $_POST["password"] ) { die(); }
|
||||
|
||||
// Get the posted content
|
||||
$content = $_POST["content"];
|
||||
|
||||
// Current time
|
||||
$timestamp = date("c");
|
||||
|
||||
// Outgoing Message ID
|
||||
$guid = uuid();
|
||||
|
||||
// Construct the Note
|
||||
$note = [
|
||||
"@context" => array(
|
||||
"https://www.w3.org/ns/activitystreams"
|
||||
),
|
||||
"id" => "https://{$server}/posts/{$guid}.json",
|
||||
"type" => "Note",
|
||||
"published" => $timestamp,
|
||||
"attributedTo" => "https://{$server}/{$username}",
|
||||
"content" => $content,
|
||||
"contentMap" => ["en" => $content],
|
||||
"to" => ["https://www.w3.org/ns/activitystreams#Public"]
|
||||
];
|
||||
|
||||
// Construct the Message
|
||||
$message = [
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => "https://{$server}/posts/{$guid}.json",
|
||||
"type" => "Create",
|
||||
"actor" => "https://{$server}/{$username}",
|
||||
"to" => [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"cc" => [
|
||||
"https://{$server}/followers"
|
||||
],
|
||||
"object" => $note
|
||||
];
|
||||
$message_json = json_encode($message);
|
||||
|
||||
// Create the context for the permalink
|
||||
$note = [ "@context" => "https://www.w3.org/ns/activitystreams", ...$note];
|
||||
|
||||
// Save the permalink
|
||||
$note_json = json_encode( $note );
|
||||
file_put_contents( "posts/{$guid}.json", print_r( $note_json, true ) );
|
||||
|
||||
// Read existing users and get their hosts
|
||||
$followers_file = file_get_contents( "followers.json" );
|
||||
$followers_json = json_decode( $followers_file, true );
|
||||
$hosts = array_keys( $followers_json );
|
||||
|
||||
// Prepare to use the multiple cURL handle
|
||||
$mh = curl_multi_init();
|
||||
|
||||
// Loop through all the severs of the followers
|
||||
// Each server needs its own cURL handle
|
||||
// Each POST to an inbox needs to be signed separately
|
||||
foreach ( $hosts as $host ) {
|
||||
$path = '/inbox';
|
||||
|
||||
// Set up signing
|
||||
$privateKey = $key_private;
|
||||
$keyId = "https://{$server}/{$username}#main-key";
|
||||
|
||||
$hash = hash( "sha256", $message_json, true );
|
||||
$digest = base64_encode( $hash );
|
||||
$date = date('D, d M Y H:i:s \G\M\T');
|
||||
|
||||
$signer = openssl_get_privatekey( $key_private );
|
||||
$stringToSign = "(request-target): post $path\nhost: $host\ndate: $date\ndigest: SHA-256=$digest";
|
||||
openssl_sign( $stringToSign, $signature, $signer, OPENSSL_ALGO_SHA256 );
|
||||
$signature_b64 = base64_encode($signature);
|
||||
|
||||
$header = 'keyId="' . $keyId . '",algorithm="rsa-sha256",headers="(request-target) host date digest",signature="' . $signature_b64 . '"';
|
||||
|
||||
// Header for POST reply
|
||||
$headers = array(
|
||||
"Host: {$host}",
|
||||
"Date: {$date}",
|
||||
"Digest: SHA-256={$digest}",
|
||||
"Signature: {$header}",
|
||||
"Content-Type: application/activity+json",
|
||||
"Accept: application/activity+json",
|
||||
);
|
||||
|
||||
// Specify the URL of the remote server
|
||||
$remoteServerUrl = "https://{$host}{$path}";
|
||||
|
||||
// POST the message and header to the requester's inbox
|
||||
$ch = curl_init( $remoteServerUrl );
|
||||
|
||||
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
|
||||
curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, "POST" );
|
||||
curl_setopt( $ch, CURLOPT_POSTFIELDS, $message_json );
|
||||
curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
|
||||
|
||||
// Add the handle to the multi-handle
|
||||
curl_multi_add_handle( $mh, $ch );
|
||||
}
|
||||
|
||||
// Execute the multi-handle
|
||||
do {
|
||||
$status = curl_multi_exec( $mh, $active );
|
||||
if ( $active ) {
|
||||
curl_multi_select( $mh );
|
||||
}
|
||||
} while ( $active && $status == CURLM_OK );
|
||||
|
||||
// Close the multi-handle
|
||||
curl_multi_close( $mh );
|
||||
|
||||
// Render the JSON so the user can see the POST has worked
|
||||
header( "Location: https://{$server}/posts/{$guid}.json" );
|
||||
die();
|
||||
}
|
||||
|
||||
die();
|
||||
die();
|
||||
die();
|
Ładowanie…
Reference in New Issue