POST Policies in Tigris

Hi,

I’m trying to implement post policies to enforce a content-length limit, but receive 405 responses when I upload to any url I can imagine being correct (I’m using custom domains but have tried tigris.dev, with/out object key, etc). Is POST supported yet?

POST is supported. Have a look at the documentation at Browser-Based Uploads Using HTTP POST | Tigris Object Storage Documentation

1 Like

Oh thanks for the quick reply :pray:, I managed to miss that page. There was an issue with my URL after all.

I banged into another wall, though. I’m using the Dart dio client to construct form data, and I’m getting the error The body of your POST request is not well-formed multipart/form-data

Not clear to me what is wrong because I’m just following the library examples and the form seems to be parsed fine in the browser - here’s an example source, if anything happens to stand out.

Hi @Wes,

We will look into this meanwhile, Is it possible to provide us with a replay-able cURL request command?

Thanks!
Jigar

@jmj Yah for sure, here’s curl for a policy example without a pre-defined object key that fails in browser. It seems like the server is allowing partial to full upload before throwing an error.

curl -X POST \
  -H "Content-Type: multipart/form-data" \
  -F "bucket=public.fffs.absurd.chat" \
  -F "x-amz-date=20240428T203306Z" \
  -F "x-amz-algorithm=AWS4-HMAC-SHA256" \
  -F "x-amz-credential=tid_EuMhLzbqmFAYVsrckIzvKvOxZZkEmYSVberVGIakQqaFwAQHqW/20240428/auto/s3/aws4_request" \
  -F "policy=eyJjb25kaXRpb25zIjpbWyJlcSIsIiRidWNrZXQiLCJwdWJsaWMuZmZmcy5hYnN1cmQuY2hhdCJdLFsiY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMjA5NzE1MjAwXSxbImVxIiwiJHgtYW16LWRhdGUiLCIyMDI0MDQyOFQyMDMzMDZaIl0sWyJlcSIsIiR4LWFtei1hbGdvcml0aG0iLCJBV1M0LUhNQUMtU0hBMjU2Il0sWyJlcSIsIiR4LWFtei1jcmVkZW50aWFsIiwidGlkX0V1TWhMemJxbUZBWVZzcmNrSXp2S3ZPeFpaa0VtWVNWYmVyVkdJYWtRcWFGd0FRSHFXLzIwMjQwNDI4L2F1dG8vczMvYXdzNF9yZXF1ZXN0Il1dLCJleHBpcmF0aW9uIjoiMjAyNC0wNC0yOFQyMDozMzowNi43NTU5NjRaIn0=" \
  -F "x-amz-signature=ccebc0c8ac157cd7b7bc0e3baba961540dda597c86a57a7fa6e74673eda31bff" \
  -F "file=@/path/to/your/image.png;type=image/png" \
  https://fly.storage.tigris.dev/public.fffs.absurd.chat

Hi Wes,

I couldn’t try your exact cURL request because it has expired. I tried replicating the same policy for my bucket post-policy-test, policy looks like this

{"conditions":[["eq","$bucket","post-policy-test"],["content-length-range",0,209715200],["eq","$x-amz-date","20240505T203306Z"],["eq","$x-amz-algorithm","AWS4-HMAC-SHA256"],["eq","$x-amz-credential","tid_NhIvhlFpTWpfZDryFcfzVwglbgFbJZoxNWerSykoyUyuftmhXQ/20240505/auto/s3/aws4_request"]],"expiration":"2024-05-05T20:33:06.755964Z"}

and my cURL looks like this

curl --location --request POST 'https://fly.storage.tigris.dev/post-policy-test' \
--header 'Content-Type: multipart/form-data' \
--form 'bucket="post-policy-test"' \
--form 'x-amz-date="20240505T203306Z"' \
--form 'x-amz-algorithm="AWS4-HMAC-SHA256"' \
--form 'x-amz-credential="tid_NhIvhlFpTWpfZDryFcfzVwglbgFbJZoxNWerSykoyUyuftmhXQ/20240505/auto/s3/aws4_request"' \
--form 'policy="eyJjb25kaXRpb25zIjpbWyJlcSIsIiRidWNrZXQiLCJwb3N0LXBvbGljeS10ZXN0Il0sWyJjb250ZW50LWxlbmd0aC1yYW5nZSIsMCwyMDk3MTUyMDBdLFsiZXEiLCIkeC1hbXotZGF0ZSIsIjIwMjQwNTA1VDIwMzMwNloiXSxbImVxIiwiJHgtYW16LWFsZ29yaXRobSIsIkFXUzQtSE1BQy1TSEEyNTYiXSxbImVxIiwiJHgtYW16LWNyZWRlbnRpYWwiLCJ0aWRfTmhJdmhsRnBUV3BmWkRyeUZjZnpWd2dsYmdGYkpab3hOV2VyU3lrb3lVeXVmdG1oWFEvMjAyNDA1MDUvYXV0by9zMy9hd3M0X3JlcXVlc3QiXV0sImV4cGlyYXRpb24iOiIyMDI0LTA1LTA1VDIwOjMzOjA2Ljc1NTk2NFoifQ=="' \
--form 'x-amz-signature="<removed>"' \
--form 'file=@"/Users/jmj/Downloads/SampleJPGImage_30mbmb.jpg"' \
--form 'key="tiger.png"'

And I tested it with image ranging from size < 1mb, 5mb, 30mb and all uploads went through fine. One difference I saw is your upload missed key field. Can you try adding that and see if that fixes it?

Thanks!
Jigar

Right, I just removed the key field from the policy to allow arbitrary file choice for replays. I just did some testing - without adding a key back I get a PostPolicyInvalidKeyName error through curl (as you might expect), but a MalformedPostRequest error in the Flutter web app for the same form data.

It looks like the server struggles to parse the dio format. Since browser has no issue with the form and dio is a hugely popular ecosystem library, I suspect the request itself is up to spec? I tried removing the file just to see if that had an effect, but the server just sends me through a long redirect loop.

Hi Wes,

Sorry for delayed back&forth

So to summarize

  1. key is not present in policy but is present in the uploading HTTP request and that worked fine while uploading it from browser.
  2. the similar request fails when you invoke upload from dart dio client
  3. If you leave the file field empty in the form upload server issues the infinite redirects.

If this is correct. Can you please send a reproducible dart dio code? I will try uploading small file on your bucket or I can use my bucket with that code.

For 3. I will fix the server.

Thanks!
Jigar

Don’t mind at all. Excited to use the tech :slight_smile:

All of that sounds right to me. Here’s a minimal Flutter app using the same logic and my services. I included preconfigured launch settings for VSCode. Should be trivial to run on web.

I’ve also updated the policy to include object key again.

Thank you very much Wes for working through with us. I will try with this dio code and provide an update.

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

Hi, Wes,

Sorry for the delayed response.

I was able to run the code from your example, thanks for sharing.

Looks like your API at /generate-file-urls returns presigned URL and not a POST policy form or signature.

In this case if you just change _dioClient.post to _dioClient.put and set 'Content-Type': 'application/octet-stream' or to the appropriate MIME type of the file you are uploading, then request should succeed.

Let me know if that works for you. You can always write us to help@tigrisdata.com and we can provide you with more details.


Yevgeniy

@Yevgeniy I changed to PUT this weekend so I could continue developing and testing after not hearing back. I’m reverting to POST now for your testing pleasure. 100% need to figure out how to get POST working - as I mentioned in the initial post, I need the ability to control for content-length in the url.

Hi Wes,

Your API that returns the policy JSON has a minor bug in date formatting.

For example

curl --location --request PUT 'https://fffs.absurd.chat/generate-file-urls' \
--header 'Content-Type: application/json' \
--data-raw '{
    "bucket": "public.fffs.absurd.chat",
    "objectKey": "guests/diotest/1715231651044"
}'

returns base64 encoded policy

eyJjb25kaXRpb25zIjpbWyJlcSIsIiRrZXkiLCJndWVzdHMvZGlvdGVzdC8xNzE1MjMxNjUxMDQ0Il0sWyJlcSIsIiRidWNrZXQiLCJwdWJsaWMuZmZmcy5hYnN1cmQuY2hhdCJdLFsiY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMjA5NzE1MjAwXSxbImVxIiwiJHgtYW16LWRhdGUiLCIyMDI0MDUwOVQwNTE0MTFaIl0sWyJlcSIsIiR4LWFtei1hbGdvcml0aG0iLCJBV1M0LUhNQUMtU0hBMjU2Il0sWyJlcSIsIiR4LWFtei1jcmVkZW50aWFsIiwidGlkX0V1TWhMemJxbUZBWVZzcmNrSXp2S3ZPeFpaa0VtWVNWYmVyVkdJYWtRcWFGd0FRSHFXLzIwMjQwNTA5L2F1dG8vczMvYXdzNF9yZXF1ZXN0Il1dLCJleHBpcmF0aW9uIjoiMjAyNC0wNS0xMVQwNToxNDoxMS44NzE4OTIifQ==

Which renders to (formatted) JSON as follows

{
  "conditions": [
    [
      "eq",
      "$key",
      "guests/diotest/1715231651044"
    ],
    [
      "eq",
      "$bucket",
      "public.fffs.absurd.chat"
    ],
    [
      "content-length-range",
      0,
      209715200
    ],
    [
      "eq",
      "$x-amz-date",
      "20240509T051411Z"
    ],
    [
      "eq",
      "$x-amz-algorithm",
      "AWS4-HMAC-SHA256"
    ],
    [
      "eq",
      "$x-amz-credential",
      "tid_EuMhLzbqmFAYVsrckIzvKvOxZZkEmYSVberVGIakQqaFwAQHqW/20240509/auto/s3/aws4_request"
    ]
  ],
  "expiration": "2024-05-11T05:14:11.871892"
}

The expiration field value needs fixing by converting it to

2024-05-11T05:14:11.871892Z

Note that you need to recompute signature with this change. With these two changes.

The base64 encoded policy looks like this

eyJjb25kaXRpb25zIjpbWyJlcSIsIiRrZXkiLCJndWVzdHMvZGlvdGVzdC8xNzE1MjMxNjUxMDQ0Il0sWyJlcSIsIiRidWNrZXQiLCJwdWJsaWMuZmZmcy5hYnN1cmQuY2hhdCJdLFsiY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMjA5NzE1MjAwXSxbImVxIiwiJHgtYW16LWRhdGUiLCIyMDI0MDUwOVQwNTE0MTFaIl0sWyJlcSIsIiR4LWFtei1hbGdvcml0aG0iLCJBV1M0LUhNQUMtU0hBMjU2Il0sWyJlcSIsIiR4LWFtei1jcmVkZW50aWFsIiwidGlkX0V1TWhMemJxbUZBWVZzcmNrSXp2S3ZPeFpaa0VtWVNWYmVyVkdJYWtRcWFGd0FRSHFXLzIwMjQwNTA5L2F1dG8vczMvYXdzNF9yZXF1ZXN0Il1dLCJleHBpcmF0aW9uIjoiMjAyNC0wNS0xMVQwNToxNDoxMS44NzE4OTJaIn0=

and computed signature for your credentials, using these I created HTML form. If you need me to pass the sensitive information such as signature to you, please write us at help@tigrisdata.com or you can compute signature yourself.

<html>
  <head>

    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

  </head>
  <body>

  <form action="https://fly.storage.tigris.dev/public.fffs.absurd.chat"
        method="post"
        enctype="multipart/form-data">
    Key to upload:
    <input type="input"  name="key" value="guests/diotest/1715231651044" /><br />
    <input type="hidden" name="success_action_redirect" value="https://your-website.com/success.html" />
    Content-Type:
    <input type="input"  name="Content-Type" value="image/jpeg" /><br />

    <input type="text"   name="X-Amz-Credential" value="tid_EuMhLzbqmFAYVsrckIzvKvOxZZkEmYSVberVGIakQqaFwAQHqW/20240509/auto/s3/aws4_request" />
    <input type="text"   name="X-Amz-Algorithm" value="AWS4-HMAC-SHA256" />
    <input type="text"   name="X-Amz-Date" value="20240509T051411Z" />

    Tags for File:
    <input type="hidden" name="Policy" value='eyJjb25kaXRpb25zIjpbWyJlcSIsIiRrZXkiLCJndWVzdHMvZGlvdGVzdC8xNzE1MjMxNjUxMDQ0Il0sWyJlcSIsIiRidWNrZXQiLCJwdWJsaWMuZmZmcy5hYnN1cmQuY2hhdCJdLFsiY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMjA5NzE1MjAwXSxbImVxIiwiJHgtYW16LWRhdGUiLCIyMDI0MDUwOVQwNTE0MTFaIl0sWyJlcSIsIiR4LWFtei1hbGdvcml0aG0iLCJBV1M0LUhNQUMtU0hBMjU2Il0sWyJlcSIsIiR4LWFtei1jcmVkZW50aWFsIiwidGlkX0V1TWhMemJxbUZBWVZzcmNrSXp2S3ZPeFpaa0VtWVNWYmVyVkdJYWtRcWFGd0FRSHFXLzIwMjQwNTA5L2F1dG8vczMvYXdzNF9yZXF1ZXN0Il1dLCJleHBpcmF0aW9uIjoiMjAyNC0wNS0xMVQwNToxNDoxMS44NzE4OTJaIn0=' />
    <input type="hidden" name="X-Amz-Signature" value="HIDDEN" />
    File:
    <input type="file"   name="file" /> <br />
    <input type="submit" name="submit" value="Upload to Amazon S3" />
  </form>

</html>

I uploaded https://fly.storage.tigris.dev/public.fffs.absurd.chat/guests/diotest/1715231651044 this using this form to your bucket.

Please let us know if this resolves your issue or if you have additional questions.

Thanks
Jigar

1 Like

@jmj

Ooh thanks, that’s definitely one issue! Fixed in my service, but it doesn’t solve the existing malformed POST error unfortunately. This policy for example still fails under the dio representation:

eyJjb25kaXRpb25zIjpbWyJlcSIsIiRrZXkiLCJndWVzdHMvaW1hZ2VzL1VwbG9hZEZpbGVfSW1hZ2VfMjAyNC0wNS0wOVQyMzozMjoyNS4xODNaX2N5Ym9yZzMucG5nIl0sWyJlcSIsIiRidWNrZXQiLCJwdWJsaWMuZmZmcy5hYnN1cmQuY2hhdCJdLFsiY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMjA5NzE1MjAwXSxbImVxIiwiJHgtYW16LWRhdGUiLCIyMDI0MDUwOVQyMzMyMjZaIl0sWyJlcSIsIiR4LWFtei1hbGdvcml0aG0iLCJBV1M0LUhNQUMtU0hBMjU2Il0sWyJlcSIsIiR4LWFtei1jcmVkZW50aWFsIiwidGlkX0V1TWhMemJxbUZBWVZzcmNrSXp2S3ZPeFpaa0VtWVNWYmVyVkdJYWtRcWFGd0FRSHFXLzIwMjQwNTA5L2F1dG8vczMvYXdzNF9yZXF1ZXN0Il1dLCJleHBpcmF0aW9uIjoiMjAyNC0wNS0xMVQyMzozMjoyNi4wNjUzMDlaIn0=

Hi Wes,

I was able to finally setup your application and run the file upload via it. I ran your application as MacOS (desktop) app. Made following changes and I uploaded couple of images to your bucket

Example is https://fly.storage.tigris.dev/public.fffs.absurd.chat/guests/diotest/1715317855499

The code changes are

in lib/main.dart

FilePickerResult? result = await FilePicker.platform.pickFiles(withData: true);

in macos/Runner/Release.entitlements and macos/Runner/DebugProfile.entitlements I added following entry under <dict> to allow outbound connection permission

<key>com.apple.security.network.client</key>
<true/>

Added some debug log lines and I was able to upload image file via your app

here is some example output

flutter: File Upload :: Sent 29/202949 bytes
flutter: File Upload :: Sent 75/202949 bytes
flutter: File Upload :: Sent 103/202949 bytes
flutter: File Upload :: Sent 105/202949 bytes
flutter: File Upload :: Sent 134/202949 bytes
flutter: File Upload :: Sent 183/202949 bytes
flutter: File Upload :: Sent 206/202949 bytes
flutter: File Upload :: Sent 208/202949 bytes
flutter: File Upload :: Sent 237/202949 bytes
flutter: File Upload :: Sent 290/202949 bytes
flutter: File Upload :: Sent 306/202949 bytes
flutter: File Upload :: Sent 308/202949 bytes
flutter: File Upload :: Sent 337/202949 bytes
flutter: File Upload :: Sent 395/202949 bytes
flutter: File Upload :: Sent 411/202949 bytes
flutter: File Upload :: Sent 413/202949 bytes
flutter: File Upload :: Sent 442/202949 bytes
flutter: File Upload :: Sent 501/202949 bytes
flutter: File Upload :: Sent 585/202949 bytes
flutter: File Upload :: Sent 587/202949 bytes
flutter: File Upload :: Sent 616/202949 bytes
flutter: File Upload :: Sent 665/202949 bytes
flutter: File Upload :: Sent 1177/202949 bytes
flutter: File Upload :: Sent 1179/202949 bytes
flutter: File Upload :: Sent 1208/202949 bytes
flutter: File Upload :: Sent 1266/202949 bytes
flutter: File Upload :: Sent 1330/202949 bytes
flutter: File Upload :: Sent 1332/202949 bytes
flutter: File Upload :: Sent 1361/202949 bytes
flutter: File Upload :: Sent 1448/202949 bytes
flutter: File Upload :: Sent 202916/202949 bytes
flutter: File Upload :: Sent 202918/202949 bytes
flutter: File Upload :: Sent 202949/202949 bytes

I understand you are having issue with upload - can you try with the above code changes?. If the issue persist at this point, I think if we can do a screen-shared video call, it will speed up the debugging.

I work in PT and available between 10am-5pm PT. You can reach to us at help@tigrisdata.com so that I can setup the meeting with you.

Thanks!
Jigar

Doesn’t change anything for me, nor would I expect it to I don’t think…I’m already pulling the byte data into memory with await ioFile.readAsBytes(). Did you get a 200 response? I haven’t built on MacOS yet, but I get bytes transferred on web as well before the server throws the malformed request error at the end.

This is also a naive example; in actuality I need to support streaming uploads.

I’ll reach out via email; I have another small question that you might be able to help with as well :slight_smile:

To close this one out - Wes and I synced on different channel and it was a DIO client issue on how it constructed the multipart-file upload without specifying the filename. This issue was resolved by properly constructing the multipart/form-data file upload.