'How do I form this POST request with Python to upgrade Axis firmware?
I am attempting to upgrade the firmware on an Axis camera, and according to their documentation here, requires sending the request below. I am using the Python 3.8+ requests library for sending the request, but I am not sure how to prepare the headers, and more specifically the portion with the firmware file content.
The firmware file is a small ".bin" file downloaded from Axis' website.
How would I craft the below POST request in Python?
POST /axis-cgi/firmwaremanagement.cgi HTTP/1.1
Content-Type: multipart/form-data; boundary=<boundary>
Content-Length: <content length>
--<boundary>
Content-Type: application/json
{
"apiVersion": "1.0",
"context": "abc",
"method": "upgrade"
}
--<boundary>
Content-Type: application/octet-stream
<firmware file content>
--<boundary>--
EDIT 1: Throwing some code around and came up with this, but receiving a 400 response: text:'Expected a JSON object\r\n'
Non-working code. Can't seem to figure out how to format this:
data_payload = {
"apiVersion": "1.0",
"method": "upgrade"
}
# Get the firmware file contents into binary
full_fw_path = os.path.join(fw_path, fw_file)
with open(full_fw_path, 'rb') as f:
fw_file_as_binary = f.read()
firmware_file = {'file': fw_file_as_binary}
resp = session.post(camera_url, auth=HTTPDigestAuth(USERNAME, PASSWORD), data=data_payload, files=firmware_file)
Here are the request headers:
{'User-Agent': 'python-requests/2.27.1', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'Content-Length': '31064416', 'Content-Type': 'multipart/form-data; boundary=6a2ab8bae917229337a4108838818b84', 'Authorization': 'Digest username="root", realm="AXIS_ACCC8EA8EA12", nonce="EFKQCRzcBQA=81e1773e462d56aede148992d701c3d1d63c8d3d", uri="/axis-cgi/firmwaremanagement.cgi", response="388a610a821d53082dffc4c8a378c8d0", algorithm="MD5", qop="auth", nc=00000001, cnonce="717377924e9eed35"'}
Solution 1:[1]
Can't test it without more information but to solve your 'Expected a JSON object problem try changing
data_payload = {
"apiVersion": "1.0",
"method": "upgrade"
}
To
data_payload = json.dumps({
"apiVersion": "1.0",
"method": "upgrade"
})
Note: You'll have to import json at if you haven't already
Solution 2:[2]
Are you using requests? As the camera is expecting JSON, the request must be posted with the correct Content-Type. Could you try changing the data=data_payload to json=data_payload? More information: 1.
resp = session.post(camera_url, auth=HTTPDigestAuth(USERNAME, PASSWORD), json=data_payload, files=firmware_file)
The rest of the structure looks fine.
Solution 3:[3]
Sounds complicated, but you might be better off sending the request with sockets, something like this for python3, as sending customised multipart form data in requests isnt a strong point, if you do want to play with it you need to use the files={}, argument.
Edited the code to calculate Content-Length
import socket
content = b' --<boundary>\n'\
b'Content-Type: application/json\n'\
b'\n'\
b'{\n'\
b' "apiVersion": "1.0",\n'\
b' "context": "abc",\n'\
b' "method": "upgrade"\n'\
b'}\n'\
b'\n'\
b'--<boundary>\n'\
b'Content-Type: application/octet-stream\n\n'
content += open("firmware.bin", "rb").read()
content += b"\n\n--<boundary>--\n\n"
header = 'POST /axis-cgi/firmwaremanagement.cgi HTTP/1.1\n'\
'Content-Type: multipart/form-data; boundary=<boundary>\n'\
f'Content-Length: {len(content)}\n\n'.encode()
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 8000))
client.send(header + content)
print(client.recv(4096).decode())
If you really want to use requests then this is the closest I can get.
import requests
json_data = b"""\
{
"apiVersion": "1.0",
"context": "abc",
"method": "upgrade"
}\
"""
firmware = open("firmware.bin", 'rb').read()
requests.post("http://127.0.0.1:8000", files=(
(None, json_data),
(None, firmware),
))
this is the resulting request captured by netcat, note i just used a test file filled with random data
POST / HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: python-requests/2.27.1
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: keep-alive
Content-Length: 510
Content-Type: multipart/form-data; boundary=fd288c0032002f2b200f1a04cdc5df70
--fd288c0032002f2b200f1a04cdc5df70
Content-Disposition: form-data;
{
"apiVersion": "1.0",
"context": "abc",
"method": "upgrade"
}
--fd288c0032002f2b200f1a04cdc5df70
Content-Disposition: form-data;
H8Z#[xbfYow~
a?m?QtEf:9BMK?S{QrQ;1?,KcF=[?LtXRo?N@?2J+tj:X6?bn^oA0& v/~aLH3}wtdHAjg,"s]
?W.
--fd288c0032002f2b200f1a04cdc5df70--
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Fuledbyramen |
| Solution 2 | Mads Ruben |
| Solution 3 |
