Erm I really think I ought to commit this to git naow :P

This commit is contained in:
Starbeamrainbowlabs 2018-03-05 18:03:34 +00:00
commit 524796e43d
Signed by: sbrl
GPG key ID: 1BE5172E637709C2
15 changed files with 852 additions and 0 deletions

18
.gitignore vendored Normal file
View file

@ -0,0 +1,18 @@
# The application's data directory
data/
# Created by https://www.gitignore.io/api/composer,git
### Composer ###
composer.phar
/vendor/
# Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
# composer.lock
### Git ###
*.orig
# End of https://www.gitignore.io/api/composer,git

18
AbstractAction.php Normal file
View file

@ -0,0 +1,18 @@
<?php
abstract class AbstractAction
{
public abstract function handle();
public function param_exists($key) {
return !empty($_GET[$key]);
}
public function get_param($key, $default_value) {
return $_GET[$key] ?? $default_value;
}
public get_post_body() {
return file_get_contents("php://input");
}
}

16
Actions/Report.php Normal file
View file

@ -0,0 +1,16 @@
<?php
namespace Sandpiper\Actions;
class Report extends \Sandpiper\AbstractAction
{
public function __construct()
{
}
public function handle() {
}
}

16
composer.json Normal file
View file

@ -0,0 +1,16 @@
{
"name": "sbrl/wandering-sandpiper",
"description": "A simple error report / stack trace gathering system.",
"type": "project",
"require": {
"yosymfony/toml": "^1.0",
"aura/autoload": "^2.0"
},
"license": "MPL-2.0",
"authors": [
{
"name": "Starbeamrainbowlabs",
"email": "sbrl@starbeamrainbowlabs.com"
}
]
}

163
composer.lock generated Normal file
View file

@ -0,0 +1,163 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "3f0d14de50f1944fa0995459b410e76c",
"packages": [
{
"name": "aura/autoload",
"version": "2.0.4",
"source": {
"type": "git",
"url": "https://github.com/auraphp/Aura.Autoload.git",
"reference": "306a7f8d3cb58fb6f94bcff1dddf20c543f68668"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/auraphp/Aura.Autoload/zipball/306a7f8d3cb58fb6f94bcff1dddf20c543f68668",
"reference": "306a7f8d3cb58fb6f94bcff1dddf20c543f68668",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"aura": {
"type": "library"
}
},
"autoload": {
"psr-4": {
"Aura\\Autoload\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"authors": [
{
"name": "Aura.Autoload Contributors",
"homepage": "https://github.com/auraphp/Aura.Autoload/contributors"
}
],
"description": "Provides a PSR-4 compliant autoloader implementation.",
"homepage": "https://github.com/auraphp/Aura.Autoload",
"keywords": [
"PSR-4",
"SPL autoloader",
"autoload",
"autoloader",
"class loader"
],
"time": "2016-10-03T19:36:19+00:00"
},
{
"name": "yosymfony/parser-utils",
"version": "v1.0.0",
"source": {
"type": "git",
"url": "https://github.com/yosymfony/parser-utils.git",
"reference": "5376469d996a8f09139c0c795048b1140d143281"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yosymfony/parser-utils/zipball/5376469d996a8f09139c0c795048b1140d143281",
"reference": "5376469d996a8f09139c0c795048b1140d143281",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-4": {
"Yosymfony\\ParserUtils\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Victor Puertas",
"email": "vpgugr@gmail.com",
"homepage": "http://yosymfony.com"
}
],
"description": "Parser utilities",
"homepage": "http://github.com/yosymfony/toml",
"keywords": [
"lexer",
"parser"
],
"time": "2017-11-18T22:30:41+00:00"
},
{
"name": "yosymfony/toml",
"version": "v1.0.1",
"source": {
"type": "git",
"url": "https://github.com/yosymfony/toml.git",
"reference": "8d67c2c9a5941d40a3a37ff42c3c3b72a0a09678"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yosymfony/toml/zipball/8d67c2c9a5941d40a3a37ff42c3c3b72a0a09678",
"reference": "8d67c2c9a5941d40a3a37ff42c3c3b72a0a09678",
"shasum": ""
},
"require": {
"php": ">=7.1",
"yosymfony/parser-utils": "^1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-4": {
"Yosymfony\\Toml\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Victor Puertas",
"email": "vpgugr@gmail.com",
"homepage": "http://yosymfony.com"
}
],
"description": "A PHP parser for TOML compatible with specification 0.4.0",
"homepage": "http://github.com/yosymfony/toml",
"keywords": [
"mojombo",
"parser",
"toml"
],
"time": "2018-02-05T22:43:21+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}

8
get-report-slot.php Normal file
View file

@ -0,0 +1,8 @@
<?php
require("settings.php");
require("utilities/comment_key.php");
header("content-type: text/plain");
exit(key_generate($settings->comment_key_pass));

30
index.php Normal file
View file

@ -0,0 +1,30 @@
<?php
require("vendor/autoload.php");
require("utilities/transform.php");
// ---------------------------------------------------------------------------------
$aura_loader = new \Aura\Autoload\Loader;
$aura_loader->addPrefix('Sandpiper', '.');
$aura_loader->addPrefix('SBRL\Utilities', './utilities');
$aura_loader->register();
// ---------------------------------------------------------------------------------
function send_error($code, $message) {
http_response_code($code);
header("content-type: text/plain");
exit("$message\n");
}
if(empty($_GET["action"]))
send_error(400, "No action specified.");
$action = $_GET["action"];
$handler_name = "Sandpiper\\Actions\\$action";
$handler = new $handler_name();
$handler->handle();

106
report.php Normal file
View file

@ -0,0 +1,106 @@
<?php
require("settings.php");
require("utilities/comment_key.php");
require("utilities/transform.php");
require("utilities/simplexmlwriter.php");
header("content-type: text/plain");
if(empty($_GET["place_id"]))
exit("Error: No place id provided.\n");
if(empty($_GET["summary"]))
exit("Error: No summary provided.\n");
if(empty($_GET["version"]))
exit("No version provided.\n");
/*if(empty($_GET["key"]))
exit("Error: No key provided.\n");
if(!key_verify(
$_GET["key"],
$settings->comment_key_pass,
$settings->comment_key_min_age, $settings->comment_key_max_age
))
exit("Error: Invalid key.\n");
*/
/******************************************************/
$places = json_decode(file_get_contents("$settings->data_dir/places.json"));
$place = null;
foreach($places as $current_place) {
if($place->key == $_GET["place_id"]) {
$place = $current_place;
break;
}
}
if($place == null)
exit("Error: Place id doesn't match.\n");
$new_report = new stdClass();
$new_report->version = escape4xml($_GET["version"]);
$new_report->summary = escape4xml($_GET["summary"]);
$new_report->stack_trace = escape4xml(file_get_contents('php://input'));
$report_dir = "$settings->data_dir/places/$place->name";
$report_filename = "$report_dir/" . slugify($new_report->summary) . ".xml";
// Save the individual report
if(!file_exists($report_filename)) {
$writer = new SimpleXmlWriter();
$writer->start();
$writer->add_xslt("../../theme/stack_traces.xslt");
$writer->open("error_info");
$writer->addtag("summary", [], $new_report->summary);
$writer->open("reports");
$writer->close();
$writer->close();
file_put_contents($report_filename, $writer->render());
}
$report_xml = simplexml_load_file($report_filename);
if($report_xml === false)
exit("Error: invalid XML generated when creating a new report file.\n");
$report_node = $report_xml->reports->addChild("report");
$report_node->addChild("timestamp", date(DATE_ATOM));
$report_node->addChild("version", $new_report->version);
$report_node->addChild("summary", $new_report->summary);
$report_node->addChild("stack_trace", $new_report->stack_trace);
file_put_contents($report_filename, $report_xml->asXML());
// Update the place index
$place_index_filename = "$report_dir/index.xml";
if(!file_exists($place_index_filename)) {
$writer = new SimpleXmlWriter();
$writer->start();
$writer->add_xslt("../../theme/place_index.xslt");
$writer->open("errors");
$writer->close();
file_put_contents($place_index_filename, $writer->render());
}
$index_xml = simplexml_load_file($place_index_filename);
$index_entry = null;
foreach($index_xml as $entry) {
if($entry->summary->__toString() != $new_report->summary)
continue;
$index_entry = $entry;
break;
}
if($index_entry == null) {
$index_entry = $index_xml->addChild("error");
$index_entry->summary = $new_report->summary;
$index_entry->last_report = date(DATE_ATOM);
$index_entry->report_count = 0;
}
$index_entry->report_count = intval($index_entry->report_count) + 1;
file_put_contents($place_index_filename, $index_xml->asXML());

0
submit-report.sh Normal file
View file

6
theme/place_index.xslt Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0" xmlns:xsl="http://w3.org/1999/XSL/Transform">
<xsl:template match="/">
</xsl:template>
</xsl:transform>

6
theme/stack_traces.xslt Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0" xmlns:xsl="http://w3.org/1999/XSL/Transform">
<xsl:template match="/">
</xsl:template>
</xsl:transform>

182
utilities/TomlConfig.php Normal file
View file

@ -0,0 +1,182 @@
<?php
namespace SBRL\Utilities;
use \stdClass;
use \Yosymfony\Toml\Toml;
use \Yosymfony\Toml\TomlBuilder;
/**
* Handles the loading and saving of settings from a toml file.
* Supports loading default settings from a separate file.
* @author Starbeamrainbowlabs
* @version v0.2
* @lastModified 22nd March 2017
* @license https://www.mozilla.org/en-US/MPL/2.0/ Mozilla Public License 2.0
* Changelog:
* v0.3 - 5th March 2018
* Forked to create a version for toml.
* v0.2 - 22nd March 2017
* Add license
* Filled in some missing comments
* v0.1: 28th February 2017
* Initial release
*/
class TomlConfig
{
private $defaultSettings;
private $settingsFilePath = "";
/**
* The custom settings object.
* @var stdClass
*/
private $customSettings;
/**
* The banner to insert at the top of the custom settings file when saving it.
* @var string
*/
private $customSettingsBanner;
/**
* Creates a new JsonConfig
* @param string $settingsFilePath The path to the customised settings file.
* @param string $defaultSettingsFilePath The path to the default settings file.
*/
public function __construct($settingsFilePath, $defaultSettingsFilePath) {
$this->customSettingsBanner = "-------[ Custom Settings File - Last updated " . date("Y-m-d") . " ]-------";
$this->defaultSettings = Toml::ParseFile($defaultSettingsFilePath, true);
$this->customSettings = new stdClass();
if(file_exists($settingsFilePath)) {
$this->customSettings = Toml::ParseFile($settingsFilePath, true);
}
}
/**
* Gets the value identified by the specified property, in dotted notation (e.g. `Network.Firewalling.Ports.Http`)
* @param string $key The property, in dotted notation, to return.
* @return mixed The value odentified by the given dotted property notation.
*/
public function get($key) {
if(static::hasPropertyByPath($this->customSettings, $key)) {
return static::getPropertyByPath($this->customSettings, $key);
}
return static::getPropertyByPath($this->defaultSettings, $key);
}
/**
* Fetches the default value for the specified property, specified in dotted notation.
* @param string $key The key to fetch.
* @return mixed The default value of the property identified by the given dotted notation.
*/
public function getDefault($key) {
return static::getPropertyByPath($this->defaultSettings, $key);
}
/**
* Determines whether the given property has been customised or explicitly specified in the customised settings file.
* @param string $key The property, in dotted notation, to check.
* @return boolean Whether the specified properties file has been explicitly specified in the custom settings file.
*/
public function hasChanged($key) {
return static::hasPropertyByPath($this->customSettings, $key);
}
/**
* Sets the property identified by the specified dotted path.
* Does not re-save the properties back to disk!
* @param string $key The dotted path to update.
* @param mixed $value The value to set the property to.
* @return self This ConfigFile instance, to aid method chaining.
*/
public function set($key, $value) {
static::setPropertyByPath($this->customSettings, $key, $value);
return $this;
}
public function setCustomSettingsBanner($value) {
$this->customSettingsBanner = $value;
return $this;
}
/**
* Saves the customised settings back to the disk.
* @return boolean Whether the save was successful or not.
*/
public function save() {
$output_builder = new TomlBuilder();
$toml_builder->addComment($this->customSettingsBanner);
$this->build($toml_builder, $this->customSettings);
return file_put_contents($settingsFilePath, $toml_builder->getTomlString());
}
private function build($toml_builder, $customSettingsObject, $subobject_name = null) {
if($subobject_name !== null)
$toml_builder->addTable($subobject_name);
foreach($customSettingsObject as $key => $value) {
if(!\is_object($value))
$toml_builder->addValue($key, $value);
else
$this->build($toml_builder, $value, "$subobject_name.$key");
}
}
/**
* Works out whether a given dotted path to a property exists on a given object.
* @param stdClass $obj The object to search.
* @param string $path The dotted path to search with. e.g. `Network.Firewall.OpenPorts`
* @return boolean Whether the given property is present in the given object.
*/
public static function hasPropertyByPath($obj, $path) {
$pathParts = explode(".", $path);
$subObj = $obj;
foreach($pathParts as $part) {
if(!isset($subObj->$part))
return false;
$subObj = $subObj->$part;
}
return true;
}
/**
* Returns the property identified by the given dotted path.
* If you're not sure whether a property exists or not, use static::hasPropertyByPath() first to check.
* @param stdClass $obj The object to fetch from.
* @param string $path The path to extract from the given object.
* @return mixed The property identified by the given object.
*/
public static function getPropertyByPath($obj, $path) {
$pathParts = explode(".", $path);
$subObj = $obj;
foreach($pathParts as $part) {
$subObj = $subObj->$part;
}
return $subObj;
}
/**
* Returns the property identified by the given dotted path.
* If you're not sure whether a property exists or not, use static::hasPropertyByPath() first to check.
* @param stdClass $obj The object to set the property on.
* @param string $path The path to set on the given object.
* @param mixed $value The value to set the given property to.
*/
public static function setPropertyByPath($obj, $path, $value) {
$pathParts = explode(".", $path);
$pathPartsCount = count($pathParts);
$subObj = $obj;
for($i = 0; $i < $pathPartsCount; $i++) {
// Set the property on the last iteration
if($i + 1 == $pathPartsCount) {
$subObj->$part = $value;
break;
}
// Create sub-objects if they dobn't exist already
if(!isset($subObj->$part)) {
$subObj->$part = new stdClass();
}
$subObj = $subObj->$part;
}
}
}

68
utilities/comment_key.php Normal file
View file

@ -0,0 +1,68 @@
<?php
/***********************************************
* Comment Key Generation System
***********************************************
* By Starbeamrainbowlabs <feedback@starbeamrainbowlabs.com>
*
* A set of functions to protect low-value comment boxes, contact us
* forms, etc. from spam.
*
* It works by recording the time the page the form is on was loaded,
* and then checking to make sure that it was loaded for at least
* 10 - 15 seconds, and not more than 24 hours.
*
* This is done by taking the current UNIX timestamp, running it through
* a number of (arbitrary) reversible transformations, and then
* embedding it in the form as a hidden field. When the form is
* submitted, the transformations are reversed and the time the page
* was loaded checked.
*
* Note that this is not suitable for high-value targets, as it can very
* easily be bypassed and reverse-engineered by any determined human.
*
***********************************************
* Changelog:
* 30th January 2018:
* Initial Release.
*/
/**
* Generates a new comment key.
* Note that the 'encryption' used is not secure - it's just simple XOR!
* It's mainly to better support verification in complex setups and
* serve as a nother annoying hurdle for spammers.
* TODO: Swap out the XOR for openssl AES symmetric encryption
* @param string $pass The password to transform it with. Should be a pre-selected random string.
* @return string A new comment key stamped at the current time.
*/
function key_generate($pass) {
$new_key = strval(time());
// Repeat the key so that it's long enough to XOR the key with
$pass_enc = str_repeat($pass, (strlen($new_key) / strlen($pass)) + 1);
$new_key = $new_key ^ $pass_enc;
return base64_encode(strrev($new_key));
}
/**
* Decodes the given key.
* @param string $key The key to decode.
* @param string $pass The password to decode it with.
* @return int The time that the key was generated.
*/
function key_decode($key, $pass) {
$key_dec = strrev(base64_decode($key));
// Repeat the key so that it's long enough to XOR the key with
$pass_dec = str_repeat($pass, (strlen($key_dec) / strlen($pass)) + 1);
return intval($key_dec ^ $pass_dec);
}
/**
* Verifies a key.
* @param string $key The key to decode and verify.
* @param string $pass The password to decode the key with.
* @param int $min_age The minimum required age for the key.
* @param int $max_age The maximum allowed age for the key.
* @return bool Whether the key is valid or not.
*/
function key_verify($key, $pass, $min_age, $max_age) {
$age = time() - key_decode($key, $pass);
return $age >= $min_age && $age <= $max_age;
}

View file

@ -0,0 +1,197 @@
<?php
namespace Utilities;
/****************
* Todo
****************
* Test this code!
*
* Add support of processing instructions (<?....?>, e.g. xslt support)
*
* Allow customisation of the characters used for indentation
* Allow customisation of the newline character(s)
*/
class SimpleXmlWriter {
/*
*
* Constants
*
*/
//the XML doctype
const xml_doctype = '<?xml version="1.0" encoding="utf-8"?>';
//the generator information
const generator_info = "<!-- Generated by simplexmlwriter.php, which was built by Starbeamrainbowlabs -->";
/*
*
* Properties
*
*/
//whether we should prettyprint the xml - remember to set this first!
public $prettyprint = true;
//the currently generated XML - don't reference this directly as it may have unclosed tags and therefore invalid XML
protected $xml = "";
//the currently open tags
protected $opentags = [];
/*
*
* Functions
*
*/
//begin constructing XMl
protected function start()
{
$this->add($this::xml_doctype);
$this->add($this::generator_info);
}
public function add_xslt($uri = "")
{
$this->add("<?xml-stylesheet type=\"text/xsl\" href=\"$uri\"?>");
}
//open a tag
public function open($tagname = "root", $attributes = [])
{
$attrtext = "";
foreach($attributes as $name => $value)
$attrtext .= "$name=\"$value\" ";
$attrtext = " " . trim($attrtext);
if(strlen($attrtext) === 1)
$attrtext = "";
$this->add("<$tagname$attrtext>");
$this->opentags[] = $tagname; //add the tag name to the list of currently open tags
}
//add some plain text
public function addtext($text = "", $newline = true)
{
$this->add($text, $newline);
}
//close a tag
public function close()
{
$this->add("</" . array_pop($this->opentags) . ">");
}
//close all open tags
public function closeall()
{
while(count($this->opentags) > 0)
{
$this->close();
}
}
//shortcut to add a whole tag
public function addtag($tagname = "root", $attributes = [], $text = "")
{
$attrtext = "";
foreach($attributes as $name => $value)
$attrtext .= "$name=\"$value\" ";
$attrtext = " " . trim($attrtext);
if(strlen($attrtext) === 1)
$attrtext = "";
$this->add("<$tagname$attrtext>$text</$tagname>");
}
//add a comment
public function addcomment($text = "")
{
$this->add("<!-- $text -->");
}
//get a rendered version of the currently generated XML
public function render()
{
$result = $this->xml;
$revopentags = array_reverse($this->opentags);
foreach($revopentags as $tagname)
{
if($this->prettyprint)
$result .= str_repeat("\t", count($revopentags)) . "</$tagname>\n";
else
$result .= "</$tagname>";
}
return $result;
}
//get the appropriate number of tab characters for indentation purposes
protected function getindent()
{
return str_repeat("\t", count($this->opentags));
}
//function to add to the rendered XML string
protected function add($text = "", $newline = true)
{
if($newline and $this->prettyprint)
$this->xml .= $this->getindent();
$this->xml .= $text;
if($newline and $this->prettyprint)
$this->xml .= "\n";
}
public function __toString() {
return $this->render();
}
/*
*
* Constuctor
*
*/
function __construct() {
$this->start();
}
}
if(basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"]))
{
//we were not included
$xml = new simplexmlwriter();
$xml->add_xslt("https://example.com/test.xslt");
$xml->open("root");
$xml->addcomment("- ------------------------------ -");
$xml->addcomment("- -- simplexmlwriter.php test -- -");
$xml->addcomment("- ------------------------------ -");
foreach(range(1, 10) as $i)
{
$xml->open("number");
$xml->addtag("type", [], "integer");
$xml->open("value");
$xml->addtext($i);
$xml->close();
$xml->close();
}
$xml->closeall();
header("content-type: application/xml");
echo($xml->render());
}
?>

18
utilities/transform.php Normal file
View file

@ -0,0 +1,18 @@
<?php
function slugify($string, $maxlength = 50) {
return preg_replace([
'\s+',
'[^a-z0-9\-_]'
], [
'-',
''
],
strtolower(trim($string))
);
}
function escape4xml($string) {
return htmlspecialchars($string, ENT_QUOTES | ENT_XML1);
}