pythoncurlfile-uploadposter

How to upload file as array element with poster?


I am trying to repeat the following curl request:

curl -i -k -H "Content-Type: multipart/form-data" \
  -F "method=uploadphoto" \
  -F "version=1.2.3" \
  -F "format=json" \
  -F "image[0]=@/Users/user/Downloads/file.jpeg" \
  https://example.com/api

with poster usage:

from poster.encode import multipart_encode, MultipartParam

file_content = open('/Users/user/Downloads/file.jpeg', 'rb')
url = 'https://example.com/api'
headers = {
    'Content-Type': 'multipart/form-data'
}
parms = {
    'method': 'uploadphoto',
    'version': '1.2.3',
    'format': 'json'
}
mp_parms = []
for name, value in parms.items():
    mp_parms.append(MultipartParam(name, value, filetype='text/plain'))
file_photo = MultipartParam('image', file_content, 'file.jpeg', 'application/image')
mp_parms.append(file_photo)
parameters, headers = multipart_encode(mp_parms)
response = urlfetch.Fetch(url, payload=''.join(parameters), headers=headers, method=urlfetch.POST, deadline=60)

But looks like image field is passed as single field, when array should be passed. Usage of image[0] as name doesn't help.

How can I fix it?


Solution

  • I switched the example to use requests because I don't have the Google App SDK installed but the below code works.

    One important change was changing image to image[] to make so the remote end would recognize multiple inputs with the same name. Not sure if the same would apply with urlfetch but I had to add an extra \r\n after the raw post body.

    For illustration and testing, I had it upload a second image.

    First, the code:

    from poster.encode import multipart_encode, MultipartParam
    import requests
    from pprint import pprint
    
    file_content = open('/home/me/Pictures/admin.jpg', 'rb').read()
    file_content2 = open('/home/me/Pictures/ed.jpeg', 'rb').read()
    url = 'http://localhost/test.php'
    parms = {
        'method': 'uploadphoto',
        'version': '1.2.3',
        'format': 'json'
    }
    mp_parms = []
    for name, value in parms.items():
        mp_parms.append(MultipartParam(name, value, filetype='text/plain'))
    
    file_photo = MultipartParam('image[]', file_content, 'file.jpeg', 'image/jpg')
    mp_parms.append(file_photo)
    
    file_photo = MultipartParam('image[]', file_content2, 'test.jpeg', 'image/jpg')
    mp_parms.append(file_photo)
    
    parameters, headers = multipart_encode(mp_parms)
    
    pprint(headers)
    
    #response = urlfetch.Fetch(url, payload=''.join(parameters), headers=headers, method=urlfetch.POST, deadline=60)
    
    r = requests.post(url, data=''.join(parameters) + "\r\n", headers=headers)
    
    print r.text
    

    The test.php script it posts to contains <?php var_dump($_POST, $_FILES, $_SERVER); and shows output like:

    // The post data:
    array(3) {
      ["version"]=>
      string(5) "1.2.3"
      ["method"]=>
      string(11) "uploadphoto"
      ["format"]=>
      string(4) "json"
    }
    
    // The uploaded files (both of them as expected):
    array(1) {
      ["image"]=>
      array(5) {
        ["name"]=>
        array(2) {
          [0]=>
          string(9) "file.jpeg"
          [1]=>
          string(9) "test.jpeg"
        }
        ["type"]=>
        array(2) {
          [0]=>
          string(9) "image/jpg"
          [1]=>
          string(9) "image/jpg"
        }
        ["tmp_name"]=>
        array(2) {
          [0]=>
          string(14) "/tmp/phpnbpGGx"
          [1]=>
          string(14) "/tmp/php7TVcyL"
        }
        ["error"]=>
        array(2) {
          [0]=>
          int(0)
          [1]=>
          int(0)
        }
        ["size"]=>
        array(2) {
          [0]=>
          int(71066)
          [1]=>
          int(30450)
        }
      }
    }
    
    # some server vars:
    ["CONTENT_LENGTH"]=>
      string(6) "102186"
    ["CONTENT_TYPE"]=>
      string(62) "multipart/form-data; boundary=73772149e2ef4a5daf9b5eb18a5d73f5"
    

    It produced the following POST as grabbed in Wireshark:

    POST /test.php HTTP/1.1
    Host: localhost
    Connection: keep-alive
    Accept-Encoding: gzip, deflate
    Accept: */*
    User-Agent: python-requests/2.12.1
    Content-Length: 102186
    Content-Type: multipart/form-data; boundary=176473ffab3146b5bfffc6185ad9474a
    
    --176473ffab3146b5bfffc6185ad9474a
    Content-Disposition: form-data; name="version"
    Content-Type: text/plain
    
    1.2.3
    --176473ffab3146b5bfffc6185ad9474a
    Content-Disposition: form-data; name="method"
    Content-Type: text/plain
    
    uploadphoto
    --176473ffab3146b5bfffc6185ad9474a
    Content-Disposition: form-data; name="format"
    Content-Type: text/plain
    
    json
    --176473ffab3146b5bfffc6185ad9474a
    Content-Disposition: form-data; name="image[]"; filename="file.jpeg"
    Content-Type: image/jpg
    
    ......JFIF.....H.H.....C...................................
    ...snip...
    ..K5{......1....fh........k..n...
    --176473ffab3146b5bfffc6185ad9474a
    Content-Disposition: form-data; name="image[]"; filename="test.jpeg"
    Content-Type: image/jpg
    
    ......JFIF.............;CREATOR: gd-jpeg v1.0 (using IJG JPEG v80), quality = 90
    ...snip...
    6.......~.h.j......}29..b~.h.).2.!E..tU.........L....d...lv..+....f)Y...
    --176473ffab3146b5bfffc6185ad9474a--
    
    HTTP/1.1 200 OK
    

    I hope that helps. I'm not sure if the API will care, but you might want to base64 encode the image data and set the Content-Encoding to base64 to keep raw binary out of the post.

    If you're still stuck after integrating some of the changes let me know!