Project

General

Profile

Download (4.51 KB) Statistics
| Branch: | Tag: | Revision:

haketilo / test / haketilo_test / server.py @ aec5c9ae

1
# SPDX-License-Identifier: AGPL-3.0-or-later
2

    
3
"""
4
A modular "virtual network" proxy,
5
wrapping the classes in proxy_core.py
6
"""
7

    
8
# This file is part of Haketilo.
9
#
10
# Copyright (C) 2021 jahoti <jahoti@tilde.team>
11
# Copyright (C) 2021 Wojtek Kosior <koszko@koszko.org>
12
#
13
# This program is free software: you can redistribute it and/or modify
14
# it under the terms of the GNU Affero General Public License as
15
# published by the Free Software Foundation, either version 3 of the
16
# License, or (at your option) any later version.
17
#
18
# This program is distributed in the hope that it will be useful,
19
# but WITHOUT ANY WARRANTY; without even the implied warranty of
20
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21
# GNU Affero General Public License for more details.
22
#
23
# You should have received a copy of the GNU Affero General Public License
24
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
25
#
26
#
27
# I, Wojtek Kosior, thereby promise not to sue for violation of this
28
# file's license. Although I request that you do not make use of this code
29
# in a proprietary program, I am not going to enforce this in court.
30

    
31
from pathlib import Path
32
from urllib.parse import parse_qs
33
from threading import Thread
34
import traceback
35

    
36
from selenium.webdriver.common.utils import free_port
37

    
38
from .proxy_core import ProxyRequestHandler, ThreadingHTTPServer
39
from .misc_constants import *
40
from .world_wide_library import catalog as internet
41

    
42
class RequestHijacker(ProxyRequestHandler):
43
    def handle_request(self, req_body):
44
        path_components = self.path.split('?', maxsplit=1)
45
        path = path_components[0]
46
        try:
47
            # Response format: (status_code, headers (dict. of strings),
48
            #       body as bytes or filename containing body as string)
49
            if path in internet:
50
                info = internet[path]
51
                if type(info) is tuple:
52
                    status_code, headers, body_file = info
53
                    resp_body = b''
54
                    if body_file is not None:
55
                        if 'Content-Type' not in headers:
56
                            ext = body_file.suffix[1:]
57
                            if ext and ext in mime_types:
58
                                headers['Content-Type'] = mime_types[ext]
59

    
60
                        with open(body_file, mode='rb') as f:
61
                            resp_body = f.read()
62
                else:
63
                    # A function to evaluate to get the response
64
                    get_params, post_params = {}, {}
65
                    if len(path_components) == 2:
66
                        get_params = parse_qs(path_components[1])
67

    
68
                    # Parse POST parameters; currently only supports
69
                    # application/x-www-form-urlencoded
70
                    if req_body:
71
                        post_params = parse_qs(req_body.encode())
72

    
73
                    status_code, headers, resp_body = info(self.command, get_params, post_params)
74
                    if type(resp_body) == str:
75
                        resp_body = resp_body.encode()
76

    
77
                if type(status_code) != int or status_code <= 0:
78
                    raise Exception('Invalid status code %r' % status_code)
79

    
80
                for header, header_value in headers.items():
81
                    if type(header) != str:
82
                        raise Exception('Invalid header key %r' % header)
83

    
84
                    elif type(header_value) != str:
85
                        raise Exception('Invalid header value %r' % header_value)
86
            else:
87
                status_code, headers = 404, {'Content-Type': 'text/plain'}
88
                resp_body = b'Handler for this URL not found.'
89

    
90
        except Exception:
91
            status_code = 500
92
            headers     = {'Content-Type': 'text/plain'}
93
            resp_body   = b'Internal Error:\n' + traceback.format_exc().encode()
94

    
95
        headers['Content-Length'] = str(len(resp_body))
96
        self.send_response(status_code)
97
        for header, header_value in headers.items():
98
            self.send_header(header, header_value)
99

    
100
        self.end_headers()
101
        if resp_body:
102
            self.wfile.write(resp_body)
103

    
104
def do_an_internet(certdir=default_cert_dir, port=None):
105
    """Start up the proxy/server"""
106
    if port is None:
107
        port = free_port()
108

    
109
    class RequestHijackerWithCertdir(RequestHijacker):
110
        def __init__(self, *args, **kwargs):
111
            super().__init__(*args, certdir=certdir, **kwargs)
112

    
113
    httpd = ThreadingHTTPServer(('', port), RequestHijackerWithCertdir)
114
    Thread(target=httpd.serve_forever).start()
115

    
116
    return httpd
(9-9/11)