#!/usr/bin/php-cgi -dcgi.force_redirect=0
<?php
# encoding: utf-8
# api: cgi
# type: store
# category: ticket
# title: Crashreport
# description: Receives errors (JSON POST) and files them as tickets
# version: 0.1
# state: alpha
# config: -
# access: public
#
# 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]));
    }
}

