# - Document magic Expect Header values
# - Internal fuzz support?
#
-# Copyright (c) 2007-2016 Fabian Keil <fk@fabiankeil.de>
+# Copyright (c) 2007-2021 Fabian Keil <fk@fabiankeil.de>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
use Getopt::Long;
use constant {
- PRT_VERSION => 'Privoxy-Regression-Test 0.7.1',
+ PRT_VERSION => 'Privoxy-Regression-Test 0.7.3',
CURL => 'curl',
CLI_LOOPS => 1,
CLI_MAX_TIME => 5,
CLI_MIN_LEVEL => 0,
- # XXX: why limit at all?
+ # The reason for a maximum test level is explained in the
+ # perldoc section TEST LEVELS near the end of this file.
CLI_MAX_LEVEL => 100,
CLI_FORKS => 0,
CLI_SLEEP_TIME => 0,
+ PRIVOXY_ADDRESS => 'http://127.0.0.1:8118/',
PRIVOXY_CGI_URL => 'http://p.p/',
FELLATIO_URL => 'http://127.0.0.1:8080/',
LEADING_LOG_DATE => 1,
REDIRECT_TEST =>108,
};
-sub init_our_variables () {
+sub init_our_variables() {
our $leading_log_time = LEADING_LOG_TIME;
our $leading_log_date = LEADING_LOG_DATE;
our $privoxy_cgi_url = PRIVOXY_CGI_URL;
our $log_level = get_default_log_level();
+ our $proxy = defined $ENV{'http_proxy'} ? $ENV{'http_proxy'} : PRIVOXY_ADDRESS;
}
-sub get_default_log_level () {
+sub get_default_log_level() {
my $log_level = 0;
#
############################################################################
-sub parse_tag ($) {
+sub parse_tag($) {
my $tag = shift;
return $tag;
}
-sub check_for_forbidden_characters ($) {
+sub check_for_forbidden_characters($) {
my $string = shift;
- my $allowed = '[-=\dA-Za-z~{}:./();\t ,+@"_%?&*^]';
+ my $allowed = '[-=\dA-Za-z~{}\[\]:./();\t ,+@"_%?&*^|]';
unless ($string =~ m/^$allowed*$/o) {
my $forbidden = $string;
# XXX: Contains a lot of code duplicated from load_action_files()
# that should be factored out.
-sub load_regression_tests_from_file ($) {
+sub load_regression_tests_from_file($) {
my $action_file = shift;
# initialized here
}
-sub load_regression_tests_through_privoxy () {
+sub load_regression_tests_through_privoxy() {
our $privoxy_cgi_url;
our @privoxy_config;
load_action_files(\@actionfiles);
}
-sub token_starts_new_test ($) {
+sub token_starts_new_test($) {
my $token = shift;
my @new_test_directives = ('set header', 'fetch test',
return 0;
}
-sub tokenize ($) {
+sub tokenize($) {
my ($token, $value) = (undef, undef);
# Remove leading and trailing white space and a
- # a leading <pre> which is part of the first line.
+ # leading <pre> which is part of the first line.
s@^\s*(<pre>)?@@;
s@\s*$@@;
return ($token, $value);
}
-sub enlist_new_test ($$$$$$) {
+sub enlist_new_test($$$$$$) {
my ($regression_tests, $token, $value, $si, $ri, $number) = @_;
my $type;
# XXX: Shares a lot of code with load_regression_tests_from_file()
# that should be factored out.
-sub load_action_files ($) {
+sub load_action_files($) {
# initialized here
our %actions;
############################################################################
# Fisher Yates shuffle from Perl's "How do I shuffle an array randomly?" FAQ
-sub fisher_yates_shuffle ($) {
+sub fisher_yates_shuffle($) {
my $deck = shift;
my $i = @$deck;
while ($i--) {
}
}
-sub execute_regression_tests () {
+sub execute_regression_tests() {
our @regression_tests;
my $loops = get_cli_option('loops');
}
}
-sub get_skip_reason ($) {
+sub get_skip_reason($) {
my $test = shift;
my $skip_reason = undef;
return $skip_reason;
}
-sub level_is_unacceptable ($) {
+sub level_is_unacceptable($) {
my $level = shift;
my $min_level = get_cli_option('min-level');
my $max_level = get_cli_option('max-level');
return $reason;
}
-sub dependency_unsatisfied ($) {
+sub dependency_unsatisfied($) {
my $level = shift;
our %dependencies;
return $dependency_problem;
}
-sub register_dependency ($$) {
+sub register_dependency($$) {
my $level = shift;
my $dependency = shift;
}
}
-sub execute_method_test ($) {
+sub execute_method_test($) {
my $test = shift;
+ our $privoxy_cgi_url;
+
my $buffer_ref;
my $status_code;
my $method = $test->{'data'};
# Don't complain about the 'missing' body
$curl_parameters .= '--head ' if ($method =~ /^HEAD$/i);
- $curl_parameters .= PRIVOXY_CGI_URL;
+ $curl_parameters .= $privoxy_cgi_url;
$buffer_ref = get_page_with_curl($curl_parameters);
$status_code = get_status_code($buffer_ref);
return check_status_code_result($status_code, $expected_status_code);
}
-sub execute_redirect_test ($) {
+sub execute_redirect_test($) {
my $test = shift;
my $buffer_ref;
return $success;
}
-sub execute_dumb_fetch_test ($) {
+sub execute_dumb_fetch_test($) {
my $test = shift;
+ our $privoxy_cgi_url;
+
my $buffer_ref;
my $status_code;
$curl_parameters .= '--request ' . quote($test->{method}) . ' ';
}
if ($test->{type} == TRUSTED_CGI_REQUEST) {
- $curl_parameters .= '--referer ' . quote(PRIVOXY_CGI_URL) . ' ';
+ $curl_parameters .= '--referer ' . quote($privoxy_cgi_url) . ' ';
}
$curl_parameters .= quote($test->{'data'});
return check_status_code_result($status_code, $expected_status_code);
}
-sub execute_block_test ($) {
+sub execute_block_test($) {
my $test = shift;
my $url = $test->{'data'};
return defined $final_results->{'+block'};
}
-sub execute_sticky_actions_test ($) {
+sub execute_sticky_actions_test($) {
my $test = shift;
my $url = $test->{'data'};
return $verified_actions == @sticky_actions;
}
-sub get_final_results ($) {
+sub get_final_results($) {
my $url = shift;
+ our $privoxy_cgi_url;
+
my $curl_parameters = '';
my %final_results = ();
my $final_results_reached = 0;
$url =~ s@:@%3A@g;
$url =~ s@/@%2F@g;
- $curl_parameters .= quote(PRIVOXY_CGI_URL . 'show-url-info?url=' . $url);
+ $curl_parameters .= quote($privoxy_cgi_url . 'show-url-info?url=' . $url);
foreach (@{get_cgi_page_or_else($curl_parameters)}) {
return \%final_results;
}
-sub check_status_code_result ($$) {
+sub check_status_code_result($$) {
my $status_code = shift;
my $expected_status_code = shift;
return $result;
}
-sub execute_client_header_regression_test ($) {
+sub execute_client_header_regression_test($) {
my $test = shift;
my $buffer_ref;
return check_header_result($test, $header);
}
-sub execute_server_header_regression_test ($) {
+sub execute_server_header_regression_test($) {
my $test = shift;
my $buffer_ref;
return check_header_result($test, $header);
}
-sub interpret_result ($) {
+sub interpret_result($) {
my $success = shift;
return $success ? "Success" : "Failure";
}
-sub check_header_result ($$) {
+sub check_header_result($$) {
my $test = shift;
my $header = shift;
return $success;
}
-sub get_header_name ($) {
+sub get_header_name($) {
my $header = shift;
return $header;
}
-sub get_header ($$) {
+sub get_header($$) {
our $filtered_request = '';
return $header;
}
-sub get_server_header ($$) {
+sub get_server_header($$) {
my $buffer_ref = shift;
my $test = shift;
return $header;
}
-sub get_status_code ($) {
+sub get_status_code($) {
my $buffer_ref = shift;
+ our $privoxy_cgi_url;
+
+ my $skip_connection_established_response = $privoxy_cgi_url =~ m@^https://@;
my @buffer = @{$buffer_ref};
foreach (@buffer) {
+ if ($skip_connection_established_response) {
+
+ next if (m@^HTTP/1\.1 200 Connection established@);
+ next if (m@^\r\n$@);
+ $skip_connection_established_response = 0;
+ }
+
if (/^HTTP\/\d\.\d (\d{3})/) {
return $1;
}
}
-sub get_test_keys () {
+sub get_test_keys() {
return ('tag', 'data', 'expect-header', 'ignore');
}
# XXX: incomplete
-sub test_content_as_string ($) {
+sub test_content_as_string($) {
my $test = shift;
#
############################################################################
-sub get_cgi_page_or_else ($) {
+sub get_cgi_page_or_else($) {
my $cgi_url = shift;
my $content_ref = get_page_with_curl($cgi_url);
if (200 != $status_code) {
- my $log_message = "Failed to fetch Privoxy CGI Page. " .
+ my $log_message = "Failed to fetch Privoxy CGI page '$cgi_url'. " .
"Received status code ". $status_code .
" while only 200 is acceptable.";
}
# XXX: misleading name
-sub get_show_request_with_curl ($) {
+sub get_show_request_with_curl($) {
our $privoxy_cgi_url;
my $test = shift;
# Enable the action to test
$curl_parameters .= '-H \'X-Privoxy-Control: ' . $test->{'tag'} . '\' ';
- # The header to filter
- $curl_parameters .= '-H \'' . $header . '\' ';
+
+ # Add the header to filter
+ if ($privoxy_cgi_url =~ m@^https://@ and $header =~ m@^Host:@) {
+ $curl_parameters .= '--proxy-header \'' . $header . '\' ';
+ } else {
+ $curl_parameters .= '-H \'' . $header . '\' ';
+ }
$curl_parameters .= ' ';
$curl_parameters .= $privoxy_cgi_url;
return get_cgi_page_or_else($curl_parameters);
}
-sub get_head_with_curl ($) {
+sub get_head_with_curl($) {
our $fellatio_url = FELLATIO_URL;
my $test = shift;
return get_page_with_curl($curl_parameters);
}
-sub get_page_with_curl ($) {
+sub get_page_with_curl($) {
our $proxy;
#
############################################################################
-sub array_as_string ($) {
+sub array_as_string($) {
my $array_ref = shift;
my $string = '';
return $string;
}
-sub show_test ($) {
+sub show_test($) {
my $test = shift;
log_message('Test is:' . test_content_as_string($test));
}
# Conditional log
-sub l ($$) {
+sub l($$) {
our $log_level;
my $this_level = shift;
my $message = shift;
log_message($message) if ($log_level & $this_level);
}
-sub log_and_die ($) {
+sub log_and_die($) {
my $message = shift;
log_message('Oh noes. ' . $message . ' Fatal error. Exiting.');
exit;
}
-sub log_message ($) {
+sub log_message($) {
my $message = shift;
printf("%s\n", $message);
}
-sub log_result ($$) {
+sub log_result($$) {
our $filtered_request;
log_message($message) if (!$result or cli_option_is_set('verbose'));
}
-sub quote ($) {
+sub quote($) {
my $s = shift;
return '\'' . $s . '\'';
}
-sub print_version () {
+sub print_version() {
printf PRT_VERSION . "\n";
}
-sub list_test_types () {
+sub list_test_types() {
my %test_types = (
'Client header test' => CLIENT_HEADER_TEST,
'Server header test' => 2,
}
}
-sub help () {
+sub help() {
our %cli_options;
+ our $privoxy_cgi_url;
print_version();
print << " EOF"
Options and their default values if they have any:
+ [--check-bad-ssl]
[--debug $cli_options{'debug'}]
[--forks $cli_options{'forks'}]
[--fuzzer-address]
[--max-level $cli_options{'max-level'}]
[--max-time $cli_options{'max-time'}]
[--min-level $cli_options{'min-level'}]
- [--privoxy-address]
+ [--privoxy-address $cli_options{'privoxy-address'}]
+ [--privoxy-cgi-prefix $privoxy_cgi_url]
[--retries $cli_options{'retries'}]
[--show-skipped-tests]
[--shuffle-tests]
exit(0);
}
-sub init_cli_options () {
+sub init_cli_options() {
our %cli_options;
our $log_level;
+ our $proxy;
$cli_options{'debug'} = $log_level;
$cli_options{'forks'} = CLI_FORKS;
$cli_options{'min-level'} = CLI_MIN_LEVEL;
$cli_options{'sleep-time'}= CLI_SLEEP_TIME;
$cli_options{'retries'} = CLI_RETRIES;
+ $cli_options{'privoxy-address'} = $proxy;
}
-sub parse_cli_options () {
+sub parse_cli_options() {
our %cli_options;
our $log_level;
+ our $privoxy_cgi_url;
init_cli_options();
GetOptions (
+ 'check-bad-ssl' => \$cli_options{'check-bad-ssl'},
'debug=i' => \$cli_options{'debug'},
'forks=i' => \$cli_options{'forks'},
'fuzzer-address=s' => \$cli_options{'fuzzer-address'},
'max-time=i' => \$cli_options{'max-time'},
'min-level=i' => \$cli_options{'min-level'},
'privoxy-address=s' => \$cli_options{'privoxy-address'},
+ 'privoxy-cgi-prefix=s' => \$privoxy_cgi_url, # XXX: Should use cli_options()
'retries=i' => \$cli_options{'retries'},
'shuffle-tests' => \$cli_options{'shuffle-tests'},
'show-skipped-tests' => \$cli_options{'show-skipped-tests'},
$log_level |= $cli_options{'debug'};
}
-sub cli_option_is_set ($) {
+sub cli_option_is_set($) {
our %cli_options;
my $cli_option = shift;
return defined $cli_options{$cli_option};
}
-sub get_cli_option ($) {
+sub get_cli_option($) {
our %cli_options;
my $cli_option = shift;
}
}
-sub main () {
+sub check_bad_ssl() {
+ my $failures = 0;
+ my @bad_ssl_urls_to_check = (
+ "https://expired.badssl.com/",
+ "https://wrong.host.badssl.com/",
+ "https://self-signed.badssl.com/",
+ "https://untrusted-root.badssl.com/",
+ "https://no-common-name.badssl.com/", # XXX: Certificate has expired ...
+ "https://no-subject.badssl.com/", # XXX: Certificate has expired ...
+ "https://incomplete-chain.badssl.com/",
+ );
+ # This is needed for get_status_code() to skip the
+ # status code from the "HTTP/1.1 200 Connection established"
+ # reply.
+ our $privoxy_cgi_url = "https://p.p/";
+
+ log_message("Requesting pages from badssl.com with various " .
+ "certificate problems. This will only work if Privoxy " .
+ "has been configured properly and can reach the Internet.");
+
+ foreach my $url_to_check (@bad_ssl_urls_to_check) {
+ my ($buffer_ref, $status_code);
+ log_message("Requesting $url_to_check");
+
+ $buffer_ref = get_page_with_curl($url_to_check);
+ $status_code = get_status_code($buffer_ref);
+
+ if (!check_status_code_result($status_code, "403")) {
+ $failures++;
+ }
+
+ }
+ if ($failures == 0) {
+ log_message("All requests resulted in status code 403 as expected.");
+ } else {
+ log_message("There were $failures requests that did not result in status code 403!");
+ }
+
+ return $failures;
+}
+
+sub main() {
init_our_variables();
parse_cli_options();
init_proxy_settings('vanilla-proxy');
+ if (cli_option_is_set('check-bad-ssl')) {
+ exit check_bad_ssl();
+ }
load_regression_tests();
init_proxy_settings('fuzz-proxy');
start_forks(get_cli_option('forks')) if cli_option_is_set('forks');
=head1 SYNOPSIS
-B<privoxy-regression-test> [B<--debug bitmask>] [B<--forks> forks]
+B<privoxy-regression-test> [B<--check-bad-ssl>] [B<--debug bitmask>] [B<--forks> forks]
[B<--fuzzer-feeding>] [B<--fuzzer-feeding>] [B<--help>] [B<--level level>]
[B<--local-test-file testfile>] [B<--loops count>] [B<--max-level max-level>]
[B<--max-time max-time>] [B<--min-level min-level>] B<--privoxy-address proxy-address>
-[B<--retries retries>] [B<--test-number test-number>]
+B<--privoxy-cgi-prefix cgi-prefix> [B<--retries retries>] [B<--test-number test-number>]
[B<--show-skipped-tests>] [B<--sleep-time> seconds] [B<--verbose>]
[B<--version>]
=head1 OPTIONS
+B<--check-bad-ssl> Instead of running the regression tests
+as described above, request pages from badssl.com with bad
+certificates to verify that Privoxy is detecting the
+certificate issues. Only works if Privoxy has been compiled
+with FEATURE_HTTPS_INSPECTION, has been configured properly
+and can reach the Internet.
+
B<--debug bitmask> Add the bitmask provided as integer
to the debug settings.
B<--privoxy-address proxy-address> Privoxy's listening address.
If it's not set, the value of the environment variable http_proxy
-will be used. B<proxy-address> has to be specified in http_proxy
-syntax.
+will be used unless the variable isn't set in which case
+http://127.0.0.1:8118/ will be used. B<proxy-address> has to
+be specified in http_proxy syntax.
+
+B<--privoxy-cgi-prefix privoxy-cgi-prefix> The prefix to use when
+building URLs that are supposed to reach Privoxy's CGI interface.
+If it's not set, B<http://p.p/> is used, which is supposed to work
+with the default Privoxy configuration.
+If Privoxy has been built with B<FEATURE_HTTPS_INSPECTION> enabled,
+and if https inspection is activated with the B<+https-inspection>
+action, this option can be used with
+B<https://p.p/> provided the system running Privoxy-Regression-Test
+has been configured to trust the certificate used by Privoxy.
+Note that there are currently two tests in the official
+B<regression-tests.action> file that are expected to fail when
+using a B<privoxy-cgi-prefix> with B<https://> and aren't automatically
+skipped.
B<--retries retries> Retry B<retries> times.
=head1 SEE ALSO
-privoxy(1) curl(1)
+privoxy(8) curl(1)
=head1 AUTHOR