f513feda2d90e21af37ca0d79674b69040e1c5c2
[pyi2ncommon] / src / web_interface.py
1 # The software in this package is distributed under the GNU General
2 # Public License version 2 (with a special exception described below).
3 #
4 # A copy of GNU General Public License (GPL) is included in this distribution,
5 # in the file COPYING.GPL.
6 #
7 # As a special exception, if other files instantiate templates or use macros
8 # or inline functions from this file, or you compile this file and link it
9 # with other works to produce a work based on this file, this file
10 # does not by itself cause the resulting work to be covered
11 # by the GNU General Public License.
12 #
13 # However the source code for this file must still be made available
14 # in accordance with section (3) of the GNU General Public License.
15 #
16 # This exception does not invalidate any other reasons why a work based
17 # on this file might be covered by the GNU General Public License.
18 #
19 # Copyright (c) 2016-2018 Intra2net AG <info@intra2net.com>
20
21 """
22
23 SUMMARY
24 ------------------------------------------------------
25 Utility for HTTP based interaction with the arnied web page.
26
27 Copyright: Intra2net AG
28
29
30 INTERFACE
31 ------------------------------------------------------
32
33 """
34
35 import re
36 import ssl
37 import http.client as client
38 import urllib.parse as parse
39 import socket
40 import logging
41
42 log = logging.getLogger('pyi2ncommon.web_interface')
43
44
45 #: FQDN of local machine
46 LOCALHOST = socket.gethostname()
47
48
49 def find_in_form(regex, form="status", escape=False, check_certs=True):
50     """
51     Find a regex in given I2N web page form.
52
53     :param str regex: regular expression to find
54     :param str form: form name to open
55     :param bool escape: whether to escape the regex
56     :param check_certs: forwarded to :py:func:`web_page_request`, see doc there
57     :returns: whether the regex was found
58     :rtype: bool
59     """
60     data = web_page_request(method="GET", url="/arnie?form=" + form,
61                             check_certs=check_certs)
62     if escape:
63         regex = re.escape(regex)
64     if re.search(regex, data):
65         return True
66     else:
67         log.debug("'%s' could not be found in:\n%s", regex, data)
68         return False
69
70
71 def web_page_request(method="GET", url="/", body=None, check_certs=True):
72     """
73     Send an HTTPS request and return any response data.
74
75     SSL certificates are checked against a default set of certificates
76     installed on you system. This can be disabled (not recommended, security
77     implications!) by setting `check_certs` to `False`. To allow a secure
78     connection to host with e.g. a self-signed certificate, the caller can
79     load this certificate by specifying `check_certs=/path/to/cert.pem`.
80     (see also: :py:meth:`ssl.SSLContext.load_verify_locations`). Note that the
81     certificate has to be issued for the same server name that we try to
82     access, i.e. :py:data:`LOCALHOST`.
83
84     :param str method: GET or POST method for the request
85     :param str url: url location within the remote host
86     :param body: dictionary to be parsed and added to the url
87     :type body: {str, str} or None
88     :param check_certs: Whether or not to check ssl certificates for connection
89                         or file name to a certificate file
90     :type check_certs: bool or str
91     :returns: data from the response if any
92     :rtype: str
93     """
94     body = parse.urlencode(body) if body is not None else ""
95     headers = {"Content-Type": "application/x-www-form-urlencoded",
96                "Accept": "text/plain"}
97
98     if isinstance(check_certs, str):
99         context = ssl.create_default_context(cafile=check_certs)
100     elif check_certs:
101         context = ssl.create_default_context()
102     else:
103         # disable certificate checks
104         context = ssl._create_unverified_context()
105     conn = client.HTTPSConnection(LOCALHOST, context=context)
106     conn.request(method, url, body, headers)
107     resp = conn.getresponse()
108     logging.info("Request status %s and response %s",
109                  resp.status, resp.reason)
110     if resp.status != 200:
111         raise client.HTTPException("POST request failed.")
112     data = resp.read().decode()
113     conn.close()
114
115     return data