seanmonstar / reqwest

An easy and powerful Rust HTTP Client
https://docs.rs/reqwest
Apache License 2.0
9.63k stars 1.08k forks source link

Web Kit Form Boundary Support? #1537

Open GenerelSchwerz opened 2 years ago

GenerelSchwerz commented 2 years ago

I'm facing an issue when using the reqwest::multipart part of this library. I'm currently attempting to make a POST request to a website that only accepts Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXXXXXXXXXXXX.

Reqwest's current support for multipart data automatically derives the boundary and such using a standard that does not comply with what WebkitFormBoundary desires.

Current multipart boundary generation:

fn gen_boundary() -> String {
    use crate::util::fast_random as random;

    let a = random();
    let b = random();
    let c = random();
    let d = random();

    format!("{:016x}-{:016x}-{:016x}-{:016x}", a, b, c, d)
}

The request I'm attempting to emulate:

curl 'https://canary.discord.com/api/v9/interactions' \
  -H 'authority: canary.discord.com' \
  -H 'x-super-properties: eyJvcyI6IkxpbnV4IiwiYnJvd3NlciI6IkRpc2NvcmQgQ2xpZW50IiwicmVsZWFzZV9jaGFubmVsIjoiY2FuYXJ5IiwiY2xpZW50X3ZlcnNpb24iOiIwLjAuMTM1Iiwib3NfdmVyc2lvbiI6IjUuMTUuMzItMS1NQU5KQVJPIiwib3NfYXJjaCI6Ing2NCIsInN5c3RlbV9sb2NhbGUiOiJlbi1VUyIsIndpbmRvd19tYW5hZ2VyIjoiS0RFLHVua25vd24iLCJkaXN0cm8iOiJcIk1hbmphcm8gTGludXhcIiIsImNsaWVudF9idWlsZF9udW1iZXIiOjEyNjQ2MiwiY2xpZW50X2V2ZW50X3NvdXJjZSI6bnVsbH0=' \
  -H 'x-discord-locale: en-US' \
  -H 'x-debug-options: logGatewayEvents,bugReporterEnabled' \
  -H 'accept-language: en-US' \
  -H 'authorization: <insert token here>' \
  -H 'user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) discord/0.0.135 Chrome/94.0.4606.81 Electron/15.5.1 Safari/537.36' \
  -H 'content-type: multipart/form-data; boundary=----WebKitFormBoundarykwAyTISRXus0p8sG' \
  -H 'accept: */*' \
  -H 'origin: https://canary.discord.com' \
  -H 'sec-fetch-site: same-origin' \
  -H 'sec-fetch-mode: cors' \
  -H 'sec-fetch-dest: empty' \
  -H 'referer: https://canary.discord.com/channels/955145480940191768/968518565349175386' \
  -H 'cookie: __dcfduid=d954a700b87911ec802a21102b798aa2; __sdcfduid=d954a701b87911ec802a21102b798aa26512292cd369303989d7bcaa9d0b23f35a19ba3f82f583bade6d58374394da19; __stripe_mid=529d07b4-7ac5-4792-8c86-cae4ab8234f77bd1d2; __cf_bm=9VnuEGwtDWWAir33kr3V4xNhu4DM8LJlhTJYIn5Alr0-1651495304-0-AehyMRJKc56uT51nmX3QOjN+0jViPy9GQ9QYvrlwUObPrBk0AZquE3wcl+j85lqXSryqUp2Lze7ArCwx+XKjqTS4DgEbvHqR/fyHA0aBzZAx6L+OEA087xymdRs2KNl0Xw==' \
  --data-raw $'------WebKitFormBoundarykwAyTISRXus0p8sG\r\nContent-Disposition: form-data; name="payload_json"\r\n\r\n{"type":2,"application_id":"276060004262477825","guild_id":"955145480940191768","channel_id":"968518565349175386","session_id":"f79084f31c7a8f41f8e7a3a4a2bfca16","data":{"version":"969007199709962270","id":"955966192806404190","name":"reddit","type":1,"options":[{"type":1,"name":"test","options":[{"type":3,"name":"reddit","value":"e"}]}],"application_command":{"id":"955966192806404190","application_id":"276060004262477825","version":"969007199709962270","default_permission":true,"default_member_permissions":"32","type":1,"name":"reddit","description":"📰 Follow subreddits in your Discord server.","dm_permission":false,"options":[{"type":1,"name":"list","description":"📰 List followed subreddits."},{"type":1,"name":"follow","description":"📰 Follow a subreddit in a channel.","options":[{"type":3,"name":"name","description":"The name of the subreddit.","required":true},{"type":7,"name":"channel","description":"The channel to post submissions.","required":true,"channel_types":[0]},{"type":3,"name":"type","description":"The type of the subreddit. (Defaults to Slow)","choices":[{"name":"Slow (1 hour)","value":"slow"},{"name":"Fast (2 mins) (Premium only)","value":"fast"}]},{"type":3,"name":"filters","description":"The filters to use, separated with commas. (Example: one piece, manga, chapter, new)"},{"type":3,"name":"message","description":"The message to send along with every submission. (Example: New submission on /r/OnePiece subreddit\u0021)"},{"type":5,"name":"everyone","description":"Either to mention @everyone along with every submission. (Defaults to False)"},{"type":3,"name":"roles","description":"The roles to mentions along with every submission. (Example: @Role1 @Role2)"},{"type":3,"name":"nsfw","description":"NSFW Filtering to use on new submissions.","choices":[{"name":"Post everything","value":"none"},{"name":"Only post NSFW submissions","value":"only"},{"name":"Ignore NSFW submissions","value":"ignore"}]},{"type":3,"name":"spoiler","description":"Spoiler Filtering to use on new submissions.","choices":[{"name":"Post everything","value":"none"},{"name":"Only post Spoiler-tagged submissions","value":"only"},{"name":"Ignore Spoiler-tagged submissions","value":"ignore"}]}]},{"type":1,"name":"unfollow","description":"📰 Unfollow a subreddit.","options":[{"type":3,"name":"reddit","description":"Subreddit to unfollow.","required":true,"autocomplete":true}]},{"type":1,"name":"test","description":"📰 Test a subreddit.","options":[{"type":3,"name":"reddit","description":"Subreddit to test.","required":true,"autocomplete":true}]}]},"attachments":[]},"nonce":"970612694862266368"}\r\n------WebKitFormBoundarykwAyTISRXus0p8sG--\r\n' \
  --compressed

Is there any alternative for sending multipart forms, or to manually construct them?

GenerelSchwerz commented 2 years ago

A slight update:

This could be easily resolved if there was an option to specify the boundary of each part manually. I'm not sure if I should open a pull request or not, since I'm relatively new to rust, but if I should please let me know.

Coops0 commented 10 months ago

This is old but the solution I found was to use the mpart-async library, specify my own content type header, and then use the body function with reqwest.

Here is an example:

use mpart_async::client::MultipartRequest;
use mpart_async::filestream::FileStream;
use reqwest::{Body, Client, get, header};
use reqwest::header::CONTENT_TYPE;

pub async fn update_name(
    fields: Vec<(String, String)>,
) -> Result<ResultStruct> {
    let mut mpart = MultipartRequest::<FileStream>::new("------WebKitFormBoundaryhSUnLTjMGhbdZ0Qg");
    for (name, value) in fields {
        mpart.add_field(name, value);
    }

    let res = Client::new()
        .post(format!("website"))
        .header(
            CONTENT_TYPE,
            format!("multipart/form-data; boundary={}", mpart.get_boundary()),
        )
        .body(Body::wrap_stream(mpart))
        .send()
        .await?
        .json::<ResultStruct>()
        .await?;

    Ok(res)
}