#!/usr/bin/php-cgi -dcgi.force_redirect=0
<?php
# encoding: utf-8
# api: cgi
# type: api
# category: tickets
# title: Crashreport
# description: Receives errors (JSON POST) and files them as tickets
# version: 0.1
# state: alpha
# config: -
#
# Creates a ticket for any POST it receives that contains valid JSON.
# Unpacks some fields into ticket properties, and posts the whole blob
# as ticket comment. This is probably more suitable for GUI apps than
# smaller console tools or librarikes.
# And it could probably also be reused for storing feature requests.
#
# Now this has some privacy implications. So care should be taken which
# information is included, the filter options expanded, or perhaps even
# the ticket backend taken non-public.
#
# Basic usage could be something like:
#
# catch Exception as e:
# import requests, traceback, json
# requests.post("http://fossil.example.com/repo/ext/crashreport", data=json.dumps({
# "version": "0.0.1",
# "file": __file__,
# "error": str(e),
# "exception": repr(e),
# "backtrace": traceback.extract_tb,
# "locals": locals(),
# }, default=lambda v: repr(v)))
#
# Obviously you might want to wrap this in some general reporting call.
# And certainly customize the mapping for actually used fields.
#-- config
# require that POST payload contains at least one of those fields (to avert arbitrary spambots filing tickets)
$required_fields = [
"version",
"error",
"exception",
];
# contained fields onto ticket properties
$map_fields = [
"error" => "title",
"version" => "foundin",
"module" => "subsystem",
];
# ticket defaults
$default_fields = [
#"comment" => "JSON",
#"foundin" => "0.0.1",
#"icomment" => "".
#"login" => "extroot",
"mimetype" => "text/json",
"private_contact" => "",
"type" => "Incident",
"status" => "Open",
"severity" => "Medium",
"priority" => "Medium",
#"resolution" => "",
"subsystem" => "core",
"title" => "Crashreport",
"username" => "crashreport",
];
# rewrite text contents in any field (some privacy filtering)
$filter_content = [
"~/home/[\w\-\.]+/~" => "/home/user/", # avoid user names
"~[^\\t\\r\\n\\x20-\\xFF]/~" => "#", # no control chars
];
#-- init
if ($_SERVER["CONTENT_LENGTH"] >= 10) {
$json = from_input();
if (!array_intersect(array_keys($json), $required_fields)) {
die(json_encode(["error" => "required fields missing"]));
}
store($json);
}
else {
header("Content-Type: text/x-markdown");
die("Save any stacktraces/errors/etc. as tickets, if sent as JSON blob per POST.");
}
# assume input is JSON
function from_input() {
return json_decode(file_get_contents("php://input"), True);
}
# store as ticket
function store($data) {
global $map_fields, $default_fields, $filter_content;
#-- prepare fields
$fields = $default_fields;
$fields["comment"] = json_encode($data, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);
#-- extra fields
foreach ($map_fields as $from=>$to) {
if (isset($data[$from])) {
$fields[$to] = substr($data[$from], 0, 100);
}
}
#-- assemble cmd
$cmd = "fossil --nocgi -R {$q($_SERVER['FOSSIL_REPOSITORY'])} ticket add";
$q = 'escapeshellarg';
foreach ($fields as $name=>$value) {
foreach ($filter_content as $rx=>$to) {
$value = preg_replace($rx, $to, $value);
}
$cmd .= " {$q($name)} {$q($value)}";
}
#-- run
exec($cmd, $stdout, $errno);
#-- JSON response
if (!$errno) {
die(json_encode(["error" => $stdout, "errno" => $errno]));
}
else {
die(json_encode(["success" => $stdout, "errno" => $errno]));
}
}