Reputation: 1221
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"'}
Upvotes: 0
Views: 1006
Reputation: 319
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.
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--
Upvotes: 1
Reputation: 11
This is what I use
import requests
import json
from requests.auth import HTTPDigestAuth
cam_user = 'root'
cam_pass = 'pass'
auth=HTTPDigestAuth(cam_user, cam_pass)
url = "http://192.168.30.14/axis-cgi/firmwaremanagement.cgi?"
payload = {
"apiVersion": "1.3",
"context": "abc",
"method": "upgrade"
}
files = {
'file': [('file.bin',open('P1468.bin', 'rb'),'application/octet-stream')],
'payload': json.dumps(payload)
}
fields={'payload': json.dumps(payload)}
response = requests.post(url, auth=auth,data=fields,files =[('file',open('P1468.bin', 'rb'))])
print(response.status_code)
print(response.text)
Upvotes: 1
Reputation: 1
Tested and working with axis 1455-LE and 1445-LE
def run_update(bin_path):
import requests
from requests.auth import HTTPDigestAuth
import os
json_data = b"""\
{
"apiVersion": "1.4",
"context": "abc",
"method": "upgrade"
}\
"""
files = {
'data': (None, json_data, 'application/json'),
'fileData': (os.path.basename(bin_path), open(bin_path, 'rb'), 'application/octet-stream')
}
destUrl = f"http://{IPADDRESS}/axis-cgi/firmwaremanagement.cgi"
resp = requests.post(destUrl, files=files, auth=HTTPDigestAuth(USERNAME, PASSWORD), proxies=proxy)
return resp
Upvotes: 0
Reputation: 16
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.
Upvotes: 0
Reputation: 304
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
Upvotes: 0