0

I'm currently dealing with the implementation of a Anel Power Outlet manager. The Anel power outlet supports the following requests: https://forum.anel.eu/viewtopic.php?f=52&t=888&sid=39081b8e472aaae7ffcec4cd3fb41e83

However, the special form of the http request specifying the concatenated userpassword comma separated in the URL makes me headache, because it appears to be unsupported by the python requests package.

Therefore here my question:
How do I sent a http request in either one of the forms

  1. http://IP?param=value,userpassword
  2. http://IP?param=value&userpassword or
  3. http://IP?param&userpassword

e.g.

  1. http://192.168.0.244?Stat&userpassword
  2. http://192.168.0.244?Sw=0xc0&userpassword

using the python requests package? I can sent the request manually using the browser and get the appropriate response. However, I cannot send it programmatically.

I'm struggling with the syntax as the requests documentation tells nothing about unnamed parameters (https://requests.readthedocs.io/en/latest/user/quickstart/#make-a-request).

It tells me about passing parameters as key-value pairs using a dict or a tuple or about passing parameters in the body of the request or dealing with response objects. But all this is not my problem. It's just about sending the "userpassword" string, which can either be passed as concatenated cleartext or the same base64 encoded separated by a comma or ampersand after the named parameter.

req = requests.get("IP", {"Param": "Value"}, "userpassword")
Traceback (most recent call last):
  File "/home/user/pycharm-231.7864.77/plugins/python/helpers/pydev/pydevconsole.py", line 364, in runcode
    coro = func()
  File "<input>", line 1, in <module>
TypeError: get() takes from 1 to 2 positional arguments but 3 were given
req = requests.post("http://IP", {"Param": "Value"}, "userpassword")
req
<Response [401]>
req = requests.post("http://IP", {"Param": "Value", None: "userpassword"})
req
<Response [401]>

However, assembling the URL manually works.

req = requests.get("http://IP?Param=Value,userpassword", allow_redirects=False)
req.content
b'304 Redirect: /u_res.htm\r\n'

req2 = requests.get("http://IP/u_res.htm")
req2.text
"blablablabla"

Unfortunately, if I use an abstraction layer I would not like my code to depend on the exact details of the request format.

7
  • Please check this url. this might help you. stackoverflow.com/questions/23576970/… Commented Mar 16, 2023 at 10:19
  • why not requests.post("http://IP", {"Param": "Value,userpassword"} It will be considered as a coma separated value Commented Mar 16, 2023 at 10:26
  • I can try that. However, I was expecting the requests package to relieve me from such low-level code burdens as string concatenation. ;) Commented Mar 16, 2023 at 10:33
  • The auth= parameter is not supported by the device I'm dealing with. Commented Mar 16, 2023 at 11:27
  • It sounds like Anel isn't parsing the query in the standard way. So far as l can tell commas were never used to separate parameters in query strings. Requests, and similar packages, will use ampersands. (Apparently semicolons were recommended for a while by W3C, but no longer). In your code, if you check the output of req.url, you'll see what URL was requested. Commented Mar 16, 2023 at 12:05

2 Answers 2

0

you can create a function and replace the params.

For example:

def request_function(host, param, value, user, passwd):
    try:
        host = f'http://{host}?{param}={value},{user}{password}'
        response = requests.get(host)
        return [True, response.status_code, response.text]
    except:
        try:
            host = f'http://{host}?{param}={value}&{user}{password}'
            response = requests.get(host)
            return [True, response.status_code, response.text]
        except:
            try:
                host = f'http://{host}?{param}&{user}{password}'
                response = requests.get(host)
                return [True, response.status_code, response.text]
            except:
                return [False, 0, 'No works']

Depend which works , you can delete others try block.

Sign up to request clarification or add additional context in comments.

1 Comment

That's probably the only possible solution. Although not really satisfactory. At least something I can work with until I find something better.
0

It needed a little bit fiddling around, but now I found a solution by myself. The clue was to recognize that a request can be prepared and that the so called PreparedRequest is fully mutable as stated in the manual https://requests.readthedocs.io/en/latest/api/#requests.PreparedRequest (followed by a usage example):

class requests.PreparedRequest

The fully mutable PreparedRequest object, containing the exact bytes that will be sent to the server. [...]

So, when they claim that it's fully mutable I should be able to condition the PreparedRequest for my needs.

This is shown in the following code, which makes two different requests to the anel outlet. It first generates a Request object and then prepares it, resulting in a PreparatedRequest instance. In the prepared request the url is modified stripping some placeholders for positional only parameters. Then the modified request is sent to the outlet.

The additional code needs effectively 7 LLOCs, which should be reasonable in most cases. The rest is just testcode and code for debugging output. The given user and pass is the factory default in this example and would need to be replaced by the actual user and pass.

import requests
from requests import session, Request


BASE_URL = "http://192.168.0.245"


def request_hack(url: str = BASE_URL, params: dict = None) -> requests.PreparedRequest:
    DUMMY = "dummy"

    if isinstance(params, dict):
        # The requests lib would normally ignore params mapping to None,
        # but if we replace the None values using some DUMMY the resulting
        # url can be conditioned to strip the superfluous assignments in
        # the positional only parameters
        params = {k: v or DUMMY for k, v in params.items()}

    # The request cannot be sent directly as the URL syntax for the anel outlets is not supported by the requests lib
    # Therefore, we first set-up a PreparedRequest and then modify the url to our needs
    req = Request("GET", url, params=params).prepare()
    print(f"Unconditioned request {req.url}")
    # Remove the DUMMY values and the preceeding equal signs by replacing them with empty strings
    req.url = req.url.replace(f"={DUMMY}", "")
    print(f"Conditioned request {req.url}")

    return req


if __name__ == "__main__":
    the_session = session()


    """ Perform a Stat request to the anel outlet """
    req = request_hack(params={"Stat": None, "adminanel": None})
    res = the_session.send(req)
    print(res.text)

    """ Perform a Switch request to the anel outlet """
    req = request_hack(params={"Sw": "0x55", "adminanel": None})
    res = the_session.send(req)
    print(res.text)

The sample code would output something like:

Unconditioned request http://192.168.0.245/?Stat=dummy&adminanel=dummy
Conditioned request http://192.168.0.245/?Stat&adminanel
NET-CONTROL    ;192.168.0.245;NET - Power Control;<font color=red><span style='font-size:7pt'>Kein Zeit Server. Die Uhr ist nicht gestellt: Timer inaktiv.</span></font>;1946;P;4.6 DE;24.5;<br>Nr. 1;1;0;Nr. 2;0;0;Nr. 3;1;0;Nr. 4;0;0;Nr. 5;1;0;Nr. 6;0;0;Nr. 7;1;0;Nr. 8;0;0;
Unconditioned request http://192.168.0.245/?Sw=0x55&adminanel=dummy
Conditioned request http://192.168.0.245/?Sw=0x55&adminanel
NET-CONTROL    ;192.168.0.245;NET - Power Control;<font color=red><span style='font-size:7pt'>Kein Zeit Server. Die Uhr ist nicht gestellt: Timer inaktiv.</span></font>;1946;P;4.6 DE;24.5;<br>Nr. 1;1;0;Nr. 2;0;0;Nr. 3;1;0;Nr. 4;0;0;Nr. 5;1;0;Nr. 6;0;0;Nr. 7;1;0;Nr. 8;0;0;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.