Source Matters
Source Matters

Reputation: 1221

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

Upvotes: 0

Views: 1006

Answers (5)

lonny
lonny

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

Don Mannion
Don Mannion

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

SATANS&#39;418
SATANS&#39;418

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

Mads Ruben
Mads Ruben

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

Fuledbyramen
Fuledbyramen

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

Related Questions