Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
30 changes: 30 additions & 0 deletions test/modules/rewrite/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import logging
import os
import sys

import pytest

from .env import RewriteTestEnv

sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))


def pytest_report_header(config, start_path):
env = RewriteTestEnv()
return f"rewrite [apache: {env.get_httpd_version()}, mpm: {env.mpm_module}, {env.prefix}]"


@pytest.fixture(scope="package")
def env(pytestconfig) -> RewriteTestEnv:
logging.getLogger('').setLevel(level=logging.INFO)
env = RewriteTestEnv(pytestconfig=pytestconfig)
env.setup_httpd()
env.apache_access_log_clear()
env.httpd_error_log.clear_log()
return env


@pytest.fixture(autouse=True, scope="package")
def _stop_package_scope(env):
yield
assert env.apache_stop() == 0
25 changes: 25 additions & 0 deletions test/modules/rewrite/env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import inspect
import logging
import os

from pyhttpd.env import HttpdTestEnv, HttpdTestSetup

log = logging.getLogger(__name__)


class RewriteTestSetup(HttpdTestSetup):

def __init__(self, env: 'HttpdTestEnv'):
super().__init__(env=env)
self.add_source_dir(os.path.dirname(inspect.getfile(RewriteTestSetup)))
self.add_modules(["rewrite", "proxy", "proxy_http"])


class RewriteTestEnv(HttpdTestEnv):

def __init__(self, pytestconfig=None):
super().__init__(pytestconfig=pytestconfig)
self.add_httpd_log_modules(["rewrite", "core"])

def setup_httpd(self, setup: HttpdTestSetup = None):
super().setup_httpd(setup=RewriteTestSetup(env=self))
64 changes: 64 additions & 0 deletions test/modules/rewrite/test_001_redirect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import pytest

from pyhttpd.conf import HttpdConf


class TestRewriteRedirect:

@pytest.fixture(autouse=True, scope='class')
def _class_scope(self, env):
conf = HttpdConf(env)
# VirtualHost that strips "www." prefix and redirects
conf.start_vhost(
domains=[f"www.rewrite.{env.http_tld}"],
port=env.http_port,
doc_root="htdocs",
with_ssl=False,
)
conf.add([
"RewriteEngine On",
r"RewriteCond %{HTTP_HOST} ^www\.(.+)",
r"RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]",
])
conf.end_vhost()
# VirtualHost that prepends "www." and redirects
conf.start_vhost(
domains=[f"rewrite.{env.http_tld}"],
port=env.http_port,
doc_root="htdocs",
with_ssl=False,
)
conf.add([
"RewriteEngine On",
r"RewriteCond %{HTTP_HOST} !^www\.",
r"RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]",
])
conf.end_vhost()
conf.install()
assert env.apache_restart() == 0
yield

# Redirect www.domain -> non-www domain
def test_rewrite_001_01(self, env):
url = f"http://www.rewrite.{env.http_tld}:{env.http_port}/"
r = env.curl_raw(url, options=[])
assert r.exit_code == 0, f"curl failed: {r}"
assert r.response["status"] == 301
location = r.response["header"]["location"]
expected = f"http://rewrite.{env.http_tld}:{env.http_port}/"
assert location == expected, (
f"expected redirect to '{expected}', got '{location}'"
)

# Redirect non-www domain -> www.domain
def test_rewrite_001_02(self, env):
url = f"http://rewrite.{env.http_tld}:{env.http_port}/"
r = env.curl_raw(url, options=[])
assert r.exit_code == 0, f"curl failed: {r}"
assert r.response["status"] == 301
location = r.response["header"]["location"]
expected = f"http://www.rewrite.{env.http_tld}:{env.http_port}/"
assert location == expected, (
f"expected redirect to '{expected}', "
f"got '{location}'"
)
79 changes: 79 additions & 0 deletions test/modules/rewrite/test_002_open_redirect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import os
import re

import pytest

from pyhttpd.conf import HttpdConf


class TestRewriteOpenRedirect:
@pytest.fixture(autouse=True, scope='class')
def _class_scope(self, env):
doc_dir = os.path.join(env.server_dir, "htdocs", "test1")
rhts_dir = os.path.join(doc_dir, "rhts")
os.makedirs(rhts_dir, exist_ok=True)
with open(os.path.join(rhts_dir, "index.html"), "w") as f:
f.write("test content\n")
conf = HttpdConf(env, extras={
f"test1.{env.http_tld}": f"""
<Directory "{rhts_dir}">
RewriteEngine On
RewriteRule (.*)$ https://localhost$1
</Directory>
LogLevel rewrite:trace6
""",
})
conf.add_vhost_test1()
conf.install()
assert env.apache_restart() == 0

def test_rewrite_002_01(self, env):
url = env.mkurl("http", "test1", "/rhts/%0a.evilwebsite.com")
r = env.curl_raw(url, options=['-v', '--max-redirs', '0'])
if r.response is not None:
assert r.response["status"] != 301 and \
r.response["status"] != 302, \
f"Should not redirect, got {r.response['status']}"
if "location" in r.response.get("header", {}):
loc = r.response["header"]["location"]
assert "evilwebsite" not in loc, \
f"Redirect target contains attacker domain: {loc}"
env.httpd_error_log.ignore_recent(
matches=[r'.*rewrite:error.*', r'.*:error\].*',
r'.*:warn\].*'])


class TestRewriteEscapeSequences:

@pytest.fixture(autouse=True, scope='class')
def _class_scope(self, env):
doc_dir = os.path.join(env.server_dir, "htdocs", "test1")
rhts_dir = os.path.join(doc_dir, "rhts2")
os.makedirs(rhts_dir, exist_ok=True)
with open(os.path.join(rhts_dir, "index.html"), "w") as f:
f.write("test content\n")
conf = HttpdConf(env, extras={
f"test1.{env.http_tld}": f"""
<Directory "{rhts_dir}">
RewriteEngine On
RewriteRule "^path(.*)" "http://localhost:{env.http_port}/\\ttest/"
</Directory>
LogLevel rewrite:trace8
""",
})
conf.add_vhost_test1()
conf.install()
assert env.apache_restart() == 0

def test_rewrite_002_02(self, env):
start_pos = env.httpd_error_log.current_pos()
url = env.mkurl("http", "test1", "/rhts2/path")
env.curl_raw(url, options=['-v', '--max-redirs', '0'])
found = env.httpd_error_log.wait_for(
re.compile(r'.*escaping.*for redirect'),
start_pos, timeout=5)
env.httpd_error_log.ignore_recent(
matches=[r'.*rewrite:error.*', r'.*:error\].*',
r'.*:warn\].*', r'.*:trace\d\].*'])
assert found, \
"Log should show 'escaping ... for redirect' for tab in target"