<?php
require_once($INC . "/util.inc.php");
require_once($INC . "/log.inc.php");
require_once($INC . "/sql.inc.php");
require_once($INC . "/prof.inc.php");
require_once($INC . "/events.inc.php");
require_once($INC . "/wh/core.inc.php");
$rg_wh_http_functions = array(
'wh_http_send' => 'rg_wh_http_send',
'wh_http_send_one' => 'rg_wh_http_send_one'
);
rg_event_register_functions($rg_wh_http_functions);
/*
* Helper for rg_wh_http_send
*/
function rg_wh_http_send_one($db, $event)
{
rg_prof_start('wh_http_send_helper');
$wh = &$event['wh'];
// replace ##tags##
rg_wh_replace_tags($event);
//if ($wh['idata']['debug'] == 1)
rg_log_ml('wh_http_send_one: event: ' . print_r($event, TRUE));
$headers = array();
while (!empty($wh['idata']['key'])) {
if ($wh['idata']['itype'] == 0)
break;
$headers[] = 'X-RocketGit-Signature: '
. hash_hmac('sha512', $wh['idata']['final'], $wh['idata']['key']);
break;
}
$c = curl_init($wh['idata']['url']);
curl_setopt($c, CURLOPT_POST, 1);
curl_setopt($c, CURLOPT_POSTFIELDS, $wh['idata']['final']);
curl_setopt($c, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($c, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($c, CURLOPT_HEADER, 1);
curl_setopt($c, CURLOPT_HTTPHEADER, $headers);
curl_setopt($c, CURLOPT_USERAGENT, 'RocketGit WebHook');
curl_setopt($c, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($c, CURLOPT_ENCODING, ''); // => use all methods
curl_setopt($c, CURLOPT_VERBOSE, TRUE);
curl_setopt($c, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
//curl_setopt($c, CURLOPT_CERTINFO, TRUE);
// Else, a second wh, without client cert can use the connection:
curl_setopt($c, CURLOPT_FORBID_REUSE, TRUE);
$err = @fopen('php://temp', 'w');
if ($err !== FALSE)
curl_setopt($c, CURLOPT_STDERR, $err);
if (strchr($wh['flags'], 'I'))
curl_setopt($c, CURLOPT_SSL_VERIFYPEER, FALSE);
if (strchr($wh['flags'], 'H'))
curl_setopt($c, CURLOPT_SSL_VERIFYHOST, 0); // TODO verify 0 is a good value (default 2)
$ret = FALSE;
$cert_file = FALSE;
$ca_file = FALSE;
while (1) {
$xid = rg_id(8);
if (!empty($wh['idata']['client_cert'])) {
rg_log('DEBUG: will provide client cert...');
$f = 'wh-' . $event['ui']['uid'] . '-' . $wh['id'] . '-client-' . $xid;
$cert_file = rg_tmp_file($f, $wh['idata']['client_cert']);
if ($cert_file === FALSE)
break;
curl_setopt($c, CURLOPT_SSLCERT, $cert_file);
} else {
rg_log('DEBUG: will NOT provide client cert...');
// TODO: Somehow, without next line, the cert is still sent!
curl_setopt($c, CURLOPT_SSLCERT, FALSE);
}
if (!empty($wh['idata']['client_ca_cert'])) {
$f = 'wh-' . $event['ui']['uid'] . '-' . $wh['id'] . '-ca-' . $xid;
$ca_file = rg_tmp_file($f, $wh['idata']['client_ca_cert']);
if ($ca_file === FALSE)
break;
curl_setopt($c, CURLOPT_CAINFO, $ca_file);
}
$xerr = 'Date (UTC): '
. gmdate('Y-m-d H:i') . "\n";
$xerr .= "\n";
$r = @curl_exec($c);
if ($err !== FALSE) {
rewind($err);
$xerr .= @fread($err, 16 * 4096);
fclose($err);
rg_log_ml('xerr: ' . $xerr);
}
if ($r === FALSE) {
rg_log('Cannot execute curl: ' . curl_error($c) . '.');
$_info = @curl_getinfo($c);
rg_log_ml('curl_getinfo: ' . print_r($_info, TRUE));
if ($wh['idata']['debug'] == 1)
rg_cache_set('DEBUG::' . $event['ui']['uid']
. '::webhooks::' . $wh['idata']['opaque']
. '::' . $wh['id'],
'BAD', RG_SOCKET_NO_WAIT);
break;
}
rg_log_ml('Answer: ' . print_r($r, TRUE));
$xerr .= $r;
if ($wh['idata']['debug'] == 1)
rg_cache_set('DEBUG::' . $event['ui']['uid']
. '::webhooks::' . $wh['idata']['opaque']
. '::' . $wh['id'],
'OK', RG_SOCKET_NO_WAIT);
$ret = array();
break;
}
curl_close($c);
if ($cert_file !== FALSE)
@unlink($cert_file);
if ($ca_file !== FALSE)
@unlink($ca_file);
rg_wh_set_last_output($db, $event['ui']['uid'], $wh['id'], $xerr);
rg_prof_end('wh_http_send_helper');
return $ret;
}
/*
* Generic function which will be called when a webhook must be run
*/
function rg_wh_http_send($db, $event)
{
rg_prof_start('wh_http_send');
rg_log_ml('wh_http_send: event: ' . print_r($event, TRUE));
// First, get the list of hooks
$r = rg_wh_list($db, $event['ui']['uid']);
if ($r['ok'] != 1)
return FALSE;
$cache = array();
$ret = array();
// Filter
foreach ($r['list'] as $id => $wh) {
if (strcmp($wh['htype'], 'http') != 0)
continue;
// Diabled?
if (strchr($wh['flags'], 'D'))
continue;
// If the web hook does not contain our type, skip it
if (!strchr($wh['idata']['events'], $event['wh_event'])) {
rg_log('DEBUG: ' . $event['wh_event']
. ' is not present in '
. $wh['idata']['events']);
continue;
}
if (isset($event['ri']['name'])
&& !rg_wh_repo_match($wh['repo'], $event['ri']['name'])) {
rg_log('hook is not for this repo');
continue;
}
if (isset($event['refname'])
&& !rg_repo_compare_refs($wh['refname'], $event['refname'])) {
rg_log('hook is not for this ref');
continue;
}
if (!isset($wh['idata']['itype'])) {
rg_log_ml('wh: ' . print_r($wh, TRUE));
rg_internal_error('DEBUG: itype is not present');
continue;
}
$itype = $wh['idata']['itype'];
if (!isset($cache[$itype])) {
switch ($itype) {
case 0: // http post
$cache[$itype] = &$event['ri'];
break;
case 1: // php serialize
$cache[$itype] = serialize($event['ri']);
break;
case 2: // custom body
$cache[$itype] = $wh['idata']['custom_body'];
break;
default:
rg_log('Unknown type ' . $itype . '!');
$cache[$itype] = '';
break;
}
}
$wh['idata']['final'] = $cache[$itype];
$x = $event;
$x['category'] = 'wh_http_send_one';
$x['wh'] = $wh;
$x['debug'] = $wh['idata']['debug'];
$ret[] = $x;
}
rg_prof_end('wh_http_send');
return $ret;
}
$rg_wh_http_itypes = array(
0 => 'HTTP (application/x-www-form-urlencoded)',
1 => 'PHP serialize',
2 => 'Custom (use custom body field)'
);
/*
* Transforms a type into HTML select
*/
function rg_wh_http_select_itype($itype)
{
global $rg_wh_http_itypes;
$ret = '<select name="wh::idata::itype" id="itype">';
foreach ($rg_wh_http_itypes as $_itype => $name) {
$add = '';
if ($_itype == $itype)
$add = ' selected';
$ret .= '<option value="' . $_itype . '"' . $add . '>'
. $name
. '</option>' . "\n";
}
$ret .= '</select>' . "\n";
return $ret;
}
/*
* Returns type text based on id
*/
function rg_wh_http_itype($itype)
{
global $rg_wh_http_itypes;
foreach ($rg_wh_http_itypes as $_itype => $name)
if ($_itype == $itype)
return $name;
}
/*
* Some cosmetics applied to a webhook
*/
function rg_wh_http_cosmetic(&$row)
{
// TODO DEBUG remove the following 2 lines
if (!isset($row['idata']['itype']))
$row['idata']['itype'] = 0;
$row['idata']['itype_text'] = rg_wh_http_itype($row['idata']['itype']);
$row['idata']['HTML:client_cert_short'] =
rg_xss_safe(rg_cert_short($row['idata']['client_cert']));
$row['idata']['HTML:client_ca_cert_short'] =
rg_xss_safe(rg_cert_short($row['idata']['client_ca_cert']));
$row['idata']['HTML:custom_body_nlbr'] =
nl2br(rg_xss_safe($row['idata']['custom_body']));
// this must be last
$row['idata']['HTML:private'] = rg_template(
'user/settings/wh/http/show.html', $row['idata'], TRUE /*xss*/);
}
/*
* Fill private data based on parameters passed
*/
function rg_wh_http_fill_vars(&$rg)
{
$a = &$rg['wh']['idata'];
$a['url'] = trim(rg_var_str('wh::idata::url'));
$a['itype'] = rg_var_uint('wh::idata::itype');
$a['client_cert'] = trim(rg_var_str('wh::idata::client_cert'));
$a['client_ca_cert'] = trim(rg_var_str('wh::idata::client_ca_cert'));
$a['opaque'] = trim(rg_var_str('wh::idata::opaque'));
$a['key'] = trim(rg_var_str('wh::idata::key'));
$a['custom_body'] = trim(rg_var_str('wh::idata::custom_body'));
}
/*
* Validate parameters passed
*/
function rg_wh_http_validate_vars($rg, &$errmsg)
{
global $rg_wh_http_itypes;
$a = $rg['wh'];
$ret = FALSE;
while (1) {
if ((strncasecmp($a['idata']['url'], 'http', 4) != 0)
&& (strncasecmp($a['idata']['url'], 'https', 5) != 0)) {
$errmsg[] = rg_template('user/settings/wh/http/inv_proto.txt',
$rg, TRUE /*xss*/);
break;
}
if (!isset($rg_wh_http_itypes[$a['idata']['itype']])) {
$errmsg[] = rg_template('user/settings/wh/http/inv_type.txt',
$rg, TRUE /*xss*/);
break;
}
$ret = TRUE;
break;
}
return $ret;
}
/*
* Transfers to $rg the custom parameters - used when showing the form
*/
function rg_wh_http_add_form($db, &$rg)
{
$rg['HTML:select_itype'] = rg_wh_http_select_itype($rg['wh']['idata']['itype']);
$rg['HTML:custom_form'] = rg_template('user/settings/wh/http/form.html',
$rg, TRUE /*xss*/);
}
/*
* Add custom hints
*/
function rg_wh_http_fill_hints($rg, &$hints)
{
$hints[]['HTML:hint'] = rg_template('user/settings/wh/http/hints.html',
$rg, TRUE /*xss*/);
}
/*
* Loads default paras for a form
*/
function rg_wh_http_default_paras(&$rg)
{
$a = &$rg['wh']['idata'];
$a['url'] = '';
$a['itype'] = 0;
$a['client_cert'] = '';
$a['client_ca_cert'] = '';
$a['opaque'] = '';
$a['key'] = '';
$a['custom_body'] = '';
if (strcmp($rg['wh']['hsubtype'], 'slack') == 0) {
$rg['wh']['description'] = 'Slack integration';
$a['itype'] = 2;
$a['events'] = 'P';
$a['custom_body'] = '{' . "\n"
. '"channel": "#rocketgit-##branch##",' . "\n"
. '"username": "RocketGit robot (hook ##hook_id##)",' . "\n"
. '"text": "Repo ##repo##, branch ##branch## pushed: <##commit_url##>",' . "\n"
. '"icon_emoji": ":ghost:"' . "\n"
. '}';
}
}
$rg_wh_plugins['http'] = array(
'htype' => 'http',
'description' => 'HTTP[S] - Calls any URL you want',
'cosmetic' => 'rg_wh_http_cosmetic',
'fill_vars' => 'rg_wh_http_fill_vars',
'validate_vars' => 'rg_wh_http_validate_vars',
'add_form' => 'rg_wh_http_add_form',
'default_paras' => 'rg_wh_http_default_paras',
'fill_hints' => 'rg_wh_http_fill_hints',
'have_events' => TRUE,
'subtypes' => array(
'slack' => 'Slack'
),
'flags' => array(
'I' => 'Do not verify the server certificate',
'H' => 'Do not verify the server hostname'
)
);
?>