<?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); } $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'], substr($xerr, 0, 4096)); 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 (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 the web hook does not contain our type, skip it if (!strchr($wh['idata']['events'], $event['wh_event'])) { rg_log($event['wh_event'] . ' is not present in ' . $wh['idata']['events']); 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(&$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' ) ); ?>