/* Released under AGPL v3 with exception for the OpenSSL library. See license.txt */

#include "config.h"
#include <libintl.h>
#define _GNU_SOURCE
#define __USE_XOPEN
#include <time.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <locale.h>
#include "gen.h"
#include <stdlib.h>
#include <errno.h>
#include <math.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#ifndef NO_SSL
#include <openssl/ssl.h>
#include "mssl.h"
#endif
#include <arpa/inet.h>
#include <sys/time.h>
#if defined(sun) || defined(__sun)
#include <sys/termios.h>
#endif
#if HAVE_NCURSES
#include <ncurses.h>
#endif

#include "gen.h"
#include "help.h"
#include "colors.h"
#include "http.h"
#include "io.h"
#include "tcp.h"
#include "res.h"
#include "utils.h"
#include "error.h"
#include "socks5.h"
#if HAVE_NCURSES
#include "nc.h"
#endif
#include "cookies.h"

volatile int stop = 0;

int quiet = 0;
char machine_readable = 0;
char json_output = 0;
char show_ts = 0;

int max_x = 80, max_y = 24;

char nagios_mode = 0;
char ncurses_mode = 0;

int fd = -1;

volatile char got_sigquit = 0;

void handler_quit(int s)
{
	signal(SIGQUIT, handler_quit);

	got_sigquit = 1;
}

void determine_terminal_size(int *max_y, int *max_x)
{
        struct winsize size;

        *max_x = *max_y = 0;

	if (!isatty(1))
	{
		*max_y = 24;
		*max_x = 80;
	}
#ifdef TIOCGWINSZ
        else if (ioctl(1, TIOCGWINSZ, &size) == 0)
        {
                *max_y = size.ws_row;
                *max_x = size.ws_col;
        }
#endif

        if (!*max_x || !*max_y)
        {
                char *dummy = getenv("COLUMNS");
                if (dummy)
                        *max_x = atoi(dummy);
                else
                        *max_x = 80;

                dummy = getenv("LINES");
                if (dummy)
                        *max_y = atoi(dummy);
                else
                        *max_y = 24;
        }
}

void emit_statuslines(double run_time)
{
#if HAVE_NCURSES
	if (ncurses_mode)
	{
		time_t t = time(NULL);
		char *t_str = ctime(&t);
		char *dummy = strchr(t_str, '\n');

		if (dummy)
			*dummy = 0x00;

		status_line(gettext("%s, run time: %.3fs, press ctrl + c to stop"), t_str, run_time);
	}
#else
	(void)run_time;
#endif
}

void emit_headers(char *in)
{
#if HAVE_NCURSES
	int len_in = -1;

	if (ncurses_mode && in != NULL && (len_in = strlen(in) - 4) > 0)
	{
		extern WINDOW *w_slow;
		werase(w_slow);

		int pos = 0, pos_out = 0;
		char *copy = (char *)malloc(len_in + 1), *dummy = NULL;

		for(pos=0; pos<len_in; pos++)
		{
			if (in[pos] != '\r')
				copy[pos_out++] = in[pos];
		}

		copy[pos_out] = 0x00;

		/* in case more than the headers were sent */
		dummy = strstr(copy, "\n\n");
		if (dummy)
			*dummy = 0x00;

		slow_log("\n%s", copy);

		free(copy);
	}
#else
	(void)in;
#endif
}

void emit_json(char ok, int seq, double start_ts, stats_t *t_resolve, stats_t *t_connect, stats_t *t_request, int http_code, const char *msg, int header_size, int data_size, double Bps, const char *host, const char *ssl_fp, double toff_diff_ts, char tfo_success, stats_t *t_ssl, stats_t *t_write, stats_t *t_close, int n_cookies, stats_t *stats_to, stats_t *tcp_rtt_stats, int re_tx, int pmtu, int recv_tos, stats_t *t_total)
{
	if (seq > 1)
		printf(", \n");
	printf("{ ");
	printf("\"status\" : \"%d\", ", ok);
	printf("\"seq\" : \"%d\", ", seq);
	printf("\"start_ts\" : \"%f\", ", start_ts);
	if (t_resolve!=NULL && t_resolve -> cur_valid)
		printf("\"resolve_ms\" : \"%e\", ", t_resolve -> cur);
	else
		printf("\"resolve_ms\" : \"%e\", ",-1.0);
	if (t_connect!=NULL && t_connect -> cur_valid)
		printf("\"connect_ms\" : \"%e\", ", t_connect -> cur);
	else
		printf("\"connect_ms\" : \"%e\", ",-1.0);
	if (t_request != NULL)
		printf("\"request_ms\" : \"%e\", ", t_request -> cur);
	if (t_total != NULL)
		printf("\"total_ms\" : \"%e\", ", t_total -> cur);
	printf("\"http_code\" : \"%d\", ", http_code);
	printf("\"msg\" : \"%s\", ", msg);
	printf("\"header_size\" : \"%d\", ", header_size);
	printf("\"data_size\" : \"%d\", ", data_size);
	printf("\"bps\" : \"%f\", ", Bps);
	printf("\"host\" : \"%s\", ", host);
	printf("\"ssl_fingerprint\" : \"%s\", ", ssl_fp ? ssl_fp : "");
	printf("\"time_offset\" : \"%f\", ", toff_diff_ts);
	printf("\"tfo_success\" : \"%s\", ", tfo_success ? "true" : "false");
	if (t_ssl != NULL && t_ssl -> cur_valid)
		printf("\"ssl_ms\" : \"%e\", ", t_ssl -> cur);
	printf("\"tfo_succes\" : \"%s\", ", tfo_success ? "true" : "false");
	if (t_ssl !=NULL && t_ssl -> cur_valid)
		printf("\"ssl_ms\" : \"%e\", ", t_ssl -> cur);
	if (t_write != NULL)
		printf("\"write\" : \"%e\", ", t_write -> cur);
	if (t_close != NULL)
		printf("\"close\" : \"%e\", ", t_close -> cur);
	printf("\"cookies\" : \"%d\", ", n_cookies);
	if (stats_to != NULL && stats_to -> cur_valid)
		printf("\"to\" : \"%e\", ", stats_to -> cur);
	if (tcp_rtt_stats !=NULL && tcp_rtt_stats -> cur_valid)
		printf("\"tcp_rtt_stats\" : \"%e\", ", tcp_rtt_stats -> cur);
	printf("\"re_tx\" : \"%d\", ", re_tx);
	printf("\"pmtu\" : \"%d\", ", pmtu);
	printf("\"tos\" : \"%02x\" ", recv_tos);
	printf("}");
}

char *get_ts_str(int verbose)
{
	char buffer[4096] = { 0 };
	struct tm *tvm = NULL;
	struct timeval tv;

	(void)gettimeofday(&tv, NULL);

	tvm = localtime(&tv.tv_sec);

	if (verbose == 1)
		sprintf(buffer, "%04d/%02d/%02d ", tvm -> tm_year + 1900, tvm -> tm_mon + 1, tvm -> tm_mday);
	else if (verbose >= 2)
		sprintf(buffer, "%.6f", get_ts());

	if (verbose <= 1)
		sprintf(&buffer[strlen(buffer)], "%02d:%02d:%02d.%03d", tvm -> tm_hour, tvm -> tm_min, tvm -> tm_sec, (int)(tv.tv_usec / 1000));

	return strdup(buffer);
}

void emit_error(int verbose, int seq, double start_ts)
{
	char *ts = show_ts ? get_ts_str(verbose) : NULL;

#if HAVE_NCURSES
	if (ncurses_mode)
	{
		slow_log("\n%s%s", ts ? ts : "", get_error());
		update_terminal();
	}
	else
#endif
		if (!quiet && !machine_readable && !nagios_mode && !json_output)
			printf("%s%s%s%s\n", ts ? ts : "", c_error, get_error(), c_normal);

	if (json_output)
		emit_json(0, seq, start_ts, NULL, NULL, NULL, -1, get_error(), -1, -1, -1, "", "", -1, 0, NULL, NULL, NULL, 0, NULL, NULL, 0, 0, 0, NULL);

	clear_error();

	free(ts);

	fflush(NULL);
}

void handler(int sig)
{
#if HAVE_NCURSES
	if (sig == SIGWINCH)
		win_resize = 1;
	else
#endif
	{
		if (!json_output)
			fprintf(stderr, gettext("Got signal %d\n"), sig);

		stop = 1;
	}
}

char * read_file(const char *file)
{
	char buffer[4096] = { 0 }, *lf = NULL;
	FILE *fh = fopen(file, "rb");
	if (!fh)
		error_exit(gettext("Cannot open password-file %s"), file);

	if (!fgets(buffer, sizeof buffer, fh))
		error_exit(gettext("Problem reading password from file %s"), file);

	fclose(fh);

	lf = strchr(buffer, '\n');
	if (lf)
		*lf = 0x00;

	return strdup(buffer);
}

char * create_http_request_header(const char *get, char use_proxy_host, char get_instead_of_head, char persistent_connections, const char *hostname, const char *useragent, const char *referer, char ask_compression, char no_cache, const char *auth_usr, const char *auth_password, char **static_cookies, int n_static_cookies, char **dynamic_cookies, int n_dynamic_cookies, const char *proxy_buster, const char *proxy_user, const char *proxy_password, char **additional_headers, int n_additional_headers, char http2, char is_ssl)
{
	int index;
	char *request = NULL;
	char pb[128] = { 0 };

	if (proxy_buster)
	{
		if (strchr(get, '?'))
			pb[0] = '&';
		else
			pb[0] = '?';

		snprintf(pb + 1, sizeof pb - 1, "%s=%ld", proxy_buster, lrand48());
	}

	if (use_proxy_host)
		str_add(&request, "%s %s%s HTTP/1.%c\r\n", get_instead_of_head?"GET":"HEAD", get, pb, persistent_connections || http2 ? '1':'0');
	else
	{
		const char *dummy = get, *slash = NULL;
		if (strncasecmp(dummy, "http://", 7) == 0)
			dummy += 7;
		else if (strncasecmp(dummy, "https://", 7) == 0)
			dummy += 8;

		slash = strchr(dummy, '/');
		if (slash)
			str_add(&request, "%s %s HTTP/1.%c\r\n", get_instead_of_head?"GET":"HEAD", slash, persistent_connections || http2?'1':'0');
		else
			str_add(&request, "%s / HTTP/1.%c\r\n", get_instead_of_head?"GET":"HEAD", persistent_connections || http2 ? '1':'0');
	}

	if (hostname)
		str_add(&request, "Host: %s\r\n", hostname);

	if (useragent)
		str_add(&request, "User-Agent: %s\r\n", useragent);
	else
		str_add(&request, "User-Agent: HTTPing v" VERSION "\r\n");

	if (referer)
		str_add(&request, "Referer: %s\r\n", referer);

	if (ask_compression)
		str_add(&request, "Accept-Encoding: gzip,deflate\r\n");

	if (http2) {
		str_add(&request, "Connection: Upgrade, HTTP2-Settings\r\n");
		if (is_ssl) // https
			str_add(&request, "Upgrade: h2\r\n");
		else
			str_add(&request, "Upgrade: h2c\r\n");
		str_add(&request, "HTTP2-Settings: \r\n");
	}

	if (no_cache)
	{
		str_add(&request, "Pragma: no-cache\r\n");
		str_add(&request, "Cache-Control: no-cache\r\n");
	}

	/* Basic Authentification */
	if (auth_usr)
	{ 
		char auth_string[256] = { 0 };
		char b64_auth_string[512] = { 0 };

		sprintf(auth_string, "%s:%s", auth_usr, auth_password); 
		enc_b64(auth_string, strlen(auth_string), b64_auth_string);

		str_add(&request, "Authorization: Basic %s\r\n", b64_auth_string);
	}

	/* proxy authentication */
	if (proxy_user)
	{ 
		char ppa_string[256] = { 0 };
		char b64_ppa_string[512] = { 0 };

		sprintf(ppa_string, "%s:%s", proxy_user, proxy_password); 
		enc_b64(ppa_string, strlen(ppa_string), b64_ppa_string);

		str_add(&request, "Proxy-Authorization: Basic %s\r\n", b64_ppa_string);
	}

	/* Cookie insertion */
	for(index=0; index<n_static_cookies; index++)
		str_add(&request, "Cookie: %s\r\n", static_cookies[index]);
	for(index=0; index<n_dynamic_cookies; index++)
		str_add(&request, "Cookie: %s\r\n", dynamic_cookies[index]);

	for(index=0; index<n_additional_headers; index++)
		str_add(&request, "%s\r\n", additional_headers[index]);

	if (persistent_connections)
		str_add(&request, "Connection: keep-alive\r\n");

	str_add(&request, "\r\n");

	return request;
}

void interpret_url(const char *in, char **path, char **hostname, int *portnr, char use_ipv6, char use_ssl, char **complete_url, char **auth_user, char **auth_password)
{
	char in_use[65536] = { 0 }, *dummy = NULL;

	if (strlen(in) >= sizeof in_use)
		error_exit(gettext("URL too big, HTTPing has a %d bytes limit"), sizeof in_use - 1);

	/* make url complete, if not already */
	if (strncasecmp(in, "http://", 7) == 0 || strncasecmp(in, "https://", 8) == 0) /* complete url? */
	{
		snprintf(in_use, sizeof in_use - 1, "%s", in);

		if (strchr(&in[8], '/') == NULL)
			in_use[strlen(in_use)] = '/';
	}
	else if (strchr(in, '/')) /* hostname + location without 'http://'? */
		sprintf(in_use, "http://%s", in);
	else if (use_ssl)
		sprintf(in_use, "https://%s/", in);
	else
		sprintf(in_use, "http://%s/", in);

	/* sanity check */
	if (strncasecmp(in_use, "http://", 7) == 0 && use_ssl)
		error_exit(gettext("using \"http://\" with SSL enabled (-l)"));

	*complete_url = strdup(in_use);

	/* fetch hostname */
	if (strncasecmp(in_use, "http://", 7) == 0)
		*hostname = strdup(&in_use[7]);
	else /* https */
		*hostname = strdup(&in_use[8]);

	dummy = strchr(*hostname, '/');
	if (dummy)
		*dummy = 0x00;

	/* fetch port number */
	if (*portnr == 0)
	{
		if (use_ssl || strncasecmp(in, "https://", 8) == 0)
			*portnr = 443;
		else
			*portnr = 80;
	}

	if (!use_ipv6)
	{
		char *at = strchr(*hostname, '@');
		char *colon = strchr(*hostname, ':');
		char *colon2 = colon ? strchr(colon + 1, ':') : NULL;

		if (colon2)
		{
			*colon2 = 0x00;
			*portnr = atoi(colon2 + 1);

			if (at)
			{
				*colon = 0x00;
				*at = 0x00;

				*auth_user = strdup(*hostname);
				*auth_password = strdup(colon + 1);
			}
		}
		else if (colon)
		{
			if (colon < at)
			{
				*colon = 0x00;
				*at = 0x00;

				*auth_user = strdup(*hostname);
				*auth_password = strdup(colon + 1);
			}
			else if (at)
			{
				*at = 0x00;
				*auth_user = strdup(*hostname);
			}
			else
			{
				*colon = 0x00;
				*portnr = atoi(colon + 1);
			}
		}
	}

	/* fetch path */
	dummy = strchr(&in_use[8], '/');
	if (dummy)
		*path = strdup(dummy);
	else
		*path = strdup("/");
}

typedef struct {
	int interval, last_ts;
	double value, sd, min, max;
	int n_values;
} aggregate_t;

void set_aggregate(char *in, int *n_aggregates, aggregate_t **aggregates)
{
	char *dummy = in;

	*n_aggregates = 0;

	for(;dummy;)
	{
		(*n_aggregates)++;

		*aggregates = (aggregate_t *)realloc(*aggregates, *n_aggregates * sizeof(aggregate_t));

		memset(&(*aggregates)[*n_aggregates - 1], 0x00, sizeof(aggregate_t));

		(*aggregates)[*n_aggregates - 1].interval = atoi(dummy);
		(*aggregates)[*n_aggregates - 1].max = -MY_DOUBLE_INF;
		(*aggregates)[*n_aggregates - 1].min =  MY_DOUBLE_INF;

		dummy = strchr(dummy, ',');
		if (dummy)
			dummy++;
	}
}

void do_aggregates(double cur_ms, int cur_ts, int n_aggregates, aggregate_t *aggregates, int verbose, char show_ts)
{
	int index=0;

	/* update measurements */
	for(index=0; index<n_aggregates; index++)
	{
		aggregates[index].value += cur_ms;

		if (cur_ms < aggregates[index].min)
			aggregates[index].min = cur_ms;

		if (cur_ms > aggregates[index].max)
			aggregates[index].max = cur_ms;

		aggregates[index].sd += cur_ms * cur_ms;

		aggregates[index].n_values++;
	}

	/* emit */
	for(index=0; index<n_aggregates && cur_ts > 0; index++)
	{
		aggregate_t *a = &aggregates[index];

		if (cur_ts - a -> last_ts >= a -> interval)
		{
			char *line = NULL;
			double avg = a -> n_values ? a -> value / (double)a -> n_values : -1.0;
			char *ts = get_ts_str(verbose);

			str_add(&line, "%s", show_ts ? ts : "");
			free(ts);

			str_add(&line, gettext("AGG[%d]: %d values, min/avg/max%s = %.1f/%.1f/%.1f"), a -> interval, a -> n_values, verbose ? gettext("/sd") : "", a -> min, avg, a -> max);

			if (verbose)
			{
				double sd = -1.0;

				if (a -> n_values)
					sd = sqrt((a -> sd / (double)a -> n_values) - pow(avg, 2.0));

				str_add(&line, "/%.1f", sd);
			}

			str_add(&line, " ms");

#if HAVE_NCURSES
			if (ncurses_mode)
				slow_log("\n%s", line);
			else
#endif
				printf("%s\n", line);

			free(line);

			aggregates[index].value =
				aggregates[index].sd    = 0.0;
			aggregates[index].min =  MY_DOUBLE_INF;
			aggregates[index].max = -MY_DOUBLE_INF;
			aggregates[index].n_values = 0;
			aggregates[index].last_ts = cur_ts;
		}
	}
}

void fetch_proxy_settings(char **proxy_user, char **proxy_password, char **proxy_host, int *proxy_port, char use_ssl, char use_ipv6)
{
	char *str = getenv(use_ssl ? "https_proxy" : "http_proxy");

	if (!str)
	{
		/* FIXME from wgetrc/curlrc? */
	}

	if (str)
	{
		char *path = NULL, *url = NULL;

		interpret_url(str, &path, proxy_host, proxy_port, use_ipv6, use_ssl, &url, proxy_user, proxy_password);

		free(url);
		free(path);
	}
}

void parse_nagios_settings(const char *in, double *nagios_warn, double *nagios_crit)
{
	char *dummy = strchr(in, ',');
	if (!dummy)
		error_exit(gettext("-n: missing parameter\n"));

	*nagios_warn = atof(in);

	*nagios_crit = atof(dummy + 1);
}

void parse_bind_to(const char *in, struct sockaddr_in *bind_to_4, struct sockaddr_in6 *bind_to_6, struct sockaddr_in **bind_to)
{
	char *dummy = strchr(in, ':');

	if (dummy)
	{
		*bind_to = (struct sockaddr_in *)bind_to_6;
		memset(bind_to_6, 0x00, sizeof *bind_to_6);
		bind_to_6 -> sin6_family = AF_INET6;

		if (inet_pton(AF_INET6, in, &bind_to_6 -> sin6_addr) != 1)
			error_exit(gettext("cannot convert ip address '%s' (for -y)\n"), in);
	}
	else
	{
		*bind_to = (struct sockaddr_in *)bind_to_4;
		memset(bind_to_4, 0x00, sizeof *bind_to_4);
		bind_to_4 -> sin_family = AF_INET;

		if (inet_pton(AF_INET, in, &bind_to_4 -> sin_addr) != 1)
			error_exit(gettext("cannot convert ip address '%s' (for -y)\n"), in);
	}
}

time_t parse_date_from_response_headers(const char *in)
{
	char *date = NULL, *komma = NULL;
	if (in == NULL)
		return -1;

	date = strstr(in, "\nDate:");
	komma = date ? strchr(date, ',') : NULL;
	if (date && komma)
	{
		struct tm tm;
		memset(&tm, 0x00, sizeof tm);

		/* 22 Feb 2015 09:13:56 */
		if (strptime(komma + 1, "%d %b %Y %H:%M:%S %Z", &tm))
			return mktime(&tm);
	}

	return -1;
}

int calc_page_age(const char *in, const time_t their_ts)
{
	int age = -1;

	if (in != NULL && their_ts > 0)
	{
		char *date = strstr(in, "\nLast-Modified:");
		char *komma = date ? strchr(date, ',') : NULL;
		if (date && komma)
		{
			struct tm tm;
			memset(&tm, 0x00, sizeof tm);

			/* 22 Feb 2015 09:13:56 */
			if (strptime(komma + 1, "%d %b %Y %H:%M:%S %Z", &tm))
				age = their_ts - mktime(&tm);
		}
	}

	return age;
}

const char *get_location(const char *host, int port, char use_ssl, char *reply)
{
	if (reply)
	{
		char *copy = strdup(reply);
		char *head = strstr(copy, "\nLocation:");
		char *lf = head ? strchr(head + 1, '\n') : NULL;

		if (head)
		{
			char *buffer = NULL;
			char *dest = head + 11;

			if (lf)
				*lf = 0x00;

			if (memcmp(dest, "http", 4) == 0)
				str_add(&buffer, "%s", dest);
			else
				str_add(&buffer, "http%s://%s:%d%s", use_ssl ? "s" : "", host, port, dest);

			free(copy);

			return buffer;
		}

		free(copy);
	}

	return NULL;
}

char check_compressed(const char *reply)
{
	if (reply != NULL)
	{
		char *encoding = strstr(reply, "\nContent-Encoding:");

		if (encoding)
		{
			char *dummy = strchr(encoding + 1, '\r');
			if (dummy) *dummy = 0x00;

			dummy = strchr(encoding + 1, '\n');
			if (dummy) *dummy = 0x00;

			if (strstr(encoding, "gzip") == 0 || strstr(encoding, "deflate") == 0)
				return 1;
		}
	}

	return 0;
}

int nagios_result(int ok, int nagios_mode, int nagios_exit_code, double avg_httping_time, double nagios_warn, double nagios_crit)
{
	if (nagios_mode == 1)
	{
		if (ok == 0)
		{
			printf(gettext("CRITICAL - connecting failed: %s"), get_error());
			return 2;
		}
		else if (avg_httping_time >= nagios_crit)
		{
			printf(gettext("CRITICAL - average httping-time is %.1f\n"), avg_httping_time);
			return 2;
		}
		else if (avg_httping_time >= nagios_warn)
		{
			printf(gettext("WARNING - average httping-time is %.1f\n"), avg_httping_time);
			return 1;
		}

		printf(gettext("OK - average httping-time is %.1f (%s)|ping=%f\n"), avg_httping_time, get_error(), avg_httping_time);

		return 0;
	}
	else if (nagios_mode == 2)
	{
		const char *err = get_error();

		if (ok && err[0] == 0x00)
		{
			printf(gettext("OK - all fine, avg httping time is %.1f|ping=%f\n"), avg_httping_time, avg_httping_time);
			return 0;
		}

		printf(gettext("%s: - failed: %s"), nagios_exit_code == 1?"WARNING":(nagios_exit_code == 2?"CRITICAL":"ERROR"), err);
		return nagios_exit_code;
	}

	return -1;
}

void proxy_to_host_and_port(char *in, char **proxy_host, int *proxy_port)
{
	if (in[0] == '[')
	{
		char *dummy = NULL;

		*proxy_host = strdup(in + 1);

		dummy = strchr(*proxy_host, ']');
		if (dummy)
		{
			*dummy = 0x00;

			/* +2: ']:' */
			*proxy_port = atoi(dummy + 2);
		}
	}
	else
	{
		char *dummy = strchr(in, ':');

		*proxy_host = in;

		if (dummy)
		{
			*dummy=0x00;
			*proxy_port = atoi(dummy + 1);
		}
	}
}

void stats_close(int *fd, stats_t *t_close, char is_failure)
{
	double t_start = get_ts(), t_end = -1;;

	if (is_failure)
		failure_close(*fd);
	else
		close(*fd);

	*fd = -1;

	t_end = get_ts();

	update_statst(t_close, (t_end - t_start) * 1000.0);
}

void add_header(char ***additional_headers, int *n_additional_headers, const char *in)
{
	*additional_headers = (char **)realloc(*additional_headers, (*n_additional_headers + 1) * sizeof(char **));
	(*additional_headers)[*n_additional_headers] = strdup(in);

	(*n_additional_headers)++;
}

void free_headers(char **additional_headers, int n_additional_headers)
{
	int index = 0;

	for(index=0; index<n_additional_headers; index++)
		free(additional_headers[index]);

	free(additional_headers);
}

typedef struct
{
	double Bps_min, Bps_max;
	long long int Bps_avg;
} bps_t;

void stats_line(const char with_header, const char *const complete_url, const int count, const int curncount, const int err, const int ok, double started_at, const char verbose, const stats_t *const t_total, const double avg_httping_time, bps_t *const bps)
{
	double total_took = get_ts() - started_at;
	int dummy = count;

	if (with_header)
		printf(gettext("--- %s ping statistics ---\n"), complete_url);

	if (curncount == 0 && err > 0)
		fprintf(stderr, gettext("internal error! (curncount)\n"));

	if (count == -1)
		dummy = curncount;

	printf(gettext("%s%d%s connects, %s%d%s ok, %s%3.2f%%%s failed, time %s%s%.0fms%s\n"), c_yellow, curncount, c_normal, c_green, ok, c_normal, c_red, (((double)err) / ((double)dummy)) * 100.0, c_normal, c_blue, c_bright, total_took * 1000.0, c_normal);

	if (ok > 0)
	{
		if (t_total->calc_median)
			printf(gettext("round-trip min/avg/median/max%s = %s%.1f%s/%s%.1f%s/%.1f/%s%.1f%s"), verbose ? gettext("/sd") : "", c_bright, t_total -> min, c_normal, c_bright, avg_httping_time, c_normal, calc_median(t_total), c_bright, t_total -> max, c_normal);
		else
			printf(gettext("round-trip min/avg/max%s = %s%.1f%s/%s%.1f%s/%s%.1f%s"), verbose ? gettext("/sd") : "", c_bright, t_total -> min, c_normal, c_bright, avg_httping_time, c_normal, c_bright, t_total -> max, c_normal);

		if (verbose)
		{
			double sd_final = t_total -> n ? sqrt((t_total -> sd / (double)t_total -> n) - pow(avg_httping_time, 2.0)) : -1.0;
			printf("/%.1f", sd_final);
		}

		printf(" ms\n");

		if (bps)
			printf(gettext("Transfer speed: min/avg/max = %s%f%s/%s%f%s/%s%f%s KB\n"), c_bright, bps -> Bps_min / 1024, c_normal, c_bright, (bps -> Bps_avg / (double)ok) / 1024.0, c_normal, c_bright, bps -> Bps_max / 1024.0, c_normal);
	}
}

int main(int argc, char *argv[])
{
	double started_at = -1;
	char do_fetch_proxy_settings = 0;
	char *hostname = NULL;
	char *proxy_host = NULL, *proxy_user = NULL, *proxy_password = NULL;
	int proxy_port = 8080;
	int portnr = 0;
	char *get = NULL, *request = NULL;
	int req_len = 0;
	int c = 0;
	int count = -1, curncount = 0;
	double wait = 1.0;
	char wait_set = 0;
	int audible = 0;
	int ok = 0, err = 0;
	double timeout = 30.0;
	char show_statuscodes = 0;
	char use_ssl = 0;
	const char *ok_str = "200";
	const char *err_str = "-1";
	const char *useragent = NULL;
	const char *referer = NULL;
	const char *auth_password = NULL;
	const char *auth_usr = NULL;
	char **static_cookies = NULL, **dynamic_cookies = NULL;
	int n_static_cookies = 0, n_dynamic_cookies = 0;
	char resolve_once = 0;
	char have_resolved = 0;
	double nagios_warn=0.0, nagios_crit=0.0;
	int nagios_exit_code = 2;
	double avg_httping_time = -1.0;
	int get_instead_of_head = 0;
	char show_Bps = 0, ask_compression = 0;
	bps_t bps;
	int Bps_limit = -1;
	char show_bytes_xfer = 0, show_fp = 0;
	SSL *ssl_h = NULL;
	BIO *s_bio = NULL;
	struct sockaddr_in *bind_to = NULL;
	struct sockaddr_in bind_to_4;
	struct sockaddr_in6 bind_to_6;
	char split = 0, use_ipv6 = 0;
	char persistent_connections = 0, persistent_did_reconnect = 0;
	char no_cache = 0;
	char use_tfo = 0;
	char abort_on_resolve_failure = 1;
	double offset_yellow = -1, offset_red = -1;
	char colors = 0;
	int verbose = 0;
	double offset_show = -1.0;
	char add_host_header = 1;
	char *proxy_buster = NULL;
	char proxy_is_socks5 = 0;
	char *url = NULL, *complete_url = NULL;
	int n_aggregates = 0;
	aggregate_t *aggregates = NULL;
	char *au_dummy = NULL, *ap_dummy = NULL;
	stats_t t_connect, t_request, t_total, t_resolve, t_write, t_ssl, t_close, stats_to, tcp_rtt_stats, stats_header_size;
	char first_resolve = 1;
	double graph_limit = MY_DOUBLE_INF;
	char nc_graph = 1;
	char adaptive_interval = 0;
	double show_slow_log = MY_DOUBLE_INF;
	char use_tcp_nodelay = 1;
	int max_mtu = -1;
	int write_sleep = 500; /* in us (microseconds), determines resolution of transmit time determination */
	char keep_cookies = 0;
	char abbreviate = 0;
	char *divert_connect = NULL;
	int recv_buffer_size = -1, tx_buffer_size = -1;
	int priority = -1, send_tos = -1;
	char **additional_headers = NULL;
	int n_additional_headers = 0;
#ifndef NO_SSL
	SSL_CTX *client_ctx = NULL;
#endif
	const char *ca_path = NULL;
	struct sockaddr_in6 addr;
	struct addrinfo *ai = NULL, *ai_use = NULL;
	struct addrinfo *ai_proxy = NULL, *ai_use_proxy = NULL;
	char http2 = 0;
	char use_median = 0;
	char ignore_ssl_errors = 0;

	static struct option long_options[] =
	{
		{"aggregate",   1, NULL, 9 },
		{"url",		1, NULL, 'g' },
		{"hostname",	1, NULL, 'h' },
		{"port",	1, NULL, 'p' },
		{"proxy",	1, NULL, 'x' },
		{"count",	1, NULL, 'c' },
		{"persistent-connections",	0, NULL, 'Q' },
		{"interval",	1, NULL, 'i' },
		{"timeout",	1, NULL, 't' },
		{"ipv6",	0, NULL, '6' },
		{"show-statuscodes",	0, NULL, 's' },
		{"split-time",	0, NULL, 'S' },
		{"get-request",	0, NULL, 'G' },
		{"show-transfer-speed",	0, NULL, 'b' },
		{"show-xfer-speed-compressed",	0, NULL, 'B' },
		{"data-limit",	1, NULL, 'L' },
		{"show-kb",	0, NULL, 'X' },
		{"no-cache",	0, NULL, 'Z' },
#ifndef NO_SSL
		{"insecure",	0, NULL, 'k' },
		{"use-ssl",	0, NULL, 'l' },
		{"show-fingerprint",	0, NULL, 'z' },
#endif
		{"flood",	0, NULL, 'f' },
		{"audible-ping",	0, NULL, 'a' },
		{"parseable-output",	0, NULL, 'm' },
		{"ok-result-codes",	1, NULL, 'o' },
		{"result-string",	1, NULL, 'e' },
		{"user-agent",	1, NULL, 'I' },
		{"referer",	1, NULL, 'S' },
		{"resolve-once",0, NULL, 'r' },
		{"nagios-mode-1",	1, NULL, 'n' },
		{"nagios-mode-2",	1, NULL, 'n' },
		{"bind-to",	1, NULL, 'y' },
		{"quiet",	0, NULL, 'q' },
		{"username",	1, NULL, 'U' },
		{"password",	1, NULL, 'P' },
		{"cookie",	1, NULL, 'C' },
		{"colors",	0, NULL, 'Y' },
		{"offset-yellow",	1, NULL, 1   },
		{"threshold-yellow",	1, NULL, 1   },
		{"offset-red",	1, NULL, 2   },
		{"threshold-red",	1, NULL, 2   },
		{"offset-show",	1, NULL, 3   },
		{"show-offset",	1, NULL, 3   },
		{"threshold-show",	1, NULL, 3   },
		{"show-threshold",	1, NULL, 3   },
		{"timestamp",	0, NULL, 4   },
		{"ts",		0, NULL, 4   },
		{"no-host-header",	0, NULL, 5 },
		{"proxy-buster",	1, NULL, 6 },
		{"proxy-user",	1, NULL, 7 },
		{"proxy-password",	1, NULL, 8 },
		{"proxy-password-file",	1, NULL, 10 },
		{"graph-limit",	1, NULL, 11 },
		{"adaptive-interval",	0, NULL, 12 },
		{"ai",	0, NULL, 12 },
		{"slow-log",	0, NULL, 13 },
		{"draw-phase",	0, NULL, 14 },
		{"no-tcp-nodelay",	0, NULL, 15 },
		{"max-mtu", 1, NULL, 16 },
		{"keep-cookies", 0, NULL, 17 },
		{"abbreviate", 0, NULL, 18 },
		{"divert-connect", 1, NULL, 19 },
		{"recv-buffer", 1, NULL, 20 },
		{"tx-buffer", 1, NULL, 21 },
		{"priority", 1, NULL, 23 },
		{"tos", 1, NULL, 24 },
		{"header", 1, NULL, 25 },
		{"ca-path", 1, NULL, 26 },
		{"median", 0, NULL, 27 },
#if HAVE_NCURSES
		{"ncurses",	0, NULL, 'K' },
		{"gui",	0, NULL, 'K' },
#if HAVE_FFTW3
		{"no-graph",	0, NULL, 'D' },
#endif
#endif
		{"http2", 0, NULL, '2' },
		{"version",	0, NULL, 'V' },
		{"help",	0, NULL, 22 },
		{NULL,		0, NULL, 0   }
	};

	bps.Bps_min = 1 << 30;
	bps.Bps_max = -bps.Bps_min;
	bps.Bps_avg = 0;

	setlocale(LC_ALL, "");
	bindtextdomain("httping", LOCALEDIR);
	textdomain("httping");

	determine_terminal_size(&max_y, &max_x);

	signal(SIGPIPE, SIG_IGN);

	while((c = getopt_long(argc, argv, "2DKEA5MvYWT:ZQ6Sy:XL:bBg:h:p:c:i:Gx:t:o:e:falqsmV?I:R:rn:N:zP:U:C:Fk", long_options, NULL)) != -1)
	{
		switch(c)
		{
			case '2':
				http2 = 1;
				break;

			case 26:
				ca_path = optarg;
				break;

			case 27:
				use_median = 1;
				break;

			case 25:
				add_header(&additional_headers, &n_additional_headers, optarg);
				break;

			case 24:
				send_tos = atoi(optarg);
				break;

			case 23:
#ifdef linux
				priority = atoi(optarg);
#else
				error_exit("Setting the network priority is only supported on Linux.\n");
#endif
				break;

			case 21:
				tx_buffer_size = atoi(optarg);
				break;

			case 20:
				recv_buffer_size = atoi(optarg);
				break;

			case 19:
				divert_connect = optarg;
				break;

			case 18:
				abbreviate = 1;
				break;

			case 17:
				keep_cookies = 1;
				break;

			case 16:
				max_mtu = atoi(optarg);
				break;

			case 15:
				use_tcp_nodelay = 0;
				break;

#if HAVE_NCURSES
			case 14:
				draw_phase = 1;
				break;
#endif

			case 13:
				show_slow_log = atof(optarg);
				break;

			case 12:
				adaptive_interval = 1;
				break;

			case 11:
				graph_limit = atof(optarg);
				break;

#if HAVE_NCURSES
			case 'K':
				ncurses_mode = 1;
				adaptive_interval = 1;
				if (!wait_set)
					wait = 0.5;
				break;
#if HAVE_FFTW3
			case 'D':
				nc_graph = 0;
				break;
#endif
#endif

			case 'E':
				do_fetch_proxy_settings = 1;
				break;

			case 'A':
				fprintf(stderr, gettext("\n *** -A is no longer required ***\n\n"));
				break;

			case 'M':
				json_output = 1;
				break;

			case 'v':
				verbose++;
				break;

			case 1:
				offset_yellow = atof(optarg);
				break;

			case 2:
				offset_red = atof(optarg);
				break;

			case 3:
				offset_show = atof(optarg);
				break;

			case 4:
				show_ts = 1;
				break;

			case 5:
				add_host_header = 0;
				break;

			case 6:
				proxy_buster = optarg;
				break;

			case '5':
				proxy_is_socks5 = 1;
				break;

			case 7:
				proxy_user = optarg;
				break;

			case 8:
				proxy_password = optarg;
				break;

			case 9:
				set_aggregate(optarg, &n_aggregates, &aggregates);
				break;

			case 10:
				proxy_password = read_file(optarg);
				break;

			case 'Y':
				colors = 1;
				break;

			case 'W':
				abort_on_resolve_failure = 0;
				break;

			case 'T':
				auth_password = read_file(optarg);
				break;

			case 'Z':
				no_cache = 1;
				break;

			case '6':
				use_ipv6 = 1;
				break;

			case 'S':
				split = 1;
				break;

			case 'Q':
				persistent_connections = 1;
				break;

			case 'y':
				parse_bind_to(optarg, &bind_to_4, &bind_to_6, &bind_to);
				break;

			case 'z':
				show_fp = 1;
				break;

			case 'X':
				show_bytes_xfer = 1;
				break;

			case 'L':
				Bps_limit = atoi(optarg);
				break;

			case 'B':
				show_Bps = 1;
				ask_compression = 1;
				break;

			case 'b':
				show_Bps = 1;
				break;

			case 'e':
				err_str = optarg;
				break;

			case 'o':
				ok_str = optarg;
				break;

			case 'x':
				proxy_to_host_and_port(optarg, &proxy_host, &proxy_port);
				break;

			case 'g':
				url = optarg;
				break;

			case 'r':
				resolve_once = 1;
				break;

			case 'h':
				free(url);
				url = NULL;
				str_add(&url, "http://%s/", optarg);
				break;

			case 'p':
				portnr = atoi(optarg);
				break;

			case 'c':
				count = atoi(optarg);
				break;

			case 'i':
				wait = atof(optarg);
				if (wait < 0.0)
					error_exit(gettext("-i cannot have a value smaller than zero"));
				wait_set = 1;
				break;

			case 't':
				timeout = atof(optarg);
				break;

			case 'I':
				useragent = optarg;
				break;

			case 'R':
				referer = optarg;
				break;

			case 'a':
				audible = 1;
				break;

			case 'f':
				wait = 0;
				wait_set = 1;
				adaptive_interval = 0;
				break;

			case 'G':
				get_instead_of_head = 1;
				break;

#ifndef NO_SSL
			case 'l':
				use_ssl = 1;
				break;
#endif

			case 'm':
				machine_readable = 1;
				break;

			case 'q':
				quiet = 1;
				break;

			case 's':
				show_statuscodes = 1;
				break;

			case 'k':
				ignore_ssl_errors = 1;
				break;

			case 'V':
				version();
				return 0;

			case 'n':
				if (nagios_mode)
					error_exit(gettext("-n and -N are mutual exclusive\n"));
				else
					nagios_mode = 1;

				parse_nagios_settings(optarg, &nagios_warn, &nagios_crit);
				break;

			case 'N':
				if (nagios_mode) error_exit(gettext("-n and -N are mutual exclusive\n"));
				nagios_mode = 2;
				nagios_exit_code = atoi(optarg);
				break;

			case 'P':
				auth_password = optarg;
				break;

			case 'U':
				auth_usr = optarg;
				break;

			case 'C':
				add_cookie(&static_cookies, &n_static_cookies, optarg);
				break;

			case 'F':
				use_tfo = 1;
				break;

			case 22:
				version();

				usage(argv[0]);

				return 0;

			case '?':
			default:
				fprintf(stderr, "\n");
				version();

				fprintf(stderr, gettext("\n\nPlease run:\n\t%s --help\nto see a list of options.\n\n"), argv[0]);

				return 1;
		}
	}

	init_statst(&t_resolve, use_median);
	init_statst(&t_connect, use_median);
	init_statst(&t_write, use_median);
	init_statst(&t_request, use_median);
	init_statst(&t_total, use_median);
	init_statst(&t_ssl, use_median);
	init_statst(&t_close, use_median);

	init_statst(&stats_to, use_median);
#if defined(linux) || defined(__FreeBSD__)
	init_statst(&tcp_rtt_stats, use_median);
#endif
	init_statst(&stats_header_size, use_median);

	if (do_fetch_proxy_settings)
		fetch_proxy_settings(&proxy_user, &proxy_password, &proxy_host, &proxy_port, use_ssl, use_ipv6);

	if (optind < argc)
		url = argv[optind];

	if (!url)
	{
		fprintf(stderr, gettext("No URL/host to ping given\n\n"));
		return 1;
	}

	if (machine_readable + json_output + ncurses_mode > 1)
		error_exit(gettext("Cannot combine -m, -M and -K"));

	if ((machine_readable || json_output) && n_aggregates > 0)
		error_exit(gettext("Aggregates can only be used in non-machine/json-output mode"));

	clear_error();

	if (!(get_instead_of_head || use_ssl) && show_Bps)
		error_exit(gettext("-b/-B can only be used when also using -G (GET instead of HEAD) or -l (use SSL)\n"));

	if (colors)
		set_colors(ncurses_mode);

	if (!machine_readable && !json_output)
		printf("%s%s", c_normal, c_white);

	interpret_url(url, &get, &hostname, &portnr, use_ipv6, use_ssl, &complete_url, &au_dummy, &ap_dummy);
	if (!auth_usr)
		auth_usr = au_dummy;
	if (!auth_password)
		auth_password = ap_dummy;

#if HAVE_NCURSES
	if (ncurses_mode)
	{
		if (wait == 0.0)
			wait = 0.001;

		init_ncurses_ui(graph_limit, 1.0 / wait, colors);
	}
#endif

	if (strncmp(complete_url, "https://", 8) == 0 && !use_ssl)
	{
		use_ssl = 1;
#if HAVE_NCURSES
		if (ncurses_mode)
		{
			slow_log(gettext("\nAuto enabling SSL due to https-URL\n"));
			update_terminal();
		}
		else
#endif
		{
			fprintf(stderr, gettext("Auto enabling SSL due to https-URL\n"));
		}
	}

	if (use_tfo && use_ssl)
		error_exit(gettext("TCP Fast open and SSL not supported together\n"));

	if (verbose)
	{
#if HAVE_NCURSES
		if (ncurses_mode)
		{
			slow_log(gettext("\nConnecting to host %s, port %d and requesting file %s"), hostname, portnr, get);

			if (proxy_host)
				slow_log(gettext("\nUsing proxyserver: %s:%d"), proxy_host, proxy_port);
		}
		else
#endif
		{
			printf(gettext("Connecting to host %s, port %d and requesting file %s\n\n"), hostname, portnr, get);

			if (proxy_host)
				fprintf(stderr, gettext("Using proxyserver: %s:%d\n"), proxy_host, proxy_port);
		}
	}

#ifndef NO_SSL
	if (use_ssl)
	{
		client_ctx = initialize_ctx(ask_compression, ca_path);
		if (!client_ctx)
		{
			set_error(gettext("problem creating SSL context"));
			goto error_exit;
		}
	}
#endif

	if (!quiet && !machine_readable && !nagios_mode && !json_output)
	{
#if HAVE_NCURSES
		if (ncurses_mode)
			slow_log("\nPING %s:%d (%s):", hostname, portnr, get);
		else
#endif
			printf("PING %s%s:%s%d%s (%s):\n", c_green, hostname, c_bright, portnr, c_normal, get);
	}

	if (json_output)
		printf("[\n");

	if (adaptive_interval && wait <= 0.0)
		error_exit(gettext("Interval must be > 0 when using adaptive interval"));

	signal(SIGINT, handler);
	signal(SIGTERM, handler);

	signal(SIGQUIT, handler_quit);

	timeout *= 1000.0;	/* change to ms */

	/*
	   if (follow_30x)
	   {
	   get headers

	   const char *get_location(const char *host, int port, char use_ssl, char *reply)

	   set new host/port/path/etc
	   }
	 */

	started_at = get_ts();
	if (proxy_host)
	{
#if HAVE_NCURSES
		if (ncurses_mode)
		{
			slow_log(gettext("\nResolving hostname %s"), proxy_host);
			update_terminal();
		}
#endif

		if (resolve_host(proxy_host, &ai_proxy, use_ipv6, proxy_port) == -1)
			error_exit(get_error());

		ai_use_proxy = select_resolved_host(ai_proxy, use_ipv6);
		if (!ai_use_proxy)
			error_exit(gettext("No valid IPv4 or IPv6 address found for %s"), proxy_host);
	}
	else if (resolve_once)
	{
		char *res_host = divert_connect ? divert_connect : hostname;
#if HAVE_NCURSES
		if (ncurses_mode)
		{
			slow_log(gettext("\nResolving hostname %s"), res_host);
			update_terminal();
		}
#endif

		if (resolve_host(res_host, &ai, use_ipv6, portnr) == -1)
		{
			err++;
			emit_error(verbose, -1, started_at);
			have_resolved = 0;
			if (abort_on_resolve_failure)
				error_exit(get_error());
		}

		ai_use = select_resolved_host(ai, use_ipv6);
		if (!ai_use)
		{
			set_error(gettext("No valid IPv4 or IPv6 address found for %s"), res_host);

			if (abort_on_resolve_failure)
				error_exit(get_error());

			/* do not emit the resolve-error here: as 'have_resolved' is set to 0
			   next, the program will try to resolve again anyway
			   this prevents a double error-message while err is increased only
			   once
			 */
			have_resolved = 0;
		}

		if (have_resolved)
			get_addr(ai_use, &addr);
	}

	if (persistent_connections)
		fd = -1;

	while((curncount < count || count == -1) && stop == 0)
	{
		double dstart = -1.0, dend = -1.0, dafter_connect = 0.0, dafter_resolve = 0.0, dafter_write_complete = 0.0;
		char *reply = NULL;
		double Bps = 0;
		char is_compressed = 0;
		long long int bytes_transferred = 0;
		time_t their_ts = 0;
		int age = -1;
		char *sc = NULL, *scdummy = NULL;
		char *fp = NULL;
		int re_tx = 0, pmtu = 0, recv_tos = 0;
		socklen_t recv_tos_len = sizeof recv_tos;

		dstart = get_ts();

		for(;;)
		{
			char did_reconnect = 0;
			int rc = -1;
			int persistent_tries = 0;
			int len = 0, overflow = 0, headers_len = 0;
			char req_sent = 0;
			double dummy_ms = 0.0;
			double their_est_ts = -1.0, toff_diff_ts = -1.0;
			char tfo_success = 0;
			double ssl_handshake = 0.0;
			char cur_have_resolved = 0;
#if defined(linux) || defined(__FreeBSD__)
			struct tcp_info info;
			socklen_t info_len = sizeof(struct tcp_info);
#endif

			curncount++;

persistent_loop:
			if ((!resolve_once || (resolve_once == 1 && have_resolved == 0)) && fd == -1 && proxy_host == NULL)
			{
				char *res_host = divert_connect ? divert_connect : hostname;

				memset(&addr, 0x00, sizeof addr);

#if HAVE_NCURSES
				if (ncurses_mode && first_resolve)
				{
					slow_log(gettext("\nResolving hostname %s"), res_host);
					update_terminal();
					first_resolve = 0;
				}
#endif

				if (ai)
				{
					freeaddrinfo(ai);

					ai_use = ai = NULL;
				}

				if (resolve_host(res_host, &ai, use_ipv6, portnr) == -1)
				{
					err++;
					emit_error(verbose, curncount, dstart);

					if (abort_on_resolve_failure)
						error_exit(get_error());
					break;
				}

				ai_use = select_resolved_host(ai, use_ipv6);
				if (!ai_use)
				{
					set_error(gettext("No valid IPv4 or IPv6 address found for %s"), res_host);
					emit_error(verbose, curncount, dstart);
					err++;

					if (abort_on_resolve_failure)
						error_exit(get_error());

					break;
				}

				get_addr(ai_use, &addr);

				cur_have_resolved = have_resolved = 1;
			}

			if (cur_have_resolved)
			{
				dafter_resolve = get_ts();
				dummy_ms = (dafter_resolve - dstart) * 1000.0;
				update_statst(&t_resolve, dummy_ms);
			}

			free(request);
			request = create_http_request_header(proxy_host ? complete_url : get, proxy_host ? 1 : 0, get_instead_of_head, persistent_connections, add_host_header ? hostname : NULL, useragent, referer, ask_compression, no_cache, auth_usr, auth_password, static_cookies, n_static_cookies, dynamic_cookies, keep_cookies ? n_dynamic_cookies : 0, proxy_buster, proxy_user, proxy_password, additional_headers, n_additional_headers, http2, use_ssl);
			req_len = strlen(request);

			if (req_len >= 4096)
			{
				char *line = NULL;
				static int notify_cnt = 0;

				notify_cnt++;

				if (notify_cnt == MAX_SHOW_SUPPRESSION + 1)
					str_add(&line, gettext("Will no longer inform about request headers too large."));
				else if (notify_cnt <= MAX_SHOW_SUPPRESSION)
					str_add(&line, gettext("Request headers > 4KB! (%d bytes) This may give failures with some HTTP servers."), req_len);

				if (line)
				{
#if HAVE_NCURSES
					if (ncurses_mode)
						slow_log("\n%s", line);
					else
#endif
						printf("%s\n", line);
				}

				free(line);
			}

			if ((persistent_connections && fd < 0) || !persistent_connections)
			{
				int rc = -1;
				struct addrinfo *ai_dummy = proxy_host ? ai_use_proxy : ai_use;

				fd = create_socket((struct sockaddr *)bind_to, ai_dummy, recv_buffer_size, tx_buffer_size, max_mtu, use_tcp_nodelay, priority, send_tos);
				if (fd < 0)
					rc = fd; /* FIXME need to fix this, this is ugly */
				else if (proxy_host)
				{
					if (proxy_is_socks5)
						rc = socks5connect(fd, ai_dummy, timeout, proxy_user, proxy_password, hostname, portnr, abort_on_resolve_failure);
#ifndef NO_SSL
					else if (use_ssl)
						rc = connect_ssl_proxy(fd, ai_dummy, timeout, proxy_user, proxy_password, hostname, portnr, &use_tfo);
#endif
					else
						rc = connect_to(fd, ai_dummy, timeout, &use_tfo, request, req_len, &req_sent);
				}
				else
				{

					rc = connect_to(fd, ai_dummy, timeout, &use_tfo, request, req_len, &req_sent);
				}

				if (rc < 0)
				{
					failure_close(fd);
					fd = rc; /* FIXME need to fix this */
				}

				did_reconnect = 1;
			}

			if (fd == RC_CTRLC)	/* ^C pressed */
				break;

			if (fd < 0)
			{
				fd = -1;
			}

			if (fd >= 0)
			{
				/* set fd blocking */
				if (set_fd_blocking(fd) == -1) /* FIXME redundant? already in connect_to etc? */
				{
					stats_close(&fd, &t_close, 1);
					break;
				}

#ifndef NO_SSL
				if (use_ssl && ssl_h == NULL)
				{
					int rc = connect_ssl(fd, client_ctx, &ssl_h, &s_bio, timeout, &ssl_handshake, hostname, ignore_ssl_errors);
					if (rc == 0)
						update_statst(&t_ssl, ssl_handshake);
					else
					{
						stats_close(&fd, &t_close, 1);
						fd = rc;

						if (persistent_connections && ++persistent_tries < 2)
						{
							persistent_did_reconnect = 1;

							goto persistent_loop;
						}
					}
				}
#endif
			}

			dafter_connect = get_ts();

			if (did_reconnect)
			{
				if (cur_have_resolved)
					dummy_ms = (dafter_connect - dafter_resolve) * 1000.0;
				else
					dummy_ms = (dafter_connect - dstart) * 1000.0;

				update_statst(&t_connect, dummy_ms - ssl_handshake);
			}

			if (fd < 0)
			{
				if (fd == RC_TIMEOUT)
					set_error(gettext("timeout connecting to host"));

				emit_error(verbose, curncount, dstart);
				err++;

				fd = -1;

				break;
			}

#ifndef NO_SSL
			if (use_ssl)
				rc = WRITE_SSL(ssl_h, request, req_len, timeout);
			else
#endif
			{
				if (!req_sent)
					rc = mywrite(fd, request, req_len, timeout);
				else
					rc = req_len;
			}

			/* wait for data transmit(!) to complete,
			   e.g. until the transmitbuffers are empty and the data was
			   sent to the next hop
			 */
#ifdef linux
			for(;;)
			{
				int bytes_left = 0;
				int i_rc = ioctl(fd, TIOCOUTQ, &bytes_left);

				if (i_rc == -1 || bytes_left == 0 || stop)
					break;

				dafter_write_complete = get_ts();
				if ((dafter_write_complete - dafter_connect) * 1000.0 >= timeout)
					break;

				/* this keeps it somewhat from becoming a busy loop
				 * I know of no other way to wait for the kernel to
				 * finish the transmission
				 */
				myusleep(write_sleep);
			}
#endif

			dafter_write_complete = get_ts();

			dummy_ms = (dafter_write_complete - dafter_connect) * 1000.0;
			update_statst(&t_write, dummy_ms);

			if (rc != req_len)
			{
				if (persistent_connections)
				{
					if (++persistent_tries < 2)
					{
						stats_close(&fd, &t_close, 0);
						persistent_did_reconnect = 1;
						goto persistent_loop;
					}
				}

				if (rc == -1)
					set_error(gettext("error sending request to host"));
				else if (rc == RC_TIMEOUT)
					set_error(gettext("timeout sending to host"));
				else if (rc == RC_INVAL)
					set_error(gettext("retrieved invalid data from host"));
				else if (rc == RC_CTRLC)
				{/* ^C */}
				else if (rc == 0)
					set_error(gettext("connection prematurely closed by peer"));

				emit_error(verbose, curncount, dstart);

				stats_close(&fd, &t_close, 1);
				err++;

				break;
			}

#if defined(linux) || defined(__FreeBSD__)
#if HAVE_NCURSES
			if (!use_ssl && ncurses_mode)
			{
				struct timeval tv;
				int t_rc = -1;

				fd_set rfds;
				FD_ZERO(&rfds);
				FD_SET(fd, &rfds);

				tv.tv_sec = (long)(timeout / 1000.0) % 1000000;
				tv.tv_usec = (long)(timeout * 1000.0) % 1000000;

				t_rc = select(fd + 1, &rfds, NULL, NULL, &tv);

#ifdef linux
				if (t_rc == 1 && \
						FD_ISSET(fd, &rfds) && \
						getsockopt(fd, IPPROTO_TCP, TCP_INFO, &info, &info_len) == 0 && \
						info.tcpi_unacked > 0)
				{
					static int in_transit_cnt = 0;

					in_transit_cnt++;
					if (in_transit_cnt == MAX_SHOW_SUPPRESSION + 1)
						slow_log(gettext("\nNo longer emitting message about \"still data in transit\""));
					else if (in_transit_cnt <= MAX_SHOW_SUPPRESSION)
						slow_log(gettext("\nHTTP server started sending data with %d bytes still in transit"), info.tcpi_unacked);
				}
#endif

				if (t_rc == 0)
				{
					stats_close(&fd, &t_close, 1);

					rc = RC_TIMEOUT;
					set_error(gettext("timeout sending to host"));

					emit_error(verbose, curncount, dstart);

					err++;

					break;
				}
			}
#endif

			if (getsockopt(fd, IPPROTO_IP, IP_TOS, &recv_tos, &recv_tos_len) == -1)
			{
				set_error(gettext("failed to obtain TOS info"));
				recv_tos = -1;
			}
#endif

			rc = get_HTTP_headers(fd, ssl_h, &reply, &overflow, timeout);

#if HAVE_NCURSES
			if (ncurses_mode && !get_instead_of_head && overflow > 0)
			{
				static int more_data_cnt = 0;

				more_data_cnt++;
				if (more_data_cnt == MAX_SHOW_SUPPRESSION + 1)
					slow_log(gettext("\nNo longer emitting message about \"more data than response headers\""));
				else if (more_data_cnt <= MAX_SHOW_SUPPRESSION)
					slow_log(gettext("\nHTTP server sent more data than just the response headers"));
			}
#endif

			emit_headers(reply);

			if (reply)
			{
				free_cookies(dynamic_cookies, n_dynamic_cookies);
				dynamic_cookies = NULL;
				n_dynamic_cookies = 0;

				get_cookies(reply, &dynamic_cookies, &n_dynamic_cookies, &static_cookies, &n_static_cookies);
			}

			if ((show_statuscodes || machine_readable || json_output || ncurses_mode) && reply != NULL)
			{
				/* statuscode is in first line behind
				 * 'HTTP/1.x'
				 */
				char *dummy = strchr(reply, ' ');

				if (dummy)
				{
					sc = strdup(dummy + 1);

					/* lines are normally terminated with a
					 * CR/LF
					 */
					dummy = strchr(sc, '\r');
					if (dummy)
						*dummy = 0x00;
					dummy = strchr(sc, '\n');
					if (dummy)
						*dummy = 0x00;
				}
			}

			their_ts = parse_date_from_response_headers(reply);

			age = calc_page_age(reply, their_ts);

			is_compressed = check_compressed(reply);

			if (persistent_connections && show_bytes_xfer && reply != NULL)
			{
				char *length = strstr(reply, "\nContent-Length:");
				if (!length)
				{
					set_error(gettext("'Content-Length'-header missing!"));
					emit_error(verbose, curncount, dstart);
					stats_close(&fd, &t_close, 1);
					break;
				}

				len = atoi(&length[17]);
			}

			if (http2 && get_instead_of_head) {
				printf("%s\n", reply);
				printf("%s\n", request);
				if (strncmp(reply, "HTTP/1.1 101", 12) != 0)
					printf("Server did not want to switch protocols\n");
				else if (strcasestr(reply, "Connection: Upgrade") == NULL)
					printf("Server did not want to upgrade connection\n");
				else if (strcasestr(reply, "Upgrade: h2c") == NULL)
					printf("Server did not want to upgrade connection to HTTP/2\n");
			}

			headers_len = 0;
			if (reply)
			{
				headers_len = strlen(reply) + 4 - overflow;
				free(reply);
				reply = NULL;
			}

			update_statst(&stats_header_size, headers_len);

			if (rc < 0)
			{
				if (persistent_connections)
				{
					if (++persistent_tries < 2)
					{
						stats_close(&fd, &t_close, 0);
						persistent_did_reconnect = 1;
						goto persistent_loop;
					}
				}

				if (rc == RC_SHORTREAD)
					set_error(gettext("short read during receiving reply-headers from host"));
				else if (rc == RC_TIMEOUT)
					set_error(gettext("timeout while receiving reply-headers from host"));

				emit_error(verbose, curncount, dstart);

				stats_close(&fd, &t_close, 1);
				err++;

				break;
			}

			ok++;

			if (get_instead_of_head && show_Bps)
			{
				int buffer_size = RECV_BUFFER_SIZE;
				char *buffer = (char *)malloc(buffer_size);
				double dl_start = get_ts(), dl_end;
				double cur_limit = Bps_limit;

				if (persistent_connections)
				{
					if (cur_limit == -1 || len < cur_limit)
						cur_limit = len - overflow;
				}

				for(;;)
				{
					int n = cur_limit != -1 ? min(cur_limit - bytes_transferred, buffer_size) : buffer_size;
					int rc = read(fd, buffer, n);

					if (rc == -1)
					{
						if (errno != EINTR && errno != EAGAIN)
							error_exit(gettext("read of response body dataa failed"));
					}
					else if (rc == 0)
					{
						break;
					}

					bytes_transferred += rc;

					if (cur_limit != -1 && bytes_transferred >= cur_limit)
						break;
				}

				free(buffer);

				dl_end = get_ts();

				Bps = (double)bytes_transferred / max(dl_end - dl_start, 0.000001);
				bps.Bps_min = min(bps.Bps_min, Bps);
				bps.Bps_max = max(bps.Bps_max, Bps);
				bps.Bps_avg += Bps;
			}

			dend = get_ts();

#ifndef NO_SSL
			if (use_ssl)
			{
				if ((show_fp || json_output || ncurses_mode) && ssl_h != NULL)
					fp = get_fingerprint(ssl_h);

				if (!persistent_connections)
				{
					if (close_ssl_connection(ssl_h) == -1)
					{
						set_error(gettext("error shutting down ssl"));
						emit_error(verbose, curncount, dstart);
					}

					SSL_free(ssl_h);
					ssl_h = NULL;
					s_bio = NULL;
				}
			}
#endif

#if defined(linux) || defined(__FreeBSD__)
			if (getsockopt(fd, IPPROTO_TCP, TCP_INFO, &info, &info_len) == 0)
			{
#ifdef TCPI_OPT_SYN_DATA
				if (info.tcpi_options & TCPI_OPT_SYN_DATA)
					tfo_success = 1;
#endif

				update_statst(&tcp_rtt_stats, (double)info.tcpi_rtt / 1000.0);

#ifdef linux
				re_tx = info.tcpi_retransmits;
				pmtu = info.tcpi_pmtu;
#endif
			}
#endif

			if (!persistent_connections)
				stats_close(&fd, &t_close, 0);

			dummy_ms = (dend - dafter_write_complete) * 1000.0;
			update_statst(&t_request, dummy_ms);

			dummy_ms = (dend - dstart) * 1000.0;
			update_statst(&t_total, dummy_ms);

			their_est_ts = (dend + dafter_connect) / 2.0; /* estimate of when other end started replying */
			toff_diff_ts = ((double)their_ts - their_est_ts) * 1000.0;
			update_statst(&stats_to, toff_diff_ts);

			if (json_output)
			{
				char current_host[4096] = { 0 };

				if (proxy_host)
					snprintf(current_host, sizeof current_host, "%s", hostname);
				else if (getnameinfo((const struct sockaddr *)&addr, sizeof addr, current_host, sizeof current_host, NULL, 0, NI_NUMERICHOST) == -1)
					snprintf(current_host, sizeof current_host, gettext("getnameinfo() failed: %d (%s)"), errno, strerror(errno));

				emit_json(1, curncount, dstart, &t_resolve, &t_connect, &t_request, atoi(sc ? sc : "-1"), sc ? sc : "?", headers_len, len, Bps, current_host, fp, toff_diff_ts, tfo_success, &t_ssl, &t_write, &t_close, n_dynamic_cookies, &stats_to, &tcp_rtt_stats, re_tx, pmtu, recv_tos, &t_total);
			}
			else if (machine_readable)
			{
				if (sc)
				{
					char *dummy = strchr(sc, ' ');
					if (dummy)
						*dummy = 0x00;

					if (strstr(ok_str, sc))
						printf("%f", t_total.cur);
					else
						printf("%s", err_str);

					if (show_statuscodes)
						printf(" %s", sc);
				}
				else
				{
					printf("%s", err_str);
				}

				if(audible)
					putchar('\a');

				printf("\n");
			}
			else if (!quiet && !nagios_mode && t_total.cur >= offset_show)
			{
				char *tot_str = NULL;
				const char *i6_bs = "", *i6_be = "";
				char *line = NULL;
				const char *ms_color = c_green;
				char current_host[4096] = { 0 };
				char *operation = !persistent_connections ? gettext("connected to") : gettext("pinged host");
				const char *sep = c_bright, *unsep = c_normal;

				if (show_ts || ncurses_mode)
				{
					char *ts = get_ts_str(verbose);

					if (ncurses_mode)
						str_add(&line, "[%s] ", ts);
					else
						str_add(&line, "%s ", ts);

					free(ts);
				}

				if (curncount & 1)
				{
					str_add(&line, "%s", c_bright);

					sep = c_normal;
					unsep = c_bright;
				}

				if (proxy_host)
					snprintf(current_host, sizeof current_host, "%s", hostname);
				else if (getnameinfo((const struct sockaddr *)&addr, sizeof addr, current_host, sizeof current_host, NULL, 0, NI_NUMERICHOST) == -1)
					snprintf(current_host, sizeof current_host, gettext("getnameinfo() failed: %d (%s)"), errno, strerror(errno));

				if (addr.sin6_family == AF_INET6)
				{
					i6_bs = "[";
					i6_be = "]";
				}

				if (offset_red > 0.0 && t_total.cur >= offset_red)
					ms_color = c_red;
				else if (offset_yellow > 0.0 && t_total.cur >= offset_yellow)
					ms_color = c_yellow;

				if (!ncurses_mode)
					str_add(&line, "%s%s ", c_white, operation);

				if (persistent_connections && show_bytes_xfer)
					str_add(&line, gettext("%s%s%s%s%s:%s%d%s (%d/%d bytes), seq=%s%d%s "), c_red, i6_bs, current_host, i6_be, c_white, c_yellow, portnr, c_white, headers_len, len, c_blue, curncount-1, c_white);
				else
					str_add(&line, gettext("%s%s%s%s%s:%s%d%s (%d bytes), seq=%s%d%s "), c_red, i6_bs, current_host, i6_be, c_white, c_yellow, portnr, c_white, headers_len, c_blue, curncount-1, c_white);

				tot_str = format_value(t_total.cur, 6, 2, abbreviate);

				if (split)
				{
					char *res_str = t_resolve.cur_valid ? format_value(t_resolve.cur, 6, 2, abbreviate) : strdup(gettext("   n/a"));
					char *con_str = t_connect.cur_valid ? format_value(t_connect.cur, 6, 2, abbreviate) : strdup(gettext("   n/a"));
					char *wri_str = format_value(t_write.cur, 6, 2, abbreviate);
					char *req_str = format_value(t_request.cur, 6, 2, abbreviate);
					char *clo_str = format_value(t_close.cur, 6, 2, abbreviate);

					str_add(&line, gettext("time=%s+%s+%s+%s+%s%s=%s%s%s%s ms %s%s%s"), res_str, con_str, wri_str, req_str, clo_str, sep, unsep, ms_color, tot_str, c_white, c_cyan, sc?sc:"", c_white);

					free(clo_str);
					free(req_str);
					free(wri_str);
					free(con_str);
					free(res_str);
				}
				else
				{
					str_add(&line, gettext("time=%s%s%s ms %s%s%s"), ms_color, tot_str, c_white, c_cyan, sc?sc:"", c_white);
				}

				free(tot_str);

				if (persistent_did_reconnect)
				{
					str_add(&line, " %sC%s", c_magenta, c_white);
					persistent_did_reconnect = 0;
				}

				if (show_Bps)
				{
					str_add(&line, " %dKB/s", (int)Bps / 1024);
					if (show_bytes_xfer)
						str_add(&line, " %dKB", (int)(bytes_transferred / 1024));
					if (ask_compression)
					{
						str_add(&line, " (");
						if (!is_compressed)
							str_add(&line, gettext("not "));
						str_add(&line, gettext("compressed)"));
					}
				}

				if (use_ssl && show_fp && fp != NULL)
				{
					str_add(&line, " %s", fp);
				}

				if (verbose > 0 && their_ts > 0)
				{
					/*  if diff_ts > 0, then their clock is running too fast */
					str_add(&line, gettext(" toff=%d"), (int)toff_diff_ts);
				}

				if (verbose > 0 && age > 0)
					str_add(&line, gettext(" age=%d"), age);

				str_add(&line, "%s", c_normal);

				if (audible)
				{
#if HAVE_NCURSES
					my_beep();
#else
					putchar('\a');
#endif
				}

				if (tfo_success)
					str_add(&line, " F");

#if HAVE_NCURSES
				if (ncurses_mode)
				{
					if (dummy_ms >= show_slow_log)
						slow_log("\n%s", line);
					else
						fast_log("\n%s", line);
				}
				else
#endif
				{
					printf("%s\n", line);
				}

				do_aggregates(t_total.cur, (int)(get_ts() - started_at), n_aggregates, aggregates, verbose, show_ts);

				free(line);
			}

			if (show_statuscodes && ok_str != NULL && sc != NULL)
			{
				scdummy = strchr(sc, ' ');
				if (scdummy) *scdummy = 0x00;

				if (strstr(ok_str, sc) == NULL)
				{
					ok--;
					err++;
				}
			}

			if (got_sigquit && !quiet && !machine_readable && !nagios_mode && !json_output)
			{
				got_sigquit = 0;
				stats_line(0, complete_url, count, curncount, err, ok, started_at, verbose, &t_total, avg_httping_time, show_Bps ? &bps : NULL);
			}

			break;
		}

		emit_statuslines(get_ts() - started_at);
#if HAVE_NCURSES
		if (ncurses_mode)
			update_stats(&t_resolve, &t_connect, &t_request, &t_total, &t_ssl, curncount, err, sc, fp, use_tfo, nc_graph, &stats_to, &tcp_rtt_stats, re_tx, pmtu, recv_tos, &t_close, &t_write, n_dynamic_cookies, abbreviate, &stats_header_size);
#endif

		free(sc);
		free(fp);

#if HAVE_NCURSES
		if (ncurses_mode)
			update_terminal();
		else
#endif
			fflush(NULL);

		if (!stop && wait > 0)
		{
			double cur_sleep = wait;

			if (adaptive_interval)
			{
				double now = get_ts();
				double interval_left = fmod(now - started_at, wait);

				if (interval_left <= 0.0)
					cur_sleep = wait;
				else
					cur_sleep = wait - interval_left;
			}

			myusleep((useconds_t)(cur_sleep * 1000000.0));
		}

		reset_statst_cur(&t_resolve);
		reset_statst_cur(&t_connect);
		reset_statst_cur(&t_ssl);
		reset_statst_cur(&t_write);
		reset_statst_cur(&t_request);
		reset_statst_cur(&t_close);
		reset_statst_cur(&t_total);
		reset_statst_cur(&stats_to);
#if defined(linux) || defined(__FreeBSD__)
		reset_statst_cur(&tcp_rtt_stats);
#endif
	}

#if HAVE_NCURSES
	if (ncurses_mode)
		end_ncurses();
#endif

	if (colors)
		set_colors(0);

	if (ok)
		avg_httping_time = t_total.avg / (double)t_total.n;
	else
		avg_httping_time = -1.0;

	if (!quiet && !machine_readable && !nagios_mode && !json_output)
		stats_line(1, complete_url, count, curncount, err, ok, started_at, verbose, &t_total, avg_httping_time, show_Bps ? &bps : NULL);

error_exit:
	if (nagios_mode)
		return nagios_result(ok, nagios_mode, nagios_exit_code, avg_httping_time, nagios_warn, nagios_crit);

	if (!json_output && !machine_readable)
		printf("%s", c_very_normal);

	if (json_output)
		printf("\n]\n");

	free_cookies(static_cookies, n_static_cookies);
	free_cookies(dynamic_cookies, n_dynamic_cookies);
	freeaddrinfo(ai);
	free(request);
	free(get);
	free(hostname);
	free(complete_url);

	free_headers(additional_headers, n_additional_headers);

	free(aggregates);

#ifndef NO_SSL
	if (use_ssl)
	{
		SSL_CTX_free(client_ctx);

		shutdown_ssl();
	}
#endif

	fflush(NULL);

	uninit_statst(&t_resolve);
	uninit_statst(&t_connect);
	uninit_statst(&t_write);
	uninit_statst(&t_request);
	uninit_statst(&t_total);
	uninit_statst(&t_ssl);
	uninit_statst(&t_close);
	uninit_statst(&stats_to);
#if defined(linux) || defined(__FreeBSD__)
	uninit_statst(&tcp_rtt_stats);
#endif
	uninit_statst(&stats_header_size);

	if (ok)
		return 0;

	return 127;
}
