6

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?

LA_
  • 19,823
  • 58
  • 172
  • 308
  • Does changing the `name` variable in `mp_parms.append(MultipartParam(name,` to `'image[0]` etc fix it? That's actually the fieid that creates the multipart form field name. – drew010 Jun 30 '17 at 19:26
  • @drew010 I've said in the question that I tried it. Looks like poster changes brackets to %xxx. – LA_ Jun 30 '17 at 19:41
  • what's the`urlfetch`is?and `[]`not in the escape character.so i don't think it's the reason. – obgnaw Jul 05 '17 at 14:43
  • @obgnaw, urlfetch - https://cloud.google.com/appengine/docs/standard/python/issue-requests, but it doesn't work even with standard `requests`. – LA_ Jul 05 '17 at 16:04
  • if `curl`can do,then `request`can do.it's just an ordinary HTTP POST.and `curl` can produce it,the `request`must can send the same POST. – obgnaw Jul 05 '17 at 16:08
  • @obgnaw, waiting for your answer then ;) – LA_ Jul 05 '17 at 17:24
  • can you use Wireshark?or i have to write a cgi to test code?i need the message created by `curl` . – obgnaw Jul 06 '17 at 05:53

1 Answers1

5

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!

drew010
  • 68,777
  • 11
  • 134
  • 162
  • Thanks. `requests` requires manual installation of many dependencies, GAE requires old version 2.3.0 (otherwise it doesn't work with integer value of content length and with built-in SSL library), but I managed to fix it and tried to test my code with `requests`. But result is the same as with `urlfetch`. It looks like the problem is with the field name. `image[]` is encoded to `image%5B%5D` and looks like 3rd party server is not able to recognize it (when your PHP server does). – LA_ Jul 01 '17 at 11:31
  • That's weird that you'd be encountering encoding on the field name where my wireshark raw capture shows it as the literal `image[]`. Are you able to perform a capture or is the API over https? – drew010 Jul 05 '17 at 19:04
  • Thanks for your help. The problem was with old version of poster module used ;). – LA_ Jul 11 '17 at 15:52