400, 'descr' => 'Bad Request' ]; } if (defined('OBS_HTTP_REQUEST') && OBS_HTTP_REQUEST === FALSE) { print_debug("HTTP requests skipped since previous request exit with timeout"); $ok = FALSE; $GLOBALS['response_headers'] = [ 'code' => 408, 'descr' => 'Previous Request Timeout' ]; } if ($ok && !is_http_curl()) { if (!ini_get('allow_url_fopen')) { print_debug('HTTP requests disabled, since PHP config option "allow_url_fopen" set to off. Please enable this option in your PHP config.'); $ok = FALSE; $GLOBALS['response_headers'] = ['code' => 400, 'descr' => 'HTTP Method Disabled']; } elseif (str_istarts($request, 'https') && !check_extension_exists('openssl')) { // Check if Secure requests allowed, but ssl extension not exists print_debug(__FUNCTION__ . '() wants to connect with https but https is not enabled on this server. Please check your PHP settings, the openssl extension must exist and be enabled.'); logfile(__FUNCTION__ . '() wants to connect with https but https is not enabled on this server. Please check your PHP settings, the openssl extension must exist and be enabled.'); $ok = FALSE; $GLOBALS['response_headers'] = ['code' => 400, 'descr' => 'HTTPS Method Disabled']; } } if ($ok && process_http_ratelimit($request, $rate_limit)) { // request exceeded rate limit $GLOBALS['response_headers'] = ['code' => 429, 'descr' => 'Too Many Requests']; $ok = FALSE; } if (OBS_DEBUG > 0) { $debug_request = $request; if (OBS_DEBUG < 2 && strpos($request, 'update.observium.org')) { $debug_request = preg_replace('/&stats=.+/', '&stats=***', $debug_request); } $debug_msg = PHP_EOL . 'REQUEST[%y' . $debug_request . '%n]'; } if (!$ok) { if (OBS_DEBUG > 0) { print_message($debug_msg . PHP_EOL . 'REQUEST STATUS[%rFALSE%n]' . PHP_EOL . 'RESPONSE CODE[' . $GLOBALS['response_headers']['code'] . ' ' . $GLOBALS['response_headers']['descr'] . ']', 'console'); } // Set GLOBAL var $request_status for use as validate status of last response $GLOBALS['request_status'] = FALSE; return FALSE; } if (!isset($GLOBALS['http_stats'])) { // Init stats $GLOBALS['http_stats'] = [ 'sec' => 0, 'requests' => 0, 'ok' => 0, 'error' => 0, 'timeout' => 0 ]; } if (is_http_curl()) { $response_array = process_http_curl($request, $context); } else { $response_array = process_http_php($request, $context); } $GLOBALS['http_stats']['sec'] += $response_array['request_runtime']; $GLOBALS['http_stats']['requests']++; // Request response $response = $response_array['response']; $runtime = $response_array['request_runtime']; // Request end unixtime and runtime $GLOBALS['request_unixtime'] = $response_array['request_unixtime']; $GLOBALS['request_runtime'] = $response_array['request_runtime']; // Headers $head = $response_array['response_headers']; $GLOBALS['response_headers'] = $response_array['response_headers']; // Set GLOBAL var $request_status for use as validate status of last response if (isset($head['code']) && ($head['code'] < 200 || $head['code'] >= 400) && $head['code'] !== 28) { $GLOBALS['request_status'] = FALSE; $GLOBALS['http_stats']['error']++; bdump($response_array); } elseif ($response === FALSE) { // An error in get response $GLOBALS['response_headers'] = ['code' => 408, 'descr' => 'Request Timeout']; $GLOBALS['request_status'] = FALSE; $GLOBALS['http_stats']['timeout']++; } else { // Valid statuses: 2xx Success, 3xx Redirection or head code not set (ie response not correctly parsed) $GLOBALS['request_status'] = TRUE; $GLOBALS['http_stats']['ok']++; } // if (str_contains($request, 'somewrong')) { // print_vars($head); // var_dump($response); // } // Set OBS_HTTP_REQUEST for skip all other requests (FALSE for skip all other requests) if (!defined('OBS_HTTP_REQUEST')) { if ($response === FALSE && empty($head)) { // Derp, no way for get proxy headers if ($runtime < 1 && isset($config['http_proxy']) && $config['http_proxy'] && !(isset($config['proxy_user']) || isset($config['proxy_password']))) { $GLOBALS['response_headers'] = ['code' => 407, 'descr' => 'Proxy Authentication Required']; } else { $GLOBALS['response_headers'] = ['code' => 408, 'descr' => 'Request Timeout']; } $GLOBALS['request_status'] = FALSE; // Validate host from request and check if it timeout request if (OBS_PROCESS_NAME === 'poller' && gethostbyname6(parse_url($request, PHP_URL_HOST))) { // Timeout error, only if not received response headers define('OBS_HTTP_REQUEST', FALSE); print_debug(__FUNCTION__ . '() exit with timeout. Access to outside localnet is blocked by firewall or network problems. Check proxy settings.'); logfile(__FUNCTION__ . '() exit with timeout. Access to outside localnet is blocked by firewall or network problems. Check proxy settings.'); } } else { define('OBS_HTTP_REQUEST', TRUE); } } // FIXME. what if first request fine, but second broken? //else if ($response === FALSE) //{ // if (function_exists('runkit_constant_redefine')) { runkit_constant_redefine('OBS_HTTP_REQUEST', FALSE); } //} if (defined('OBS_DEBUG') && OBS_DEBUG) { // Hide extended stats in normal debug level = 1 if (OBS_DEBUG < 2 && strpos($request, 'update.observium.org')) { $request = preg_replace('/&stats=.+/', '&stats=***', $request); } // Show debug info print_message($debug_msg . PHP_EOL . 'REQUEST STATUS[' . ($GLOBALS['request_status'] ? '%gTRUE' : '%rFALSE') . '%n]' . PHP_EOL . 'REQUEST RUNTIME[' . ($runtime > 3 ? '%r' : '%g') . round($runtime, 4) . 's%n]' . PHP_EOL . 'RESPONSE CODE[' . $GLOBALS['response_headers']['code'] . ' ' . $GLOBALS['response_headers']['descr'] . ']', 'console'); if (OBS_DEBUG > 1) { echo "RESPONSE[\n" . $response . "\n]"; if (function_exists('http_get_last_response_headers')) { // PHP 8.4+ $http_response_header = http_get_last_response_headers(); } print_vars($http_response_header); //print_vars($opts); } } return $response; } /** * @param string $request * @param mixed $rate_limit * @return bool */ function process_http_ratelimit($request, $rate_limit = FALSE) { if ($rate_limit && is_numeric($rate_limit) && $rate_limit >= 0) { // Check limit rates to this domain (per/day) if (preg_match('/^https?:\/\/([\w\.]+[\w\-\.]*(:\d+)?)/i', $request, $matches)) { $date = format_unixtime(get_time(), 'Y-m-d'); $domain = $matches[0]; // base domain (with http(s)): https://test-me.com/ -> https://test-me.com $rate_db = safe_json_decode(get_obs_attrib('http_rate_' . $domain)); //print_vars($date); print_vars($rate_db); if (is_array($rate_db) && isset($rate_db[$date])) { $rate_count = $rate_db[$date]; } else { $rate_count = 0; } $rate_count++; set_obs_attrib('http_rate_' . $domain, safe_json_encode([$date => $rate_count])); if ($rate_count > $rate_limit) { print_debug("HTTP requests skipped because the rate limit $rate_limit/day for domain '$domain' is exceeded (count: $rate_count)"); //$GLOBALS['response_headers'] = [ 'code' => 429, 'descr' => 'Too Many Requests' ]; //$ok = FALSE; return TRUE; } if (OBS_DEBUG > 1) { print_debug("HTTP rate count for domain '$domain': $rate_count ($rate_limit/day)"); } } } return FALSE; } function print_debug_curl_cmd($request, $opts_http) { global $config; // DEBUG, generate curl cmd for testing: if (!defined('OBS_DEBUG') || !OBS_DEBUG) { return; } $curl_cmd = 'curl'; if (OBS_DEBUG > 1) { // Show response headers $curl_cmd .= ' -i'; } if (isset($config['http_ip_version'])) { $curl_cmd .= str_contains($config['http_ip_version'], '6') ? ' -6' : ' -4'; } if (isset($opts_http['timeout'])) { $curl_cmd .= ' --connect-timeout ' . $opts_http['timeout']; } if (isset($opts_http['method'])) { $curl_cmd .= ' -X ' . $opts_http['method']; } if (isset($opts_http['header'])) { foreach (explode("\r\n", $opts_http['header']) as $curl_header) { if (safe_empty($curl_header)) { continue; } $curl_cmd .= ' -H \'' . $curl_header . '\''; } } if (isset($opts_http['content'])) { $curl_cmd .= ' -d \'' . $opts_http['content'] . '\''; } // Proxy // -x, --proxy <[protocol://][user:password@]proxyhost[:port]> // -U, --proxy-user if (isset($config['http_proxy']) && $config['http_proxy']) { $http_proxy = $config['http_proxy']; // Basic proxy auth if (isset($config['proxy_user'], $config['proxy_password']) && $config['proxy_user']) { $http_proxy = $config['proxy_user'] . ':' . $config['proxy_password'] . '@' . $http_proxy; } $curl_cmd .= ' -x ' . $http_proxy; } print_cli("HTTP CURL cmd:\n$curl_cmd $request", FALSE); } /** * Http query by native php function, compat when curl not installed. * * @param string $request * @param array $context * @return array */ function process_http_php($request, $context = []) { global $config; // Add common http context $opts = [ 'http' => generate_http_context_defaults($context) ]; // Force IPv4 or IPv6 if (isset($config['http_ip_version'])) { // Bind to IPv4 -> 0:0 // Bind to IPv6 -> [::]:0 $bindto = str_contains($config['http_ip_version'], '6') ? '[::]:0' : '0:0'; $opts['socket'] = [ 'bindto' => $bindto ]; } // HTTPS // if ($parse_url = parse_url($request)) // { // if ($parse_url['scheme'] == 'https') // { // $opts['ssl'] = [ 'SNI_enabled' => TRUE, 'SNI_server_name' => $parse_url['host'] ]; // } // } // DEBUG, generate curl cmd for testing: print_debug_curl_cmd($request, $opts['http']); // Process http request and calculate runtime $start = utime(); $context = stream_context_create($opts); $response = file_get_contents($request, FALSE, $context); $end = utime(); $runtime = $end - $start; // Request end unixtime and runtime $GLOBALS['request_unixtime'] = $end; $GLOBALS['request_runtime'] = $runtime; // Parse response headers // Note: $http_response_header - see: http://php.net/manual/en/reserved.variables.httpresponseheader.php if (function_exists('http_get_last_response_headers')) { // PHP 8.4+ $http_response_header = http_get_last_response_headers(); } $head = []; foreach ($http_response_header as $k => $v) { $t = explode(':', $v, 2); if (isset($t[1])) { // Date: Sat, 12 Apr 2008 17:30:38 GMT $head[trim($t[0])] = trim($t[1]); } elseif (preg_match("!HTTP/([\d\.]+)\s+(\d+)(.*)!", $v, $matches)) { // HTTP/1.1 200 OK $head['http'] = $matches[1]; $head['code'] = (int)$matches[2]; $head['descr'] = trim($matches[3]); } else { $head[] = $v; } } return [ 'response' => $response, 'request_unixtime' => $end, 'request_runtime' => $runtime, 'response_headers' => $head ]; } function process_http_curl($request, $context = []) { global $config; $c = curl_init($request); $options = [ CURLOPT_RETURNTRANSFER => TRUE, // return response instead print CURLOPT_HTTP_CONTENT_DECODING => FALSE, // return raw response //CURLOPT_HEADER => TRUE, CURLINFO_HEADER_OUT => TRUE, // request headers CURLOPT_USERAGENT => OBSERVIUM_PRODUCT . '/' . OBSERVIUM_VERSION, //CURLOPT_CONNECTTIMEOUT => 0, //CURLOPT_TIMEOUT => 5, //CURLOPT_HTTPHEADER => $header, //CURLOPT_CUSTOMREQUEST => 'POST', //CURLOPT_POSTFIELDS => $fields, //CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4, //CURLOPT_ENCODING => '', //CURLOPT_DNS_USE_GLOBAL_CACHE => false, CURLOPT_SSL_VERIFYHOST => (bool)$config['http_ssl_verify'], // Disabled SSL Host check CURLOPT_SSL_VERIFYPEER => (bool)$config['http_ssl_verify'], // Disabled SSL Cert check ]; // Append options based on defaults from generate_http_context_defaults($context) $options[CURLOPT_TIMEOUT] = isset($context['timeout']) ? (int)$context['timeout'] : 15; // Proxy if (isset($config['http_proxy']) && $config['http_proxy']) { $options[CURLOPT_PROXY] = $config['http_proxy']; //$options[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP; // CURLPROXY_SOCKS4, CURLPROXY_SOCKS5, CURLPROXY_SOCKS4A, CURLPROXY_SOCKS5_HOSTNAME } // Basic proxy auth if (isset($config['proxy_user'], $config['proxy_password']) && $config['proxy_user']) { $options[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC; // CURLAUTH_NTLM $options[CURLOPT_PROXYUSERPWD] = $config['proxy_user'] . ':' . $config['proxy_password']; } // Headers if (isset($context['header'])) { $options[CURLOPT_HTTPHEADER] = explode("\r\n", trim($context['header'])); } if (!safe_empty($context['content'])) { $options[CURLOPT_POSTFIELDS] = $context['content']; print_debug_vars($context['content']); } if ($context['method']) { switch ($context['method']) { case 'GET': $options[CURLOPT_HTTPGET] = TRUE; break; case 'POST': $options[CURLOPT_POST] = TRUE; $options[CURLOPT_POSTREDIR] = 1; // follow 301 redir break; case 'PUT': $options[CURLOPT_PUT] = TRUE; $options[CURLOPT_POSTREDIR] = 1; // follow 301 redir break; default: $options[CURLOPT_CUSTOMREQUEST] = $context['method']; } } // Follow up to 3 redirects $options[CURLOPT_MAXREDIRS] = 3; $options[CURLOPT_FOLLOWLOCATION] = TRUE; // Force IPv4 or IPv6 if (isset($config['http_ip_version'])) { $options[CURLOPT_IPRESOLVE] = str_contains($config['http_ip_version'], '6') ? CURL_IPRESOLVE_V6 : CURL_IPRESOLVE_V4; } if (OBS_DEBUG) { $options[CURLOPT_VERBOSE] = TRUE; // DEBUG, generate curl cmd for testing: //print_debug_curl_cmd($request, $context); } curl_setopt_array($c, $options); $response = curl_exec($c); $end = utime(); //r($response); if (!curl_errno($c)) { // Normal response $http_info = curl_getinfo($c); print_debug_vars($http_info); //r($http_info); $head = [ 'http' => $http_info['http_version'], 'code' => $http_info['http_code'], //'descr' => curl_error($c) ]; if (preg_match("!HTTP/([\d\.]+)!", $http_info['request_header'], $matches)) { $head['http'] = $matches[1]; } // HTTP code descriptions switch ($http_info['http_code']) { case 100: $head['descr'] = 'Continue'; break; case 101: $head['descr'] = 'Switching Protocols'; break; case 200: $head['descr'] = 'OK'; break; case 201: $head['descr'] = 'Created'; break; case 202: $head['descr'] = 'Accepted'; break; case 203: $head['descr'] = 'Non-Authoritative Information'; break; case 204: $head['descr'] = 'No Content'; break; case 205: $head['descr'] = 'Reset Content'; break; case 206: $head['descr'] = 'Partial Content'; break; case 300: $head['descr'] = 'Multiple Choices'; break; case 301: $head['descr'] = 'Moved Permanently'; break; case 302: $head['descr'] = 'Moved Temporarily'; break; case 303: $head['descr'] = 'See Other'; break; case 304: $head['descr'] = 'Not Modified'; break; case 305: $head['descr'] = 'Use Proxy'; break; case 400: $head['descr'] = 'Bad Request'; break; case 401: $head['descr'] = 'Unauthorized'; break; case 402: $head['descr'] = 'Payment Required'; break; case 403: $head['descr'] = 'Forbidden'; break; case 404: $head['descr'] = 'Not Found'; break; case 405: $head['descr'] = 'Method Not Allowed'; break; case 406: $head['descr'] = 'Not Acceptable'; break; case 407: $head['descr'] = 'Proxy Authentication Required'; break; case 408: $head['descr'] = 'Request Time-out'; break; case 409: $head['descr'] = 'Conflict'; break; case 410: $head['descr'] = 'Gone'; break; case 411: $head['descr'] = 'Length Required'; break; case 412: $head['descr'] = 'Precondition Failed'; break; case 413: $head['descr'] = 'Request Entity Too Large'; break; case 414: $head['descr'] = 'Request-URI Too Large'; break; case 415: $head['descr'] = 'Unsupported Media Type'; break; case 500: $head['descr'] = 'Internal Server Error'; break; case 501: $head['descr'] = 'Not Implemented'; break; case 502: $head['descr'] = 'Bad Gateway'; break; case 503: $head['descr'] = 'Service Unavailable'; break; case 504: $head['descr'] = 'Gateway Time-out'; break; case 505: $head['descr'] = 'HTTP Version not supported'; break; default: $head['descr'] = 'Unknown HTTP status'; } $runtime = $http_info['total_time']; } else { $head = [ //'http' => $http_info['http_version'], 'code' => curl_errno($c), 'descr' => curl_error($c) ]; $runtime = curl_getinfo($c, CURLINFO_TOTAL_TIME); print_debug_vars(curl_getinfo($c)); } curl_close($c); return [ 'response' => $response, 'request_unixtime' => $end, 'request_runtime' => $runtime, 'response_headers' => $head ]; } /** * Process HTTP request by definition array and process it for valid status. * Used definition params in response key. * * @param string|array $def Definition array or alert transport key (see transports definitions) * @param string $response Response from get_http_request() * * @return boolean Return TRUE if request processed with valid HTTP code (2xx, 3xx) and API response return valid param */ function test_http_request($def, $response) { $response = trim($response); if (is_string($def)) { // Get transport definition for responses $def = $GLOBALS['config']['transports'][$def]['notification']; } // Response is array (or xml)? $is_response_array = strtolower($def['response_format']) === 'json'; // Set status by response status $success = get_http_last_status(); // If response return valid code and content, additional parse for specific defined tests if ($success) { // Decode if request OK if ($is_response_array) { $response = safe_json_decode($response); } // else additional formats? // Check if call succeeded if (isset($def['response_test'])) { // Convert single test condition to multi-level condition if (isset($def['response_test']['operator'])) { $def['response_test'] = [$def['response_test']]; } // Compare all definition fields with response, // if response param not equals to expected, set not success // multilevel keys should written with '->' separator, ie: $a[key][some][0] - key->some->0 foreach ($def['response_test'] as $test) { if ($is_response_array) { $field = array_get_nested($response, $test['field']); } else { // RAW response $field = $response; } if (test_condition($field, $test['operator'], $test['value']) === FALSE) { print_debug("Response [" . $field . "] not valid: [" . $test['field'] . "] " . $test['operator'] . " [" . implode(', ', (array)$test['value']) . "]"); $success = FALSE; break; } else { print_debug("Response [" . $field . "] valid: [" . $test['field'] . "] " . $test['operator'] . " [" . implode(', ', (array)$test['value']) . "]"); } } } } elseif ($is_response_array && isset($def['response_fields']['message'], $def['response_fields']['status'])) { // Decode response for useful error reports also for bad statuses $response = safe_json_decode($response); } if (!$success) { if (isset($def['response_fields']['message'], $def['response_fields']['status']) && is_array($response)) { echo PHP_EOL; if (isset($def['response_fields']['status'])) { if ($def['response_fields']['status'] === 'raw') { $status = get_http_last_code(); } else { $status = array_get_nested($response, $def['response_fields']['status']); } if (OBS_DEBUG) { print_message("%WRESPONSE STATUS%n[%r" . $status . "%n]", 'console'); } } $msg = array_get_nested($response, $def['response_fields']['message']); if (isset($def['response_fields']['info']) && $info = array_get_nested($response, $def['response_fields']['info'])) { $msg .= " ($info)"; } if (OBS_DEBUG) { print_message("%WRESPONSE ERROR%n[%y" . $msg . "%n]\n", 'console'); } $GLOBALS['last_message'] = $msg; } elseif (is_string($response) && $response && !get_http_last_status()) { if (OBS_DEBUG) { echo PHP_EOL; print_message("%WRESPONSE STATUS%n[%r" . get_http_last_code() . "%n]", 'console'); print_message("%WRESPONSE ERROR%n[%y" . $response . "%n]\n", 'console'); } $GLOBALS['last_message'] = $response; } } print_debug_vars($response, 1); return $success; } function process_http_request($def, $url, $options, &$response = NULL, $request_retry = 1) { if (is_string($def)) { // Get transport definition for responses $def = $GLOBALS['config']['transports'][$def]['notification']; } // Rate limit if (isset($def['ratelimit_key']) && !safe_empty($def['key'])) { // Ratelimit if used an api key $ratelimit = $def['ratelimit_key']; } elseif (isset($def['ratelimit'])) { $ratelimit = $def['ratelimit']; } else { $ratelimit = FALSE; } // Retry count (default 1, max 10). See discord definition if (isset($def['request_retry']) && is_intnum($def['request_retry']) && $def['request_retry'] > 1 && $def['request_retry'] <= 10) { $request_retry = $def['request_retry']; } $request_sleep = $request_retry > 1 ? 1 : 0; // sleep for 1s when retry count more than 1 $request_status = FALSE; // Send out API call and parse response for ($retry = 1; $retry <= (int)$request_retry; $retry++) { print_debug("Request [$url] #$retry:"); $response = get_http_request($url, $options, $ratelimit); if ($request_status = test_http_request($def, $response)) { // stop for on success return $request_status; } // wait little time sleep($request_sleep); } return $request_status; } /** * Return HTTP return code for last request by get_http_request() * * @return integer HTTP code */ function get_http_last_code() { return $GLOBALS['response_headers']['code']; } /** * Return HTTP return code for last request by get_http_request() * * @return boolean HTTP status TRUE if response code 2xx or 3xx */ function get_http_last_status() { return $GLOBALS['request_status']; } /** * Generate HTTP specific context with some defaults for proxy, timeout, user-agent. * Used in get_http_request(). * * @param array $context HTTP specified context, see http://php.net/manual/ru/function.stream-context-create.php * * @return array HTTP context array */ function generate_http_context_defaults($context = []) { global $config; if (!is_array($context)) { $context = []; } // Fix context if not array passed // Defaults if (!isset($context['timeout'])) { $context['timeout'] = '15'; } // HTTP/1.1 $context['protocol_version'] = 1.1; // get the entire body of the response in case of error (HTTP/1.1 400, for example) if (OBS_DEBUG) { $context['ignore_errors'] = TRUE; } // User agent (required for some type of queries, ie geocoding) if (!isset($context['header'])) { $context['header'] = ''; // Avoid 'undefined index' when concatting below } $context['header'] .= 'User-Agent: ' . OBSERVIUM_PRODUCT . '/' . OBSERVIUM_VERSION . "\r\n"; if (isset($config['http_proxy']) && $config['http_proxy']) { $context['proxy'] = 'tcp://' . $config['http_proxy']; $context['request_fulluri'] = !isset($config['proxy_fulluri']) || (bool)$config['proxy_fulluri']; } // Basic proxy auth if (isset($config['proxy_user'], $config['proxy_password']) && $config['proxy_user']) { $auth = base64_encode($config['proxy_user'] . ':' . $config['proxy_password']); $context['header'] .= 'Proxy-Authorization: Basic ' . $auth . "\r\n"; } print_debug_vars($context); return $context; } function generate_http_data($def, $tags = [], &$params = []) { if (isset($def['request_params_key'])) { // Key based link to request params, see google-chat notification $key = 'request_params_' . strtolower(array_tag_replace($tags, $def['request_params_key'])); $request_params = $def[$key] ?? $def['request_params']; } else { // Common default request_params $request_params = $def['request_params']; } // Generate request params foreach ((array)$request_params as $param => $entry) { // Param based on expression (only true/false) // See: pushover notification def if (str_contains($param, '?')) { [ $param, $param_if ] = explode('?', $param, 2); //print_vars($tags); //print_vars($params); if (!isset($tags[$param_if]) || !get_var_true($tags[$param_if])) { print_debug("Request param '$param' skipped, because other param '$param_if' false or unset."); continue; } } // Try to find all keys in header like %bot_hash% matched with same key in $endpoint array if (is_array($entry)) { // i.e., teams and pagerduty $params[$param] = array_merge((array)$params[$param], array_tag_replace($tags, $entry)); } elseif (!isset($params[$param]) || $params[$param] === '') { $params[$param] = array_tag_replace($tags, $entry); } // Clean empty params if (safe_empty($params[$param])) { unset($params[$param]); } } if (($def['method'] === 'POST' || $def['method'] === 'PUT') && strtolower($def['request_format']) === 'json') { if (OBS_DEBUG) { print_debug("\nJSON embedded params for method: ${def['method']}\n"); print_vars(safe_json_encode($params)); } // Encode params as json string return safe_json_encode($params); } // Encode params as url encoded string return http_build_query($params); } /** * Generate HTTP context based on passed params, tags and definition. * This context will be used in get_http_request() * * @param string|array $def Definition array or alert transport key (see transports definitions) * @param array $tags (optional) Contact array and other tags * @param array $params (optional) Array of requested params with key => value entries (used with request method POST) * * @return array HTTP Context which can used in get_http_request() * @global array $config */ function generate_http_context($def, $tags = [], $params = []) { global $config; if (is_string($def)) { // Get transport definition for requests $def = $config['transports'][$def]['notification']; } $context = []; // Init // Request method POST/GET if ($def['method']) { $def['method'] = strtoupper($def['method']); $context['method'] = $def['method']; } // Request timeout if (is_intnum($def['timeout']) || $def['timeout'] >= 1 || $def['timeout'] <= 300) { $context['timeout'] = $def['timeout']; } // Content and headers $header = "Connection: close\r\n"; // Add encode $params for POST request inside http headers if ($def['method'] === 'POST' || $def['method'] === 'PUT') { $data = generate_http_data($def, $tags, $params); if (strtolower($def['request_format']) === 'json') { // Encode params as json string //$data = safe_json_encode($params); $header .= "Content-Type: application/json; charset=utf-8\r\n"; } else { // Encode params as url encoded string //$data = http_build_query($params); // https://stackoverflow.com/questions/4007969/application-x-www-form-urlencoded-or-multipart-form-data //$header .= "Content-Type: multipart/form-data\r\n"; $header .= "Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n"; } $header .= "Content-Length: " . strlen($data) . "\r\n"; // Encoded content data $context['content'] = $data; } // Basic auth if (isset($def['request_user'])) { $basic_auth = $def['request_user']; if (isset($def['request_password'])) { $basic_auth .= ':' . $def['request_password']; } $basic_auth = array_tag_replace($tags, $basic_auth); $header .= 'Authorization: Basic ' . base64_encode($basic_auth) . "\r\n"; } // Additional headers with contact params foreach ($def['request_header'] as $key => $entries) { [ $head, $tag ] = explode('?', $key, 2); if ($tag && (!isset($tags[$tag]) || !$tags[$tag])) { // see webhook json transport continue; } // Try to find all keys in header like %bot_hash% matched with same key in $endpoint array foreach ((array)$entries as $entry) { // multiple same headers can be exist at same time. $header .= $head . ': ' . array_tag_replace($tags, $entry) . "\r\n"; } } $context['header'] = $header; return $context; } /** * Generate URL based on passed params, tags and definition. * This context will be used in get_http_request() or process_http_request() * * @param string|array $def Definition array or alert transport key (see transports definitions) * @param array $tags (optional) Contact array, used only if transport required additional headers (ie hipchat) * @param array $params (optional) Array of requested params with key => value entries (used with request method GET) * @param string $url_key Definition key which used for get url, default is $def['url'] * * @return string URL which can used in get_http_request() or process_http_request() * @global array $config */ function generate_http_url($def, $tags = [], $params = [], $url_key = 'url') { global $config; if (is_string($def)) { // Get definition for transport API $def = $config['transports'][$def]['notification']; } $url = ''; // Init // Append (if set $def['url']) or set hardcoded url for transport if (isset($def[$url_key])) { // Try to find all keys in URL like %bot_hash% and %%url_encoded%% matched with the same key in $endpoint array $url .= array_tag_replace_encode($tags, $def[$url_key]); } // Add GET params to url if (($def['method'] === 'GET' || $def['method'] === 'DELETE')) { $data = generate_http_data($def, $tags, $params); if (safe_count($params)) { if (str_contains($url, '?')) { // Append additional params to url string $url .= '&' . $data; } else { // Add get params as first time $url .= '?' . $data; } } } //print_debug_vars($def); //print_debug_vars($params); //print_debug_vars($data); print_debug_vars($url); return $url; } function get_http_def($http_def, $tags) { global $config; if (!is_array($http_def)) { $http_def = $config['http_api'][$http_def]; } // Generate context/options with encoded data $options = generate_http_context($http_def, $tags); // API URL $url = generate_http_url($http_def, $tags); // Request if (process_http_request($http_def, $url, $options, $response)) { return $http_def['response_format'] === 'json' ? safe_json_decode($response) : $response; } return FALSE; } // EOF