简体   繁体   中英

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"'}

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

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--

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM