From fa3b07eba0195d02effc1c8fdacc633812c8a687 Mon Sep 17 00:00:00 2001 From: Fabian Keil Date: Fri, 18 Jan 2008 19:33:00 +0000 Subject: [PATCH] Add Privoxy-Regression-Test 0.2 and a few regression tests. --- regression-tests.action | 689 ++++++++++++++ tools/privoxy-regression-test.pl | 1480 ++++++++++++++++++++++++++++++ 2 files changed, 2169 insertions(+) create mode 100644 regression-tests.action create mode 100755 tools/privoxy-regression-test.pl diff --git a/regression-tests.action b/regression-tests.action new file mode 100644 index 00000000..040540b6 --- /dev/null +++ b/regression-tests.action @@ -0,0 +1,689 @@ +############################################################################# +# $Id: regression-tests.action,v 1.52 2008/01/17 19:38:17 fk Exp $ +############################################################################# +# +# This is a configuration file for Privoxy-Regression-Test. +# +# After referencing it in your Privoxy configuration both Privoxy and +# Privoxy-Regression-Test should be good to go. +# +############################################################################# +# +# Copyright (c) 2007-2008 Fabian Keil +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# +############################################################################# + +{{settings}} +for-privoxy-version=3.0.7 + +####################################################### +# Enable taggers to activate the tests on demand +# and suppress hiding the User-Agent for +# Privoxy-Regression-Test to save log space. +####################################################### +{\ + +client-header-tagger{user-agent} \ + +client-header-tagger{privoxy-control} \ + +client-header-filter{privoxy-control} \ +} +config.privoxy.org/ +p.p/ + +{-hide-user-agent} +TAG:^User-Agent: Privoxy-Regression-Test + +####################################################### +# Test accept-language{}. +####################################################### + +# Set Header = Accept-Language: de-de +# Expect Header = Accept-Language: en-gb +{+hide-accept-language{en-gb}} +TAG:^hideaccept-language\{en-gb\}$ + +# Set Header = Accept-Language: de-de +# Expect Header = REMOVAL +{+hide-accept-language{block}} +TAG:^hide-accept-language\{block\}$ + +####################################################### +# Sections for hide-referrer{} to test: +# +# 1) conditional-block +# 2) conditional-forge +# 3) forge +# 4) block +# 5) a parameter that looks like a valid fake referrer +# 6) a parameter that looks like an invalid fake referrer +####################################################### + +# Set Header = Referer: http://www.example.org/foo +# Expect Header = REMOVAL +# +# Set Header = Referer: http://p.p/foo +# Expect Header = NO CHANGE +{+hide-referrer{conditional-block}} +TAG:^hide-referrer\{conditional-block\}$ + +# Set Header = Referer: http://www.example.org/foo +# Expect Header = Referer: http://p.p/ +# +# Set Header = Referer: http://p.p/foo +# Expect Header = NO CHANGE +{+hide-referrer{conditional-forge}} +TAG:^hide-referrer\{conditional-forge\}$ + +# Set Header = Referer: http://www.example.org/foo +# Expect Header = Referer: http://p.p/ +{+hide-referrer{forge}} +TAG:^hide-referrer\{forge\}$ + +# Set Header = Referer: http://www.example.org/foo +# Expect Header = REMOVAL +{+hide-referrer{block}} +TAG:^hide-referrer\{block\}$ + +# Set Header = Referer: http://www.example.org/foo +# Expect Header = Referer: invalid +{+hide-referrer{invalid}} +TAG:^hide-referrer\{invalid\}$ + +# Set Header = Referer: http://www.example.org/asdf +# Expect Header = Referer: http://www.privoxy.org/ +{+hide-referrer{http://www.privoxy.org/}} +TAG:^hide-referrer\{http://www.privoxy.org/\}$ + +#{+hide-referrer{}} +#TAG:^hide-referrer\{\}$ + +####################################################### +# Test hide-user-agent{}. +####################################################### + +# Set Header = User-Agent: Mozilla/5.0 (X11; U; NetBSD i386; de-CH; rv:1.8.1.6) Gecko/20070806 Firefox/2.0.0.6 +# Expect Header = User-Agent: Mozilla/5.0 (X11; U; FreeBSD alpha; en-GB; rv:1.8.1.6) Gecko/20070913 Firefox/2.0.0.6 +{+hide-user-agent{Mozilla/5.0 (X11; U; FreeBSD alpha; en-GB; rv:1.8.1.6) Gecko/20070913 Firefox/2.0.0.6}} +TAG:^hide-user-agent\{Mozilla/5\.0 \(X11; U; FreeBSD alpha; en-GB; rv:1\.8\.1\.6\) Gecko/20070913 Firefox/2\.0\.0\.6\}$ + +# XXX: Check the code that is tested here. +# Set Header = ua-blah: blah +# Expect Header = REMOVAL +{+hide-user-agent{block}} +TAG:^hide-user-agent{block}$ + +# Set Header = ua-blah: blah +# Expect Header = NO CHANGE +{-hide-user-agent{}} +TAG:^-hide-user-agent{block}$ + + +####################################################### +# Test add-header{}. +####################################################### + +# Set Header = X-Whatever: foo +# Expect Header = X-Custom-Header: yes, please + +{+add-header{X-Custom-Header: yes, please}} +TAG:^add-header\{X-Custom-Header: yes, please\}$ + +####################################################### +# Test client-header-filter{hide-tor-exit-notation}. +####################################################### + +# Set Header = Referer: http://p.p.zwiebelsuppe.exit/ +# Expect Header = Referer: http://p.p/ +# +# Set Header = Referer: http://p.p.zwiebelsuppe.exit/foo/bar/baaz/ +# Expect Header = Referer: http://p.p/foo/bar/baaz/ +# +# Set Header = Referer: http://p.p/ +# Expect Header = NO CHANGE +# +# Set Header = Referer: http://config.privoxy.org.zwiebelsuppe.exit/foo/bar/baaz.html +# Expect Header = Referer: http://config.privoxy.org/foo/bar/baaz.html +# +# Set Header = Host: p.p.zwiebelsuppe.exit +# Expect Header = Host: p.p +# +# Set Header = Host: p.p +# Expect Header = NO CHANGE + +{+client-header-filter{hide-tor-exit-notation} -hide-referer} +TAG:^client-header-filter\{hide-tor-exit-notation\}$ + +####################################################### +# Test crunch-client-header{}. +####################################################### + +# Set Header = Content-Type: text/html +# Expect Header = REMOVAL +# +# Set Header = Content-Type: text/html; charset=4711 +# Expect Header = REMOVAL +# +# Set Header = Content-Type: text/plain +# Expect Header = NO CHANGE + +{+crunch-client-header{text/html}} +TAG:^crunch-client-header\{text/plain\}$ + + +####################################################### +# Test crunch-if-none-match. +####################################################### + +# Set Header = If-None-Match: 8987afd239d2093kd2309kd +# Expect Header = REMOVAL + +# Set Header = If-None-Match: 82c3cb50c984ef11b1fed749949b2a16 +# Expect Header = REMOVAL + +# Set Header = If-Modified-Since: Thu, 04 Oct 2007 09:56:35 GMT +# Expect Header = NO CHANGE + +{+crunch-if-none-match -hide-if-modified-since} +TAG:^crunch-if-none-match$ + +####################################################### +# Test hide-if-modified-since +####################################################### + +# Set Header = If-Modified-Since: Thu, 04 Oct 2007 09:56:35 GMT +# Expect Header = REMOVAL +# +# Set Header = If-None-Match: 82c3cb50c984ef11b1fed749949b2a16 +# Expect Header = NO CHANGE + +{+hide-if-modified-since{block} -crunch-if-none-match} +TAG:^hide-if-modified-since\{block\}$ + +# Set Header = If-Modified-Since: Gee, this date is invalid +# Expect Header = REMOVAL +# Set Header = If-Modified-Since: Thu, 04 Oct 2007 09:56:35 GMT +# Expect Header = SOME CHANGE + +{+hide-if-modified-since{-60} -crunch-if-none-match} +TAG:^hide-if-modified-since\{-60\}$ + +# Set Header = If-Modified-Since: Gee, this date is invalid +# Expect Header = REMOVAL +# Set Header = If-Modified-Since: Thu, 04 Oct 2007 09:56:35 GMT +# Expect Header = SOME CHANGE + +{+hide-if-modified-since{+60} -crunch-if-none-match} +TAG:^hide-if-modified-since\{\+60\}$ + +# Set Header = If-Modified-Since: Gee, this date is invalid +# Expect Header = REMOVAL +# Set Header = If-Modified-Since: Thu, 04 Oct 2007 09:56:35 GMT +# Expect Header = SOME CHANGE + +{+hide-if-modified-since{60} -crunch-if-none-match} +TAG:^hide-if-modified-since\{60\}$ + +# Set Header = If-Modified-Since: Gee, this date is invalid +# Expect Header = REMOVAL +# Set Header = If-Modified-Since: Thu, 04 Oct 2007 09:56:35 GMT +# Expect Header = NO CHANGE + +{+hide-if-modified-since{+0} -crunch-if-none-match} +TAG:^hide-if-modified-since\{\+0\}$ + +# Set Header = If-Modified-Since: Gee, this date is invalid +# Expect Header = REMOVAL +# Set Header = If-Modified-Since: Thu, 04 Oct 2007 09:56:35 GMT +# Expect Header = NO CHANGE + +{+hide-if-modified-since{-0} -crunch-if-none-match} +TAG:^hide-if-modified-since\{-0\}$ + +# Set Header = If-Modified-Since: Gee, this date is invalid +# Expect Header = REMOVAL +# Set Header = If-Modified-Since: Thu, 04 Oct 2007 09:56:35 GMT +# Expect Header = NO CHANGE + +{+hide-if-modified-since{0} -crunch-if-none-match} +TAG:^hide-if-modified-since\{0\}$ + +# Set Header = If-Modified-Since: Gee, this date is invalid +# Expect Header = REMOVAL +# Set Header = If-Modified-Since: Thu, 04 Oct 2007 09:56:35 GMT +# Expect Header = NO CHANGE + +{+hide-if-modified-since{NaN} -crunch-if-none-match} +TAG:^hide-if-modified-since\{NaN\}$ + + +####################################################### +# Test crunch-outgoing-cookies +####################################################### + +# Set Header = If-Modified-Since: Gee, this date is invalid +# Expect Header = NO CHANGE +# +# Set Header = Cookie: PREF=ID=6cf0abd34262:TM=117335617:LM=1617:S=jZypyJ7LPiwFi1_ +# Expect Header = REMOVAL +{\ + +crunch-outgoing-cookies \ + -crunch-incoming-cookies \ + -session-cookies-only \ + -hide-if-modified-since \ +} +TAG:^crunch-outgoing-cookies$ + +####################################################### +# Test session-cookies-only +# +# XXX: pretty useless as session-cookies-only doesn't +# affect client headers. +####################################################### + +# Set Header = Cookie: NSC_gffe-iuuq-mc-wtfswfs=8efb330d3660;expires=Thu, 04-Oct-07 19:11:34 GMT;path=/ +# Expect Header = NO CHANGE +# +# Set Header = Cookie: PREF=ID=6cf0abd34262:TM=117335617:LM=1617:S=jZypyJ7LPiwFi1_ +# Expect Header = NO CHANGE +{\ + -crunch-outgoing-cookies \ + -crunch-incoming-cookies \ + +session-cookies-only \ + -hide-if-modified-since \ +} +TAG:^session-cookies-only$ + +####################################################### +# Test hide-forwarded-for-headers +####################################################### + +# Set Header = X-Forwarded-For: 10.0.0.1 +# Expect Header = REMOVAL +{\ + +hide-forwarded-for-headers \ +} +TAG:^hide-forwarded-for-headers$ + +# Set Header = X-Forwarded-For: 10.0.0.1 +# Expect Header = NO CHANGE +{\ + -hide-forwarded-for-headers \ +} +TAG:^-hide-forwarded-for-headers$ + +####################################################### +# Test hide-from-header +####################################################### + +# Set Header = From: schneewitchen@example.org +# Expect Header = REMOVAL +{\ + +hide-from-header{block}\ +} +TAG:^hide-from-header\{block\}$ + +# Set Header = From: schneewitchen@example.org +# Expect Header = From: siebenzwerge@example.org +{\ + +hide-from-header{siebenzwerge@example.org}\ +} +TAG:^hide-from-header\{siebenzwerge@example.org\}$ + +####################################################### +# Test prevent-compression +####################################################### + +# Set Header = Accept-Encoding: gzip, deflate +# Expect Header = REMOVAL +# +# Set Header = Accept-Encoding: gzip +# Expect Header = REMOVAL +# +# Set Header = Accept-Encoding: deflate +# Expect Header = REMOVAL +{\ + +prevent-compression\ +} +TAG:^prevent-compression$ + +####################################################### +# Test send-wafer. +####################################################### + +# Set Header = X-Does-Not-Matter: Foo bar +# Expect Header = Cookie: Tracking+me+is+easy+due+to+my+stupid+wafer+cookie +{\ + +send-wafer{Tracking me is easy due to my stupid wafer cookie}\ + -send-vanilla-wafer \ +} +TAG:^send-wafer\{Tracking me is easy due to my stupid wafer cookie\}$ + +####################################################### +# Test send-vanilla-wafer. +####################################################### + +# Set Header = X-Does-Not-Matter: Foo bar +# Expect Header = Cookie: NOTICE=TO_WHOM_IT_MAY_CONCERN_Do_not_send_me_any_copyrighted_information_other_than_the_document_that_I_am_requesting_or_any_of_its_necessary_components._In_particular_do_not_send_me_any_cookies_that_are_subject_to_a_claim_of_copyright_by_anybody._Take_notice_that_I_refuse_to_be_bound_by_any_license_condition_(copyright_or_otherwise)_applying_to_any_cookie._ +{\ + +send-vanilla-wafer \ + -send-wafer \ +} +TAG:^send-vanilla-wafer$ + +####################################################### +# Test content filters which could cause problems with +# range requests. +####################################################### + +# Set Header = Range: bytes=1234-5678 +# Expect Header = REMOVAL +# Set Header = If-Range: bytes=1234-5678 +# Expect Header = REMOVAL +# Set Header = Request-Range: bytes=1234-5678 +# Expect Header = REMOVAL +{\ + +deanimate-gifs{last} \ + -filter \ + -inspect-jpegs \ + -kill-popups \ +} +TAG:^deanimate-gifs\{last\}$ + +# Set Header = Range: bytes=1234-5678 +# Expect Header = REMOVAL +# Set Header = If-Range: bytes=1234-5678 +# Expect Header = REMOVAL +# Set Header = Request-Range: bytes=1234-5678 +# Expect Header = REMOVAL +{\ + -deanimate-gifs \ + +filter{banners-by-size} \ + -inspect-jpegs \ + -kill-popups \ +} +TAG:^filter\{banners-by-size\}$ + +# Set Header = Range: bytes=1234-5678 +# Expect Header = REMOVAL +# Set Header = If-Range: bytes=1234-5678 +# Expect Header = REMOVAL +# Set Header = Request-Range: bytes=1234-5678 +# Expect Header = REMOVAL +{\ + -deanimate-gifs \ + -filter \ + +inspect-jpegs \ + -kill-popups \ +} +TAG:^filter\{banners-by-size\}$ + +# Set Header = Range: bytes=1234-5678 +# Expect Header = REMOVAL +# Set Header = If-Range: bytes=1234-5678 +# Expect Header = REMOVAL +# Set Header = Request-Range: bytes=1234-5678 +# Expect Header = REMOVAL +{\ + -deanimate-gifs \ + -filter \ + -inspect-jpegs \ + +kill-popups \ +} +TAG:^kill-popups$ + +# Set Header = Range: bytes=1234-5678 +# Expect Header = NO CHANGE +# Set Header = If-Range: bytes=1234-5678 +# Expect Header = NO CHANGE +# Set Header = Request-Range: bytes=1234-5678 +# Expect Header = NO CHANGE +{\ + -deanimate-gifs \ + -filter \ + -inspect-jpegs \ + -kill-popups \ +} +TAG:^no-content-filter$ + +# Set Header = Connection: keep-alive +# Expect Header = Connection: close +# Set Header = Connection: +# Expect Header = Connection: close +{} +TAG:^Connection: close$ + +# XXX: Removing a header by not specifying a value is +# an inherited curl feature and could be viewed as a +# bug as far as Privoxy-Regression-Test is concerned. +# +# Set Header = Host: +# Expect Header = Host: p.p +{} +TAG:^No Host header$ + +# Set Header = Host: whatever.example.org +# Expect Header = NO CHANGE +{} +TAG:^Host header other than the target host$ + +# XXX: check the RFC to use a real value +# Set Header = Keep-Alive: Yes +# Expect Header = REMOVAL +{} +TAG:^Keep-Alive header removal$ + +# XXX: check the RFC to use a real value +# Set Header = proxy-connection: keep-alive +# Expect Header = REMOVAL +{} +TAG:^Proxy-Connection removal$ + +# Set Header = proxy-connection: keep-alive +# Expect Header = REMOVAL +{} +TAG:^Proxy-Connection removal$ + +# These are somewhat redundant when testing with +# GET requests, but I want to remember then when +# TRACE requests are supported. +# +# Set Header = Max-Forwards: 0 +# Expect Header = NO CHANGE +# Set Header = Max-Forwards: 1 +# Expect Header = NO CHANGE +# Set Header = Max-Forwards: -1 +# Expect Header = NO CHANGE +# Set Header = Max-Forwards: 3 +# Expect Header = NO CHANGE +{} +TAG:^Max-Forwards header without TRACE method$ + +################################################################ +# +# Fairly dumb tests for Privoxy CGI pages. +# +# These are mainly useful for checking for memory leaks +# with Valgrind or whether or not the user manual is installed +# correctly and are unlikely to actually detect any +# +# Note that if "Expect Status Code" is missing, 200 is implied. +# +################################################################ + +# Fetch Test = http://p.p/ +# Will fail if compiled with FEATURE_GRACEFUL_TERMINATION +# Fetch Test = http://p.p/die +# Expect Status Code = 404 +# Fetch Test = http://p.p/show-status +# Fetch Test = http://p.p/show-version +# Fetch Test = http://p.p/show-request +# Fetch Test = http://p.p/show-url-info +# Fetch Test = http://p.p/show-url-info?url=www.privoxy.org%2F +# Fetch Test = http://p.p/show-url-info?url=http:%2F%2Fwww.privoxy.org%2F +# Fetch Test = http://p.p/show-url-info?url=https:%2F%2Fwww.privoxy.org%2F +# Fetch Test = http://p.p/show-url-info?url= +# Fetch Test = http://p.p/show-url-info?url=%2F +# Fetch Test = http://p.p/toggle +# Fetch Test = http://p.p/edit-actions +# Fetch Test = http://p.p/eaa +# Fetch Test = http://p.p/eau +# Fetch Test = http://p.p/ear +# Fetch Test = http://p.p/eal +# Fetch Test = http://p.p/eafu +# Fetch Test = http://p.p/eas +# Fetch Test = http://p.p/easa +# Fetch Test = http://p.p/easr +# Fetch Test = http://p.p/eass +# Fetch Test = http://p.p/edit-actions-for-url +# Fetch Test = http://p.p/edit-actions-list +# Fetch Test = http://p.p/edit-actions-submit +# Fetch Test = http://p.p/edit-actions-url +# Fetch Test = http://p.p/edit-actions-url-form +# Fetch Test = http://p.p/edit-actions-add-url +# Fetch Test = http://p.p/edit-actions-add-url-form +# Fetch Test = http://p.p/edit-actions-remove-url +# Fetch Test = http://p.p/edit-actions-remove-url-form +# Fetch Test = http://p.p/edit-actions-section-add +# Fetch Test = http://p.p/edit-actions-section-remove +# Fetch Test = http://p.p/edit-actions-section-swap +# Fetch Test = http://p.p/error-favicon.ico +# Fetch Test = http://p.p/favicon.ico +# Fetch Test = http://p.p/robots.txt +# Fetch Test = http://p.p/send-banner +# Fetch Test = http://p.p/send-stylesheet +# Fetch Test = http://p.p/t +# Trusted CGI Request = http://p.p/edit-actions +# Expect Status Code = 302 +# Level = 12 # Depends on the CGI editor being enabled +# Fetch Test = http://p.p/does-not-exist +# Expect Status Code = 404 +# Trusted CGI Request = http://p.p/eaa +# Trusted CGI Request = http://p.p/eau +# Trusted CGI Request = http://p.p/ear +# Trusted CGI Request = http://p.p/eal +# Trusted CGI Request = http://p.p/eafu +# Trusted CGI Request = http://p.p/eas +# Trusted CGI Request = http://p.p/easa +# Trusted CGI Request = http://p.p/easr +# Trusted CGI Request = http://p.p/eass +# Trusted CGI Request = http://p.p/edit-actions-for-url +# Trusted CGI Request = http://p.p/edit-actions-list +# Trusted CGI Request = http://p.p/edit-actions-submit +# Trusted CGI Request = http://p.p/edit-actions-url +# Trusted CGI Request = http://p.p/edit-actions-url-form +# Trusted CGI Request = http://p.p/edit-actions-add-url +# Trusted CGI Request = http://p.p/edit-actions-add-url-form +# Trusted CGI Request = http://p.p/edit-actions-remove-url +# Trusted CGI Request = http://p.p/edit-actions-remove-url-form +# Trusted CGI Request = http://p.p/edit-actions-section-add +# Trusted CGI Request = http://p.p/edit-actions-section-remove +# Trusted CGI Request = http://p.p/edit-actions-section-swap +# Trusted CGI Request = http://p.p/send-stylesheet + +# The following tests depend on Privoxy being configured to deliver the user manual + +# Fetch Test = http://p.p/user-manual +# Expect Status Code = 302 +# Level = 9 +# Fetch Test = http://p.p/user-manual/ +# Level = 9 +# Fetch Test = http://p.p/user-manual/actions-file.html +# Level = 9 +# Fetch Test = http://p.p/user-manual/appendix.html +# Level = 9 +# Fetch Test = http://p.p/user-manual/config.html +# Level = 9 +# Fetch Test = http://p.p/user-manual/configuration.html +# Level = 9 +# Fetch Test = http://p.p/user-manual/contact.html +# Level = 9 +# Fetch Test = http://p.p/user-manual/copyright.html +# Level = 9 +# Fetch Test = http://p.p/user-manual/files-in-use.jpg +# Level = 9 +# Fetch Test = http://p.p/user-manual/filter-file.html +# Level = 9 +# Fetch Test = http://p.p/user-manual/index.html +# Level = 9 +# Fetch Test = http://p.p/user-manual/installation.html +# Level = 9 +# Fetch Test = http://p.p/user-manual/introduction.html +# Level = 9 +# Fetch Test = http://p.p/user-manual/p_doc.css +# Level = 9 +# Fetch Test = http://p.p/user-manual/proxy2.jpg +# Level = 9 +# Fetch Test = http://p.p/user-manual/proxy_setup.jpg +# Level = 9 +# Fetch Test = http://p.p/user-manual/quickstart.html +# Level = 9 +# Fetch Test = http://p.p/user-manual/seealso.html +# Level = 9 +# Fetch Test = http://p.p/user-manual/startup.html +# Level = 9 +# Fetch Test = http://p.p/user-manual/templates.html +# Level = 9 +# Fetch Test = http://p.p/user-manual/upgradersnote.html +# Level = 9 +# Fetch Test = http://p.p/user-manual/whatsnew.html +# Level = 9 + + +# Method Test = OPTIONS +# Method Test = GET +# Method Test = HEAD +# Method Test = POST +# Method Test = PUT +# Method Test = DELETE +# Method Test = OPTIONS +# Method Test = TRACE +# Method Test = CONNECT +# Method Test = PROPFIND +# Method Test = PROPPATCH +# Method Test = MOVE +# Method Test = COPY +# Method Test = MKCOL +# Method Test = LOCK +# Method Test = UNLOCK +# Method Test = BCOPY +# Method Test = BMOVE +# Method Test = BDELETE +# Method Test = BPROPFIND +# Method Test = BPROPPATCH +# Method Test = SUBSCRIBE +# Method Test = UNSUBSCRIBE +# Method Test = NOTIFY +# Method Test = POLL +# Method Test = VERSION-CONTROL +# Method Test = REPORT +# Method Test = CHECKOUT +# Method Test = CHECKIN +# Method Test = UNCHECKOUT +# Method Test = MKWORKSPACE +# Method Test = UPDATE +# Method Test = LABEL +# Method Test = MERGE +# Method Test = BASELINE-CONTROL +# Method Test = MKACTIVITY +# Method Test = PRIVOXY-REGRESSION-TEST-IN-THE-HOUSE +# Expect Status Code = 400 + +{+block} +config.privoxy.org:1-/ +p.p:1-/ + +{-block} +config.privoxy.org:3,79-81/ +p.p:3,22,79-81/ + diff --git a/tools/privoxy-regression-test.pl b/tools/privoxy-regression-test.pl new file mode 100755 index 00000000..88903945 --- /dev/null +++ b/tools/privoxy-regression-test.pl @@ -0,0 +1,1480 @@ +#!/usr/bin/perl + +############################################################################ +# +# Privoxy-Regression-Test +# +# A regression test "framework" for Privoxy. For documentation see: +# perldoc privoxy-regression-test.pl +# +# $Id: privoxy-regression-test.pl,v 1.102 2008/01/18 18:30:25 fk Exp $ +# +# Wish list: +# +# - Update documentation +# - Validate HTTP times. +# - Understand default.action.master comment syntax +# and verify that we actually block and unblock what +# the comments claim we do. +# - Implement a HTTP_VERSION directive or allow to +# specify whole request lines. +# - Support filter regression tests. +# - Add option to fork regression tests and run them in parallel, +# possibly optional forever. +# - Document magic Expect Header values +# - Internal fuzz support? +# +# Copyright (c) 2007-2008 Fabian Keil +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# +############################################################################ + +use warnings; +use strict; +use Getopt::Long; + +use constant { + PRT_VERSION => 'Privoxy-Regression-Test 0.2', + + CURL => 'curl', + + # CLI option defaults + CLI_RETRIES => 1, + CLI_LOOPS => 1, + CLI_MAX_TIME => 5, + CLI_MIN_LEVEL => 0, + CLI_MAX_LEVEL => 25, + + PRIVOXY_CGI_URL => 'http://p.p/', + FELLATIO_URL => 'http://10.0.0.1:8080/', + LEADING_LOG_DATE => 1, + LEADING_LOG_TIME => 1, + + DEBUG_LEVEL_FILE_LOADING => 0, + DEBUG_LEVEL_PAGE_FETCHING => 0, + + VERBOSE_TEST_DESCRIPTION => 1, + + DEBUG_LEVEL_VERBOSE_FAILURE => 1, + # XXX: Only partly implemented and mostly useless. + DEBUG_LEVEL_VERBOSE_SUCCESS => 0, + DEBUG_LEVEL_STATUS => 1, + + # Internal use, don't modify + # Available debug bits: + LL_ERROR => 1, + LL_VERBOSE_FAILURE => 2, + LL_PAGE_FETCHING => 4, + LL_FILE_LOADING => 8, + LL_VERBOSE_SUCCESS => 16, + LL_STATUS => 32, + LL_SOFT_ERROR => 64, + + CLIENT_HEADER_TEST => 1, + SERVER_HEADER_TEST => 2, + DUMB_FETCH_TEST => 3, + METHOD_TEST => 4, + TRUSTED_CGI_REQUEST => 6, +}; + +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 $verbose_test_description = VERBOSE_TEST_DESCRIPTION; + + our $log_level = get_default_log_level(); + +} + +sub get_default_log_level () { + + my $log_level = 0; + + $log_level |= LL_FILE_LOADING if DEBUG_LEVEL_FILE_LOADING; + $log_level |= LL_PAGE_FETCHING if DEBUG_LEVEL_PAGE_FETCHING; + $log_level |= LL_VERBOSE_FAILURE if DEBUG_LEVEL_VERBOSE_FAILURE; + $log_level |= LL_VERBOSE_SUCCESS if DEBUG_LEVEL_VERBOSE_SUCCESS; + $log_level |= LL_STATUS if DEBUG_LEVEL_STATUS; + + # These are intended to be always on. + $log_level |= LL_SOFT_ERROR; + $log_level |= LL_ERROR; + + return $log_level; +} + +############################################################################ +# +# File loading functions +# +############################################################################ + +sub parse_tag ($) { + + my $tag = shift; + + # Remove anchors + $tag =~ s@[\$\^]@@g; + # Unescape brackets and dots + $tag =~ s@\\(?=[{}().+])@@g; + + # log_message("Parsed tag: " . $tag); + + check_for_forbidden_characters($tag); + + return $tag; +} + +sub check_for_forbidden_characters ($) { + + my $tag = shift; # XXX: also used to check values though. + my $allowed = '[-=\dA-Za-z{}:.\/();\s,+@"_%\?&]'; + + unless ($tag =~ m/^$allowed*$/) { + my $forbidden = $tag; + $forbidden =~ s@^$allowed*(.).*@$1@; + + l(LL_ERROR, "'" . $tag . "' contains character '" . $forbidden. "' which is unacceptable."); + } +} + +sub load_regressions_tests () { + + our $privoxy_cgi_url; + my @actionfiles; + my $curl_url = ''; + my $file_number = 0; + + $curl_url .= $privoxy_cgi_url; + $curl_url .= 'show-status'; + + l(LL_STATUS, "Asking Privoxy for the number of action files available ..."); + + foreach (@{get_cgi_page_or_else($curl_url)}) { + + if (/(.*?)<\/td>/) { + + my $url = $privoxy_cgi_url . 'show-status?file=actions&index=' . $2; + $actionfiles[$file_number++] = $url; + } + } + + l(LL_FILE_LOADING, "Recognized " . @actionfiles . " actions files"); + + load_action_files(\@actionfiles); +} + +sub token_starts_new_test ($) { + + my $token = shift; + my @new_test_directives = + ('set header', 'fetch test', 'trusted cgi request', 'request header', 'method test'); + + foreach my $new_test_directive (@new_test_directives) { + return 1 if $new_test_directive eq $token; + } + return 0; + +} + +sub tokenize ($) { + + my ($token, $value) = (undef, undef); + + # Remove leading and trailing white space. + s@^\s*@@; + s@\s*$@@; + + # Reverse HTML-encoding + # XXX: Seriously imcomplete. + s@"@"@g; + + # Tokenize + if (/^\#\s*([^=]*?)\s*[=]\s*(.*?)\s*$/) { + + $token = $1; + $token =~ tr/[A-Z]/[a-z]/; + $value = $2; + + } elsif (/^TAG\s*:(.*)$/) { + + $token = 'tag'; + $value = $1; + + } + + return ($token, $value); +} + +sub load_action_files ($) { + + # initialized here + our %actions; + our @regression_tests; + + my $actionfiles_ref = shift; + my @actionfiles = @{$actionfiles_ref}; + my $number; + + my $si = 0; # Section index + my $ri = -1; # Regression test index + my $number_of_regression_tests = 0; + + my $ignored = 0; + + l(LL_STATUS, "Loading regression tests from action file(s) delivered by Privoxy."); + + for my $file_number (0 .. @actionfiles - 1) { + + my $curl_url = ' "' . $actionfiles[$file_number] . '"'; + my $actionfile = undef; + + foreach (@{get_cgi_page_or_else($curl_url)}) { + + my $no_checks = 0; + chomp; + + if (/

Contents of Actions File (.*?)/); + + my ($token, $value) = tokenize($_); + + next unless defined $token; + + # Load regression tests + + if (token_starts_new_test($token)) { + + # Beginning of new regression test. + $ri++; + $number_of_regression_tests++; + + l(LL_FILE_LOADING, "Regression test " . $number_of_regression_tests . " (section:" . $si . "):"); + + if ($token eq 'set header') { + + l(LL_FILE_LOADING, "Header to set: " . $value); + $regression_tests[$si][$ri]{'type'} = CLIENT_HEADER_TEST; + # Implicit default + $regression_tests[$si][$ri]{'level'} = CLIENT_HEADER_TEST; + + } elsif ($token eq 'request header') { + + l(LL_FILE_LOADING, "Header to request: " . $value); + $regression_tests[$si][$ri]{'type'} = SERVER_HEADER_TEST; + # Implicit default + $regression_tests[$si][$ri]{'expected-status-code'} = 200; + $regression_tests[$si][$ri]{'level'} = SERVER_HEADER_TEST; + + } elsif ($token eq 'trusted cgi request') { + + l(LL_FILE_LOADING, "CGI URL to test in a dumb way: " . $value); + $regression_tests[$si][$ri]{'type'} = TRUSTED_CGI_REQUEST; + # Implicit default + $regression_tests[$si][$ri]{'expected-status-code'} = 200; + $regression_tests[$si][$ri]{'level'} = TRUSTED_CGI_REQUEST; + + } elsif ($token eq 'fetch test') { + + l(LL_FILE_LOADING, "URL to test in a dumb way: " . $value); + $regression_tests[$si][$ri]{'type'} = DUMB_FETCH_TEST; + # Implicit default + $regression_tests[$si][$ri]{'expected-status-code'} = 200; + $regression_tests[$si][$ri]{'level'} = DUMB_FETCH_TEST; + + } elsif ($token eq 'method test') { + + l(LL_FILE_LOADING, "Method to test: " . $value); + $regression_tests[$si][$ri]{'type'} = METHOD_TEST; + # Implicit default + $regression_tests[$si][$ri]{'expected-status-code'} = 200; + $regression_tests[$si][$ri]{'level'} = METHOD_TEST; + + } else { + + die "Incomplete '" . $token . "' support detected."; + + } + + check_for_forbidden_characters($value); + + $regression_tests[$si][$ri]{'data'} = $value; + + # For function that only get passed single tests + $regression_tests[$si][$ri]{'section-id'} = $si; + $regression_tests[$si][$ri]{'regression-test-id'} = $ri; + $regression_tests[$si][$ri]{'file'} = $actionfile; + + } + + if ($si == -1 || $ri == -1) { + # No beginning of a test detected yet, + # so we don't care about any other test + # attributes. + next; + } + + if ($token eq 'expect header') { + + l(LL_FILE_LOADING, "Detected expectation: " . $value); + $regression_tests[$si][$ri]{'expect-header'} = $value; + + } elsif ($token eq 'tag') { + + next if ($ri == -1); + + my $tag = parse_tag($value); + + # We already checked in parse_tag() after filtering + $no_checks = 1; + + l(LL_FILE_LOADING, "Detected TAG: " . $tag); + + # Save tag for all tests in this section + do { + $regression_tests[$si][$ri]{'tag'} = $tag; + } while ($ri-- > 0); + + $si++; + $ri = -1; + + } elsif ($token eq 'ignore' && $value =~ /Yes/i) { + + l(LL_FILE_LOADING, "Ignoring section: " . test_content_as_string($regression_tests[$si][$ri])); + $regression_tests[$si][$ri]{'ignore'} = 1; + $ignored++; + + } elsif ($token eq 'expect status code') { + + l(LL_FILE_LOADING, "Expecting status code: " . $value); + $regression_tests[$si][$ri]{'expected-status-code'} = $value; + + } elsif ($token eq 'level') { # XXX: stupid name + + $value =~ s@(\d+).*@$1@; + l(LL_FILE_LOADING, "Level: " . $value); + $regression_tests[$si][$ri]{'level'} = $value; + + } elsif ($token eq 'method') { + + l(LL_FILE_LOADING, "Method: " . $value); + $regression_tests[$si][$ri]{'method'} = $value; + } else { + # We don't use it, so we don't need + $no_checks = 1; + } + # XXX: Neccessary? + check_for_forbidden_characters($value) unless $no_checks; + check_for_forbidden_characters($token); + } + } + + l(LL_FILE_LOADING, "Done loading " . $number_of_regression_tests . " regression tests." + . " Of which " . $ignored. " will be ignored)\n"); +} + +############################################################################ +# +# Regression test executing functions +# +############################################################################ + +sub execute_regression_tests () { + + our @regression_tests; + my $loops = get_cli_option('loops'); + my $all_tests = 0; + my $all_failures = 0; + my $all_successes = 0; + + unless (@regression_tests) { + + l(LL_STATUS, "No regression tests found."); + return; + } + + l(LL_STATUS, "Executing regression tests ..."); + + while ($loops-- > 0) { + + my $successes = 0; + my $tests = 0; + my $failures; + my $skipped = 0; + my $test_number = 0; + + for my $s (0 .. @regression_tests - 1) { + + my $r = 0; + + while (defined $regression_tests[$s][$r]) { + + die "Section id mismatch" if ($s != $regression_tests[$s][$r]{'section-id'}); + die "Regression test id mismatch" if ($r != $regression_tests[$s][$r]{'regression-test-id'}); + + if ($regression_tests[$s][$r]{'ignore'} + or level_is_unacceptable($regression_tests[$s][$r]{'level'}) + or test_number_is_unacceptable($test_number)) { + + $skipped++; + + } else { + + my $result = execute_regression_test($regression_tests[$s][$r]); + + log_result($regression_tests[$s][$r], $result, $tests); + + $successes += $result; + $tests++; + } + $r++; + $test_number++; + } + } + $failures = $tests - $successes; + + log_message("Executed " . $tests . " regression tests. " . + 'Skipped ' . $skipped . '. ' . + $successes . " successes, " . $failures . " failures."); + + $all_tests += $tests; + $all_failures += $failures; + $all_successes += $successes; + + } + + + if (get_cli_option('loops') > 1) { + log_message("Total: Executed " . $all_tests . " regression tests. " . + $all_successes . " successes, " . $all_failures . " failures."); + } +} + +sub level_is_unacceptable ($) { + my $level = shift; + return ((cli_option_is_set('level') and get_cli_option('level') != $level) + or ($level < get_cli_option('min-level')) + or ($level > get_cli_option('max-level')) + ); +} + +sub test_number_is_unacceptable ($) { + my $test_number = shift; + return (cli_option_is_set('test-number') + and get_cli_option('test-number') != $test_number) +} + + +# XXX: somewhat misleading name +sub execute_regression_test ($) { + + my $test_ref = shift; + my %test = %{$test_ref}; + my $result = 0; + + if ($test{'type'} == CLIENT_HEADER_TEST) { + + $result = execute_client_header_regression_test($test_ref); + + } elsif ($test{'type'} == SERVER_HEADER_TEST) { + + $result = execute_server_header_regression_test($test_ref); + + } elsif ($test{'type'} == DUMB_FETCH_TEST + or $test{'type'} == TRUSTED_CGI_REQUEST) { + + $result = execute_dumb_fetch_test($test_ref); + + } elsif ($test{'type'} == METHOD_TEST) { + + $result = execute_method_test($test_ref); + + } else { + + die "Unsuported test type detected: " . $test{'type'}; + + } + + + return $result; +} + +sub execute_method_test ($) { + + my $test_ref = shift; + my %test = %{$test_ref}; + my $buffer_ref; + my $result = 0; + my $status_code; + my $method = $test{'data'}; + + my $curl_parameters = ''; + my $expected_status_code = $test{'expected-status-code'}; + + if ($method =~ /HEAD/i) { + + $curl_parameters .= '--head '; + + } else { + + $curl_parameters .= '-X ' . $method . ' '; + } + + $curl_parameters .= PRIVOXY_CGI_URL; + + $buffer_ref = get_page_with_curl($curl_parameters); + $status_code = get_status_code($buffer_ref); + + $result = check_status_code_result($status_code, $expected_status_code); + + return $result; +} + + +sub execute_dumb_fetch_test ($) { + + my $test_ref = shift; + my %test = %{$test_ref}; + my $buffer_ref; + my $result = 0; + my $status_code; + + my $curl_parameters = ''; + my $expected_status_code = $test{'expected-status-code'}; + + if (defined $test{method}) { + $curl_parameters .= '-X ' . $test{method} . ' '; + } + if ($test{type} == TRUSTED_CGI_REQUEST) { + $curl_parameters .= '--referer ' . PRIVOXY_CGI_URL . ' '; + } + + $curl_parameters .= $test{'data'}; + + $buffer_ref = get_page_with_curl($curl_parameters); + $status_code = get_status_code($buffer_ref); + + $result = check_status_code_result($status_code, $expected_status_code); + + return $result; +} + +sub check_status_code_result ($$) { + + my $status_code = shift; + my $expected_status_code = shift; + my $result = 0; + + if ($expected_status_code == $status_code) { + + $result = 1; + l(LL_VERBOSE_SUCCESS, + "Yay. We expected status code " . $expected_status_code . ", and received: " . $status_code . '.'); + + } elsif (cli_option_is_set('fuzzer-feeding') and $status_code == 123) { + + l(LL_VERBOSE_FAILURE, + "Oh well. Status code lost while fuzzing. Can't check if it was " . $expected_status_code . '.'); + + } else { + + l(LL_VERBOSE_FAILURE, + "Ooops. We expected status code " . $expected_status_code . ", but received: " . $status_code . '.'); + + } + + return $result; +} + +sub execute_client_header_regression_test ($) { + + my $test_ref = shift; + my $buffer_ref; + my $header; + my $result = 0; + + $buffer_ref = get_show_request_with_curl($test_ref); + + $header = get_header($buffer_ref, $test_ref); + $result = check_header_result($test_ref, $header); + + return $result; +} + +sub execute_server_header_regression_test ($) { + + my $test_ref = shift; + my $buffer_ref; + my $header; + my $result = 0; + + $buffer_ref = get_head_with_curl($test_ref); + + $header = get_server_header($buffer_ref, $test_ref); + $result = check_header_result($test_ref, $header); + + return $result; +} + + +sub interpret_result ($) { + my $success = shift; + return $success ? "Success" : "Failure"; +} + +sub check_header_result ($$) { + + my $test_ref = shift; + my $header = shift; + + my %test = %{$test_ref}; + my $expect_header = $test{'expect-header'}; + my $success = 0; + + $header =~ s@ @ @g if defined($header); + + if ($expect_header eq 'NO CHANGE') { + + if (defined($header) and $header eq $test{'data'}) { + + $success = 1; + + } else { + + $header //= "REMOVAL"; + l(LL_VERBOSE_FAILURE, + "Ooops. Got: " . $header . " while expecting: " . $expect_header); + } + + } elsif ($expect_header eq 'REMOVAL') { + + if (defined($header) and $header eq $test{'data'}) { + + l(LL_VERBOSE_FAILURE, + "Ooops. Expected removal but: " . $header . " is still there."); + + } else { + + # XXX: Use more reliable check here and make sure + # the header has a different name. + $success = 1; + + } + + } elsif ($expect_header eq 'SOME CHANGE') { + + if (defined($header) and not $header eq $test{'data'}) { + + $success = 1; + + } else { + + $header //= "REMOVAL"; + l(LL_VERBOSE_FAILURE, + "Ooops. Got: " . $header . " while expecting: SOME CHANGE"); + } + + + } else { + + if (defined($header) and $header eq $expect_header) { + + $success = 1; + + } else { + + $header //= "'No matching header'"; # XXX: No header detected to be precise + l(LL_VERBOSE_FAILURE, + "Ooops. Got: " . $header . " while expecting: " . $expect_header); + } + } + return $success; +} + + +sub get_header_name ($) { + + my $header = shift; + + $header =~ s@(.*?: ).*@$1@; + + return $header; +} + +sub get_header ($$) { + + our $filtered_request = ''; + + my $buffer_ref = shift; + my $test_ref = shift; + + my %test = %{$test_ref}; + my @buffer = @{$buffer_ref}; + + my $expect_header = $test{'expect-header'}; + + my $line; + my $processed_request_reached = 0; + my $read_header = 0; + my $processed_request = ''; + my $header; + my $header_to_get; + + if ($expect_header eq 'REMOVAL' + or $expect_header eq 'NO CHANGE' + or $expect_header eq 'SOME CHANGE') { + + $expect_header = $test{'data'}; + + } + + $header_to_get = get_header_name($expect_header); + + foreach (@buffer) { + + # Skip everything before the Processed request + if (/Processed Request/) { + $processed_request_reached = 1; + next; + } + next unless $processed_request_reached; + + # End loop after the Processed request + last if (/<\/pre>/); + + # Ditch tags and leading/trailing white space. + s@^\s*<.*?>@@g; + s@\s*$@@g; + + $filtered_request .= "\n" . $_; + + if (/^$header_to_get/) { + $read_header = 1; + $header = $_; + last; + } + } + + return $header; +} + +sub get_server_header ($$) { + + my $buffer_ref = shift; + my $test_ref = shift; + + my %test = %{$test_ref}; + my @buffer = @{$buffer_ref}; + + my $expect_header = $test{'expect-header'}; + my $header; + my $header_to_get; + + if ($expect_header eq 'REMOVAL' + or $expect_header eq 'NO CHANGE' + or $expect_header eq 'SOME CHANGE') { + + $expect_header = $test{'data'}; + + } + + $header_to_get = get_header_name($expect_header); + + foreach (@buffer) { + + # XXX: shoul probably verify that the request + # was actually answered by Fellatio. + if (/^$header_to_get/) { + $header = $_; + $header =~ s@\s*$@@g; + last; + } + } + + return $header; +} + +sub get_header_to_check ($) { + + # No longer in use but not removed yet. + + my $buffer_ref = shift; + my $header; + my @buffer = @{$buffer_ref}; + my $line; + my $processed_request_reached = 0; + my $read_header = 0; + my $processed_request = ''; + + l(LL_ERROR, "You are not supposed to use get_header_to_()!"); + + foreach (@buffer) { + + # Skip everything before the Processed request + if (/Processed Request/) { + $processed_request_reached = 1; + next; + } + next unless $processed_request_reached; + + # End loop after the Processed request + last if (/<\/pre>/); + + # Ditch tags and leading/trailing white space. + s@^\s*<.*?>@@g; + s@\s*$@@g; + + $processed_request .= $_; + + if (/^X-Privoxy-Regression-Test/) { + $read_header = 1; + next; + } + + if ($read_header) { + $header = $_; + $read_header = 0; + } + + } + + return $header; +} + +sub get_status_code ($) { + + my $buffer_ref = shift; + my @buffer = @{$buffer_ref}; + + foreach (@buffer) { + + if (/^HTTP\/\d\.\d (\d{3})/) { + + return $1; + + } else { + + return '123' if cli_option_is_set('fuzzer-feeding'); + chomp; + l(LL_ERROR, 'Unexpected buffer line: "' . $_ . '"'); + } + } +} + +sub get_test_keys () { + return ('tag', 'data', 'expect-header', 'ignore'); +} + +# XXX: incomplete +sub test_content_as_string ($) { + + my $test_ref = shift; + my %test = %{$test_ref}; + + my $s = "\n\t"; + + foreach my $key (get_test_keys()) { + #$test{$key} = $test{$key} // 'undefined'; + $test{$key} = 'Not set' unless (defined $test{$key}); + } + + $s .= 'Tag: ' . $test{'tag'}; + $s .= "\n\t"; + $s .= 'Set header: ' . $test{'data'}; # XXX: adjust for other test types + $s .= "\n\t"; + $s .= 'Expected header: ' . $test{'expect-header'}; + $s .= "\n\t"; + $s .= 'Ignore: ' . $test{'ignore'}; + + return $s; +} + +############################################################################ +# +# HTTP fetch functions +# +############################################################################ + +sub check_for_curl () { + my $curl = CURL; + l(LL_ERROR, "No curl found.") unless (`which $curl`); +} + +sub get_cgi_page_or_else ($) { + + my $cgi_url = shift; + my $content_ref = get_page_with_curl($cgi_url); + my $status_code = get_status_code($content_ref); + + if (200 != $status_code) { + + my $log_message = "Failed to fetch Privoxy CGI Page. " . + "Received status code ". $status_code . + " while only 200 is acceptable."; + + if (cli_option_is_set('fuzzer-feeding')) { + + $log_message .= " Ignored due to fuzzer feeding."; + l(LL_SOFT_ERROR, $log_message) + + } else { + + l(LL_ERROR, $log_message); + + } + } + + return $content_ref; +} + +sub get_show_request_with_curl ($) { + + our $privoxy_cgi_url; + my $test_ref = shift; + my %test = %{$test_ref}; + + my $curl_parameters = ' '; + + # Enable the action to test + $curl_parameters .= '-H \'X-Privoxy-Control: ' . $test{'tag'} . '\' '; + # The header to filter + $curl_parameters .= '-H \'' . $test{'data'} . '\' '; + + $curl_parameters .= ' '; + $curl_parameters .= $privoxy_cgi_url; + $curl_parameters .= 'show-request'; + + return get_cgi_page_or_else($curl_parameters); +} + + +sub get_head_with_curl ($) { + + our $fellatio_url = FELLATIO_URL; + my $test_ref = shift; + my %test = %{$test_ref}; + + my $curl_parameters = ' '; + + # Enable the action to test + $curl_parameters .= '-H \'X-Privoxy-Control: ' . $test{'tag'} . '\' '; + # The header to filter + $curl_parameters .= '-H \'X-Gimme-Head-With: ' . $test{'data'} . '\' '; + $curl_parameters .= '--head '; + + $curl_parameters .= ' '; + $curl_parameters .= $fellatio_url; + + return get_page_with_curl($curl_parameters); +} + + +sub get_page_with_curl ($) { + + my $parameters = shift; + my @buffer; + my $curl_line = CURL; + my $retries_left = get_cli_option('retries') + 1; + my $failure_reason; + + if (cli_option_is_set('privoxy-address')) { + $curl_line .= ' --proxy ' . get_cli_option('privoxy-address'); + } + + # Let Privoxy emit two log messages less. + $curl_line .= ' -H \'Proxy-Connection:\' ' unless $parameters =~ /Proxy-Connection:/; + $curl_line .= ' -H \'Connection: close\' ' unless $parameters =~ /Connection:/; + # We don't care about fetch statistic. + $curl_line .= " -s "; + # We do care about the failure reason if any. + $curl_line .= " -S "; + # We want to see the HTTP status code + $curl_line .= " --include "; + # We want to advertise ourselves + $curl_line .= " --user-agent '" . PRT_VERSION . "' "; + # We aren't too patient + $curl_line .= " --max-time '" . get_cli_option('max-time') . "' "; + + $curl_line .= $parameters; + # XXX: still necessary? + $curl_line .= ' 2>&1'; + + l(LL_PAGE_FETCHING, "Executing: " . $curl_line); + + do { + @buffer = `$curl_line`; + + if ($?) { + $failure_reason = array_as_string(\@buffer); + chomp $failure_reason; + l(LL_SOFT_ERROR, "Fetch failure: '" . $failure_reason . $! ."'"); + } + } while ($? && --$retries_left); + + unless ($retries_left) { + l(LL_ERROR, + "Running curl failed " . get_cli_option('retries') . + " times in a row. Last error: '" . $failure_reason . "'."); + } + + return \@buffer; +} + + +############################################################################ +# +# Log functions +# +############################################################################ + +sub array_as_string ($) { + my $array_ref = shift; + my $string = ''; + + foreach (@{$array_ref}) { + $string .= $_; + } + + return $string; +} + + +sub show_test ($) { + my $test_ref = shift; + log_message('Test is:' . test_content_as_string($test_ref)); +} + +# Conditional log +sub l ($$) { + our $log_level; + my $this_level = shift; + my $message = shift; + + return unless ($log_level & $this_level); + + if (LL_ERROR & $this_level) { + $message = 'Oh noes. ' . $message . ' Fatal error. Exiting.'; + } + + log_message($message); + + if (LL_ERROR & $this_level) { + exit; + } +} + +sub log_message ($) { + + my $message = shift; + + our $logfile; + our $no_logging; + our $leading_log_date; + our $leading_log_time; + + my $time_stamp = ''; + my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime time; + + if ($leading_log_date || $leading_log_time) { + + if ($leading_log_date) { + $year += 1900; + $mon += 1; + $time_stamp = sprintf("%i/%.2i/%.2i", $year, $mon, $mday); + } + + if ($leading_log_time) { + $time_stamp .= ' ' if $leading_log_date; + $time_stamp.= sprintf("%.2i:%.2i:%.2i", $hour, $min, $sec); + } + + $message = $time_stamp . ": " . $message; + } + + + printf(STDERR "%s\n", $message); + +} + +sub log_result ($$) { + + our $verbose_test_description; + our $filtered_request; + + my $test_ref = shift; + my $result = shift; + my $number = shift; + + my %test = %{$test_ref}; + my $message = ''; + + $message .= interpret_result($result); + $message .= " for test "; + $message .= $number; + $message .= '/'; + $message .= $test{'section-id'}; + $message .= '/'; + $message .= $test{'regression-test-id'}; + $message .= '.'; + + if ($verbose_test_description) { + + if ($test{'type'} == CLIENT_HEADER_TEST) { + + $message .= ' Header '; + $message .= quote($test{'data'}); + $message .= ' and tag '; + $message .= quote($test{'tag'}); + + } elsif ($test{'type'} == SERVER_HEADER_TEST) { + + $message .= ' Request Header '; + $message .= quote($test{'data'}); + $message .= ' and tag '; + $message .= quote($test{'tag'}); + + } elsif ($test{'type'} == DUMB_FETCH_TEST) { + + $message .= ' URL '; + $message .= quote($test{'data'}); + $message .= ' and expected status code '; + $message .= quote($test{'expected-status-code'}); + + } elsif ($test{'type'} == TRUSTED_CGI_REQUEST) { + + $message .= ' CGI URL '; + $message .= quote($test{'data'}); + $message .= ' and expected status code '; + $message .= quote($test{'expected-status-code'}); + + } elsif ($test{'type'} == METHOD_TEST) { + + $message .= ' HTTP method '; + $message .= quote($test{'data'}); + $message .= ' and expected status code '; + $message .= quote($test{'expected-status-code'}); + + } else { + + die "Incomplete support for test type " . $test{'type'} . " detected."; + + } + } + + log_message($message) unless ($result && cli_option_is_set('silent')); +} + +sub quote ($) { + my $s = shift; + return '\'' . $s . '\''; +} + +sub print_version () { + printf PRT_VERSION . "\n" . 'Copyright (C) 2007-2008 Fabian Keil ' . "\n"; +} + +sub help () { + + our %cli_options; + + print_version(); + + print << " EOF" + +Options and their default values if they have any: + [--debug $cli_options{'debug'}] + [--fuzzer-feeding] + [--help] + [--level] + [--loops $cli_options{'loops'}] + [--max-level $cli_options{'max-level'}] + [--max-time $cli_options{'max-time'}] + [--min-level $cli_options{'min-level'}] + [--privoxy-address] + [--retries $cli_options{'retries'}] + [--silent] + [--version] +see "perldoc $0" for more information + EOF + ; + exit(0); +} + +sub init_cli_options () { + + our %cli_options; + our $log_level; + + $cli_options{'min-level'} = CLI_MIN_LEVEL; + $cli_options{'max-level'} = CLI_MAX_LEVEL; + $cli_options{'debug'} = $log_level; + $cli_options{'loops'} = CLI_LOOPS; + $cli_options{'max-time'} = CLI_MAX_TIME; + $cli_options{'retries'} = CLI_RETRIES; +} + +sub parse_cli_options () { + + our %cli_options; + our $log_level; + + init_cli_options(); + + GetOptions ( + 'debug=s' => \$cli_options{'debug'}, + 'help' => sub { help }, + 'silent' => \$cli_options{'silent'}, + 'min-level=s' => \$cli_options{'min-level'}, + 'max-level=s' => \$cli_options{'max-level'}, + 'privoxy-address=s' => \$cli_options{'privoxy-address'}, + 'level=s' => \$cli_options{'level'}, + 'loops=s' => \$cli_options{'loops'}, + 'test-number=s' => \$cli_options{'test-number'}, + 'fuzzer-feeding' => \$cli_options{'fuzzer-feeding'}, + 'retries=s' => \$cli_options{'retries'}, + 'max-time=s' => \$cli_options{'max-time'}, + 'version' => sub { print_version && exit(0) } + ); + $log_level |= $cli_options{'debug'}; +} + +sub cli_option_is_set ($) { + + our %cli_options; + my $cli_option = shift; + + return defined $cli_options{$cli_option}; +} + + +sub get_cli_option ($) { + + our %cli_options; + my $cli_option = shift; + + die "Unknown CLI option: $cli_option" unless defined $cli_options{$cli_option}; + + return $cli_options{$cli_option}; +} + + +sub main () { + + init_our_variables(); + parse_cli_options(); + check_for_curl(); + load_regressions_tests(); + execute_regression_tests(); +} + +main(); + +=head1 NAME + +B - A regression test "framework" for Privoxy. + +=head1 SYNOPSIS + +B [B<--debug bitmask>] [B<--fuzzer-feeding>] [B<--help>] +[B<--level level>] [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<--silent>] [B<--version>] + +=head1 DESCRIPTION + +Privoxy-Regression-Test is supposed to one day become +a regression test suite for Privoxy. It's not quite there +yet, however, and can currently only test client header +actions and check the returned status code for requests +to arbitrary URLs. + +Client header actions are tested by requesting +B and checking whether +or not Privoxy modified the original request as expected. + +The original request contains both the header the action-to-be-tested +acts upon and an additional tagger-triggering header that enables +the action to test. + +=head1 CONFIGURATION FILE SYNTAX + +Privoxy-Regression-Test's configuration is embedded in +Privoxy action files and loaded through Privoxy's web interface. + +It makes testing a Privoxy version running on a remote system easier +and should prevent you from updating your tests without updating Privoxy's +configuration accordingly. + +A client-header-action test section looks like this: + + # Set Header = Referer: http://www.example.org.zwiebelsuppe.exit/ + # Expect Header = Referer: http://www.example.org/ + {+client-header-filter{hide-tor-exit-notation} -hide-referer} + TAG:^client-header-filter\{hide-tor-exit-notation\}$ + +The example above causes Privoxy-Regression-Test to set +the header B +and to expect it to be modified to +B. + +When testing this section, Privoxy-Regression-Test will set the header +B +causing the B tagger to create the tag +B which will finally +cause Privoxy to enable the action section. + +Note that the actions itself are only used by Privoxy, +Privoxy-Regression-Test ignores them and will be happy +as long as the expectations are satisfied. + +A fetch test looks like this: + + # Fetch Test = http://p.p/user-manual + # Expect Status Code = 302 + +It tells Privoxy-Regression-Test to request B +and to expect a response with the HTTP status code B<302>. Obviously that's +not a very thorough test and mainly useful to get some code coverage +for Valgrind or to verify that the templates are installed correctly. + +If you want to test CGI pages that require a trusted +referer, you can use: + + # Trusted CGI Request = http://p.p/edit-actions + +It works like ordinary fetch tests, but sets the referer +header to a trusted value. + +If no explicit status code expectation is set, B<200> is used. + +Additionally all tests have test levels to let the user +control which ones to execute (see I below). +Test levels are either set with the B directive, +or implicitly through the test type. + +Fetch tests default to level 6, tests for trusted +CGI requests to level 3 and client-header-action tests +to level 1. + +=head1 OPTIONS + +B<--debug bitmask> Add the bitmask provided as integer +to the debug settings. + +B<--fuzzer-feeding> Ignore some errors that would otherwise +cause Privoxy-Regression-Test to abort the test because +they shouldn't happen in normal operation. This option is +intended to be used if Privoxy-Regression-Test is only +used to feed a fuzzer in which case there's a high chance +that Privoxy gets an invalid request and returns an error +message. + +B<--help> Shows available command line options. + +B<--level level> Only execute tests with the specified B. + +B<--loop count> Loop through the regression tests B times. +Useful to feed a fuzzer, or when doing stress tests with +several Privoxy-Regression-Test instances running at the same +time. + +B<--max-level max-level> Only execute tests with a B +below or equal to the numerical B. + +B<--max-time max-time> Give Privoxy B seconds +to return data. Increasing the default may make sense when +Privoxy is run through Valgrind, decreasing the default may +make sense when Privoxy-Regression-Test is used to feed +a fuzzer. + +B<--min-level min-level> Only execute tests with a B +above or equal to the numerical B. + +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 has to be specified in http_proxy +syntax. + +B<--retries retries> Retry B times. + +B<--silent> Don't log succesful test runs. + +B<--version> Print version and exit. + +The second dash is optional, options can be shortened, +as long as there are no ambiguities. + +=head1 PRIVOXY CONFIGURATION + +Privoxy-Regression-Test is shipped with B +which aims to test all official client-header modifying actions +and can be used to verify that the templates and the user manual +files are installed correctly. + +To use it, it has to be copied in Privoxy's configuration +directory, and afterwards referenced in Privoxy's configuration +file with the line: + + actionsfile regression-tests.action + +In general, its tests are supposed to work without changing +any other action files, unless you already added lots of +taggers yourself. If you are using taggers that cause problems, +you might have to temporary disable them for Privoxy's CGI pages. + +Some of the regression tests rely on Privoxy features that +may be disabled in your configuration. Tests with a level below +7 are supposed to work with all Privoxy configurations (provided +you didn't build with FEATURE_GRACEFUL_TERMINATION). + +Tests with level 9 require Privoxy to deliver the User Manual, +tests with level 12 require the CGI editor to be enabled. + +=head1 CAVEATS + +Expect the configuration file syntax to change with future releases. + +=head1 LIMITATIONS + +As Privoxy's B page only shows client headers, +Privoxy-Regression-Test can't use it to test Privoxy actions +that modify server headers. + +As Privoxy-Regression-Test relies on Privoxy's tag feature to +control the actions to test, it currently only works with +Privoxy 3.0.7 or later. + +At the moment Privoxy-Regression-Test fetches Privoxy's +configuration page through I(1), therefore you have to +have I installed, otherwise you won't be able to run +Privoxy-Regression-Test in a meaningful way. + +=head1 SEE ALSO + +privoxy(1) curl(1) + +=head1 AUTHOR + +Fabian Keil + +=cut -- 2.39.2