How can I write HTTP POST request multipart/form-data myself? - http

I want to send a POST request to server in multipart/form-data contents.
I have to write the request statement myself, without any high-level API
because it has to be sent as an AT command of the modem.
The curl command below is a command to test the server's API, and this is what I want to construct myself.
# This is what I want to construct myself.
curl -X POST $API_ENDPOINT \
-F time=yyyy-mm-dd-hh:mm:ss \
-F event=1 \
-F rssi=31 \
-F battery=80 \
-F filename=test.jpg \
-F files=#test.jpg \ # Send an image from my local file system to server.
-w %{http_code}
echo "\r\n"
And the below is what I sent to my modem.
/* This is form for multipart/form-data. Is it right?
POST $API_ENDPOINT HTTP/1.1
Host: $HOST
Content-Length: $body_length
Content-Type: multipart/form-data; boundary="boundary"
--boundary
Content-Disposition: form-data; name="files"; filename="example.jpg"
Content-Type: image/jpg
--boundary--
*/
// Below C++ code was written for the above POST request.
std::string filename = "example.jpg";
std::string filePath = "example.jpg";
std::string body =
std::string("--boundary\r\n") +
"Content-Disposition: form-data; name=\"files\"; filename=\"" + filePath + "\"\r\n" +
"Content-Type: image/jpg\r\n" +
"\r\n" +
"--boundary--\r\n";
std::string header =
std::string("POST ") + url + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Content-Length: " + std::to_string(body.length()) + "\r\n" +
"Content-Type: multipart/form-data; boundary=\"boundary\"\r\n" +
"\r\n";
std::string data = header + body;
... and this is the server log.
Error: MultipartParser.end(): stream ended unexpectedly: state = PART_DATA
This is my question.
How can I add fields(time, event, rssi, etc) in the request body? The server seems getting each fields as <key: value> format. How can I add it in multipart/form-data?
Is it correct to attach an image file like that? In the curl command, I wrote the path of my local image file (-F files) and the name that this image file will be stored on the server (-F filename). How should I reflect this in my request form?
Thanks brothers

I did it! Right that.
/*
POST $API_ENDPOINT HTTP/1.1
Host: $HOST
Content-Length: $body_length
Content-Type: multipart/form-data; boundary="boundary"
--boundary
Content-Type: text/plain
Content-Disposition: form-data; name="time"
yyyy-mm-dd-hh:mm:ss
--boundary
Content-Type: text/plain
Content-Disposition: form-data; name="event"
1
--boundary
Content-Type: text/plain
Content-Disposition: form-data; name="rssi"
31
--boundary
Content-Type: text/plain
Content-Disposition: form-data; name="battery"
99
--boundary
Content-Type: text/plain
Content-Disposition: form-data; name="filename"
example.jpg
--boundary
Content-Type: image/jpg
Content-Disposition: form-data; name="files"; filename="example.jpg"
....binary bytes of example.jpg...
--boundary--
*/
const int fd = // File descriptor of my serial port
const std::string host = // host
const std::string uri = // uri
const int tryout = 10 // for modem
std::string filename = "1996-03-05.jpg";
std::string filePath = "1996-03-05.jpg";
std::ifstream bin(filePath, std::ios::binary);
std::string imageBin((std::istreambuf_iterator<char>(bin)), std::istreambuf_iterator<char>());
std::string TIMESTAMP = "1996-03-05";
std::string event = "1";
std::string rssi = "31";
std::string battery = "90";
atcmd::__sendATcmd(fd, "AT+QHTTPCFG=\"contextid\",1\r");
atcmd::__readBufferUntil(fd, "\r\nOK\r\n", tryout);
atcmd::__sendATcmd(fd, "AT+QHTTPCFG=\"contenttype\",3\r"); // 3: multipart/form-data
atcmd::__readBufferUntil(fd, "\r\nOK\r\n", tryout);
atcmd::__sendATcmd(fd, "AT+QHTTPCFG=\"requestheader\",1\r");
atcmd::__readBufferUntil(fd, "\r\nOK\r\n", tryout);
const std::string fullUrl = "http://" + host + uri;
atcmd::__sendATcmd(fd, ("AT+QHTTPURL=" + std::to_string(fullUrl.length()) + "\r").c_str());
atcmd::__readBufferUntil(fd, "\r\nCONNECT\r\n", tryout);
atcmd::__sendATcmd(fd, fullUrl.c_str());
atcmd::__readBufferUntil(fd, "\r\nOK\r\n", tryout);
std::string body_fields = (
std::string("--boundary\r\n") +
"Content-Type: text/plain\r\n" +
"Content-Disposition: form/data; name=\"time\"\r\n" +
"\r\n" +
TIMESTAMP + "\r\n" +
"--boundary\r\n" +
"Content-Type: text/plain\r\n" +
"Content-Disposition: form/data; name=\"event\"\r\n" +
"\r\n" +
event + "\r\n" +
"--boundary\r\n" +
"Content-Type: text/plain\r\n" +
"Content-Disposition: form/data; name=\"rssi\"\r\n" +
"\r\n" +
rssi + "\r\n" +
"--boundary\r\n" +
"Content-Type: text/plain\r\n" +
"Content-Disposition: form/data; name=\"battery\"\r\n" +
"\r\n" +
battery + "\r\n" +
"--boundary\r\n" +
"Content-Type: text/plain\r\n" +
"Content-Disposition: form/data; name=\"filename\"\r\n" +
"\r\n" +
filename + "\r\n"
);
std::string body_image = (
std::string("--boundary\r\n") +
"Content-Type: image/jpeg\r\n" +
"Content-Disposition: form-data; name=\"files\"; filename=\"" + filePath + "\"\r\n" +
"\r\n" +
imageBin + "\r\n" +
"--boundary--\r\n"
);
int bodyLen = body_fields.length() + body_image.length();
std::string header = (
std::string("POST ") + uri + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Content-Length: " + std::to_string(bodyLen) + "\r\n" +
"Content-Type: multipart/form-data; boundary=\"boundary\"\r\n" +
"\r\n"
);
const std::string dataLen = std::to_string(header.length() + bodyLen);
std::string maxInputBodyTime = "80";
std::string maxResponseTime = "80";
atcmd::__sendATcmd(fd, ("AT+QHTTPPOST="
+ dataLen + ","
+ maxInputBodyTime + ","
+ maxResponseTime + "\r").c_str());
atcmd::__readBufferUntil(fd, "\r\nCONNECT\r\n", tryout);
atcmd::__sendATcmd(fd, header.c_str());
atcmd::__sendATcmd(fd, body_fields.c_str());
atcmd::__sendATcmd(fd, body_image.c_str(), body_image.length());
atcmd::__readBufferUntil(fd, "\r\nOK\r\n", tryout);
atcmd::__sendATcmd(fd, "AT+QHTTPREAD=80\r");
std::cout << atcmd::__readBuffer(fd) << std::endl;

Related

Correct HTTP response header

I am using Safari to test my C++ socket handler, I send a request to the application, in the address bar:
http://127.0.0.1:8124/?{%22module%22:%22mdFileIO%22,%22command%22:%22open%22}
In my application I send a response:
const QString CRLF("\r\n");
QString strContent(strResponse)
,strDtNow(QDateTime::currentDateTime().toUTC().toString("ddd, dd MMM yyyy hh:mm:ss"))
,strHdr = "200 OK" + CRLF
+ "Content-Type: application/json" + CRLF
+ "Content-Length: " + QString::number(strContent.length()) + CRLF
+ "Date: " + strDtNow + " GMT" + CRLF + CRLF;
strResponse = strHdr + strContent;
In the above 'strContent' contains:
{"ack":"ack","module":"mdFileIO","time":"2020-10-05 18:00:19"}
The output for the response looks like this:
200 OK\r\nContent-Type: application/json\r\nContent-Length: 62\r\nDate: Mon, 05 Oct 2020 18:25:59 GMT\r\n\r\n{\"ack\":\"ack\",\"module\":\"mdFileIO\",\"time\":\"2020-10-05 18:25:59\"}
The return from write() is 161. Safari shows the following:
A couple of errors in the header:
The header should have started with "HTTP/1.1 "
The Content-Type should have been: "application/jsonrequest"

Need some assistance with CyberSource authentication please

I am trying to post a request for "flex/v1/keys" api and i am trying to mirror the examples CyberSource have on the api page. But i keep getting back {"response":{"rmsg":"Authentication Failed"}} .
Please can someone assist me i am clueless and been struggling for a while now.
Code for getting headers sorted.
var crypto = require("crypto-js");
var requestHost = 'apitest.cybersource.com';
var merchantId = 'testrest';
var merchantKeyId = '08c94330-f618-42a3-b09d-e1e43be5efda';
var merchantSecretKey = 'yBJxy6LjM2TmcPGu+GaJrHtkke25fPpUX+UY6/L/1tE=';
var resource = "/flex/v1/keys";
//digest
var payload = '{"encryptionType": "None"}';
var data = crypto.enc.Utf8.parse(payload)
var hash = crypto.SHA256(data)
var base64 = crypto.enc.Base64.stringify(hash);
var digest = "SHA-256=" + base64;
pm.globals.set("digest",digest);
//date
var date = new Date(Date.now()).toUTCString();
pm.globals.set("date",date);
//signature
var signatureHeader = "";
signatureHeader += "keyid=\"" + merchantKeyId + "\"";
signatureHeader += ", algorithm=\"HmacSHA256\"";
var headersForPostMethod = "host date (request-target) digest v-c-merchant-id";
signatureHeader += ", headers=\"" + headersForPostMethod + "\"";
var signatureString = 'host: ' + requestHost;
signatureString += '\ndate: ' + new Date(Date.now()).toUTCString();
signatureString += '\n(request-target): ';
var targetUrlForPost = "post " + resource;
signatureString += targetUrlForPost + '\n';
signatureString += 'digest: SHA-256=' + digest + '\n';
signatureString += 'v-c-merchant-id: ' + merchantId;
var dataSigString = crypto.enc.Utf8.parse(signatureString);
var secKey = crypto.enc.Base64.parse(merchantSecretKey);
var hashHmac = CryptoJS.HmacSHA256(dataSigString, secKey)
var base64hashHmac = CryptoJS.enc.Base64.stringify(hashHmac);
signatureHeader += ", signature=\"" + base64hashHmac + "\"";
pm.globals.set("signature",signatureHeader);
My post request i am trying to send.
curl --location --request POST 'https://apitest.cybersource.com/flex/v1/keys' \
--header 'digest: SHA-256=yF79QR9XHmXEMjhnXRIvsaGie/xoTduWMP8kMOUIyVc=' \
--header 'v-c-merchant-id: testrest' \
--header 'date: Fri, 29 May 2020 15:06:42 GMT' \
--header 'host: apitest.cybersource.com' \
--header 'signature: keyid="08c94330-f618-42a3-b09d-e1e43be5efda", algorithm="HmacSHA256", headers="host date (request-target) digest v-c-merchant-id", signature="lnv5/zeUimcef0Dr3VeOyKgOw/cX8Erdb+qaKuSwuug="' \
--header 'profile-id: 93B32398-AD51-4CC2-A682-EA3E93614EB1' \
--header 'Content-Type: application/json' \
--data-raw '{"encryptionType": "None"}'
RESULTS
POST https://apitest.cybersource.com/flex/v1/keys
Request Headers
digest: SHA-256=yF79QR9XHmXEMjhnXRIvsaGie/xoTduWMP8kMOUIyVc=
v-c-merchant-id: testrest
date: Fri, 29 May 2020 14:57:17 GMT
host: apitest.cybersource.com
signature: keyid="08c94330-f618-42a3-b09d-e1e43be5efda", algorithm="HmacSHA256", headers="host date (request-target) digest v-c-merchant-id", signature="cLFxiYvra8KMBOaTB25Ke+gnQh67/MMn9wr0d8PRSm4="
profile-id: 93B32398-AD51-4CC2-A682-EA3E93614EB1
Content-Type: application/json
User-Agent: PostmanRuntime/7.25.0
Accept: */*
Cache-Control: no-cache
Postman-Token: ef33044d-e745-4014-bdd8-d30f659be760
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 27
Request Body
{"encryptionType": "None"}↵
Response Headers
Strict-Transport-Security: max-age=31536000
v-c-correlation-id: 9e9d8293-b909-4fdf-9ae6-9f992e5dca87
content-type: application/json
v-c-response-time: 1590764238
content-length: 45
Response Body
{"response":{"rmsg":"Authentication Failed"}}
for anyone encountering this issue i have found my fix and it works now with crypto-js authentication.
Silly mistake from me not building the string up properly.
just update the script mentioned in my question with the following.
BROKEN
signatureString += 'digest: SHA-256=' + digest + '\n';
FIX
signatureString += 'digest: ' + digest + '\n';

Lua Envoy upstream proxy

im looking to replace some login logic in on kong, for permission checks on a specific url (like upstream) to an envoy filter in istio.
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: api-auth
namespace: api
spec:
workloadLabels:
app: api
filters:
- listenerMatch:
listenerType: SIDECAR_INBOUND
listenerProtocol: HTTP
filterName: envoy.lua
filterType: HTTP
filterConfig:
inlineCode: |
function version()
return "v1"
end
function log(handle, value)
handle:logInfo(version() .. ": " .. value)
end
function dump(o)
if type(o) == 'table' then
local s = '{ '
for k,v in pairs(o) do
if type(k) ~= 'number' then k = '"'..k..'"' end
s = s .. '['..k..'] = ' .. dump(v) .. ','
end
return s .. '} '
else
return tostring(o)
end
end
function is_empty(value)
return value == nil or value == ""
end
function get_header(handle, header)
return handle:headers():get(header)
end
function envoy_on_request(request_handle)
local auth_host = "auth-service.services.svc.cluster.local"
local path = "/api/v1/has-permission"
local cluster = "outbound|8080||" .. auth_host
local request_headers = {
[":method"] = "POST",
[":path"] = path,
[":authority"] = auth_host,
["Authorization"] = get_header(request_handle, "Authorization")
}
local request_body = ""
local timeout = 5000 --ms
log(request_handle, "Sending auth request, headers: " .. dump(request_headers) .. ", request_body: " .. request_body .. ", timeout: " .. timeout)
local response_headers, response_body = request_handle:httpCall(
tostring(cluster),
request_headers,
request_body,
timeout
)
log(request_handle, "response_headers: " .. dump(response_headers))
log(request_handle, "response_body: " .. dump(response_body))
if tonumber(response_headers[":status"]) ~= 200 then
log(request_handle, "Key Authentication Failed")
request_handle:respond(
{[":status"] = response_headers[":status"]},
response_body
)
do return end
end
end
so this is my lua, but im still missing something, i need to send extra parameters on my body post request.
example working curl:
curl -i 'https://foo-api.com/list' \
-H 'Connection: keep-alive' \
-H 'Pragma: no-cache' \
-H 'Cache-Control: no-cache' \
-H 'AuthCode: cmdpby50ZWl4ZWlyYUBqdW1pYS5jb20iLCJleHAiOjE1ODUwNDg2MjIsImlzcyI6ImZpcmV3b3JrcyJ9.JkvIhmQuumS32HhSzKuAhpPvjLVwOrRJXwajMjBU9Ag' \
-H 'Accept-Language: en' \
-H 'Authorization: Bearer 6InNlcmdpby50ZWl4ZWlyYUBqdW1pYS5jb20iLCJleHAiOjE1ODUwNDg2MjIsImlzcyI6ImZpcmV3b3JrcyJ9.JkvIhmQuumS32HhSzKuAhpPvjLVwOrRJXwajMjBU9Ag' \
-H 'Accept: application/json, text/plain, */*' \
-H 'Sec-Fetch-Dest: empty' \
-H 'application: COMPANYCODE'
how iam supposed to send this kind of content inside the post using lua?
thanks and best regards

Upload multiple files from Powershell script

I have a webapplication that can process POSTing of a html form like this:
<form action="x" method="post" enctype="multipart/form-data">
<input name="xfa" type="file">
<input name="pdf" type="file">
<input type="submit" value="Submit">
</form>
Note that there are two type="file" <input> elements.
How can I script POSTing this from a Powershell script? I plan to do that to create a simple test-framework for the service.
I found WebClient.UploadFile(), but that can only handle a single file.
Thank you for taking your time.
I've been crafting multipart HTTP POST with PowerShell today. I hope the code below is helpful to you.
PowerShell itself cannot do multipart form uploads.
There are not many sample about it either. I built the code based on this and this.
Sure, Invoke-RestMethod requires PowerShell 3.0 but the code in the latter of the above links shows how to do HTTP POST with .NET directly, allowing you to have this running in Windows XP as well.
Good luck! Please tell if you got it to work.
function Send-Results {
param (
[parameter(Mandatory=$True,Position=1)] [ValidateScript({ Test-Path -PathType Leaf $_ })] [String] $ResultFilePath,
[parameter(Mandatory=$True,Position=2)] [System.URI] $ResultURL
)
$fileBin = [IO.File]::ReadAllBytes($ResultFilePath)
$computer= $env:COMPUTERNAME
# Convert byte-array to string (without changing anything)
#
$enc = [System.Text.Encoding]::GetEncoding("iso-8859-1")
$fileEnc = $enc.GetString($fileBin)
<#
# PowerShell does not (yet) have built-in support for making 'multipart' (i.e. binary file upload compatible)
# form uploads. So we have to craft one...
#
# This is doing similar to:
# $ curl -i -F "file=#file.any" -F "computer=MYPC" http://url
#
# Boundary is anything that is guaranteed not to exist in the sent data (i.e. string long enough)
#
# Note: The protocol is very precise about getting the number of line feeds correct (both CRLF or LF work).
#>
$boundary = [System.Guid]::NewGuid().ToString() #
$LF = "`n"
$bodyLines = (
"--$boundary",
"Content-Disposition: form-data; name=`"file`"$LF", # filename= is optional
$fileEnc,
"--$boundary",
"Content-Disposition: form-data; name=`"computer`"$LF",
$computer,
"--$boundary--$LF"
) -join $LF
try {
# Returns the response gotten from the server (we pass it on).
#
Invoke-RestMethod -Uri $URL -Method Post -ContentType "multipart/form-data; boundary=`"$boundary`"" -TimeoutSec 20 -Body $bodyLines
}
catch [System.Net.WebException] {
Write-Error( "FAILED to reach '$URL': $_" )
throw $_
}
}
I was bothered by this thing and haven't found a satisfactory solution. Although the gist here proposed can do the yob, it is not efficient in case of large files transmittal. I wrote a blog post proposing a solution for it, basing my cmdlet on HttpClient class present in .NET 4.5. If that is not a problem for you, you can check my solution at the following address http://blog.majcica.com/2016/01/13/powershell-tips-and-tricks-multipartform-data-requests/
EDIT:
function Invoke-MultipartFormDataUpload
{
[CmdletBinding()]
PARAM
(
[string][parameter(Mandatory = $true)][ValidateNotNullOrEmpty()]$InFile,
[string]$ContentType,
[Uri][parameter(Mandatory = $true)][ValidateNotNullOrEmpty()]$Uri,
[System.Management.Automation.PSCredential]$Credential
)
BEGIN
{
if (-not (Test-Path $InFile))
{
$errorMessage = ("File {0} missing or unable to read." -f $InFile)
$exception = New-Object System.Exception $errorMessage
$errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, 'MultipartFormDataUpload', ([System.Management.Automation.ErrorCategory]::InvalidArgument), $InFile
$PSCmdlet.ThrowTerminatingError($errorRecord)
}
if (-not $ContentType)
{
Add-Type -AssemblyName System.Web
$mimeType = [System.Web.MimeMapping]::GetMimeMapping($InFile)
if ($mimeType)
{
$ContentType = $mimeType
}
else
{
$ContentType = "application/octet-stream"
}
}
}
PROCESS
{
Add-Type -AssemblyName System.Net.Http
$httpClientHandler = New-Object System.Net.Http.HttpClientHandler
if ($Credential)
{
$networkCredential = New-Object System.Net.NetworkCredential #($Credential.UserName, $Credential.Password)
$httpClientHandler.Credentials = $networkCredential
}
$httpClient = New-Object System.Net.Http.Httpclient $httpClientHandler
$packageFileStream = New-Object System.IO.FileStream #($InFile, [System.IO.FileMode]::Open)
$contentDispositionHeaderValue = New-Object System.Net.Http.Headers.ContentDispositionHeaderValue "form-data"
$contentDispositionHeaderValue.Name = "fileData"
$contentDispositionHeaderValue.FileName = (Split-Path $InFile -leaf)
$streamContent = New-Object System.Net.Http.StreamContent $packageFileStream
$streamContent.Headers.ContentDisposition = $contentDispositionHeaderValue
$streamContent.Headers.ContentType = New-Object System.Net.Http.Headers.MediaTypeHeaderValue $ContentType
$content = New-Object System.Net.Http.MultipartFormDataContent
$content.Add($streamContent)
try
{
$response = $httpClient.PostAsync($Uri, $content).Result
if (!$response.IsSuccessStatusCode)
{
$responseBody = $response.Content.ReadAsStringAsync().Result
$errorMessage = "Status code {0}. Reason {1}. Server reported the following message: {2}." -f $response.StatusCode, $response.ReasonPhrase, $responseBody
throw [System.Net.Http.HttpRequestException] $errorMessage
}
$responseBody = [xml]$response.Content.ReadAsStringAsync().Result
return $responseBody
}
catch [Exception]
{
$PSCmdlet.ThrowTerminatingError($_)
}
finally
{
if($null -ne $httpClient)
{
$httpClient.Dispose()
}
if($null -ne $response)
{
$response.Dispose()
}
}
}
END { }
}
Cheers
I have found a solution to my problem after studying how multipart/form-data is built. A lot of help came in the form of http://www.paraesthesia.com/archive/2009/12/16/posting-multipartform-data-using-.net-webrequest.aspx.
The solution then is to build the body of the request up manually according to that convention. I have left of niceties like correct Content-Lengths etc.
Here is an excerpt of what I am using now:
$path = "/Some/path/to/data/"
$boundary_id = Get-Date -Format yyyyMMddhhmmssfffffff
$boundary = "------------------------------" + $boundary_id
$url = "http://..."
[System.Net.HttpWebRequest] $req = [System.Net.WebRequest]::create($url)
$req.Method = "POST"
$req.ContentType = "multipart/form-data; boundary=$boundary"
$ContentLength = 0
$req.TimeOut = 50000
$reqst = $req.getRequestStream()
<#
Any time you write a file to the request stream (for upload), you'll write:
Two dashes.
Your boundary.
One CRLF (\r\n).
A content-disposition header that tells the name of the form field corresponding to the file and the name of the file. That looks like:
Content-Disposition: form-data; name="yourformfieldname"; filename="somefile.jpg"
One CRLF.
A content-type header that says what the MIME type of the file is. That looks like:
Content-Type: image/jpg
Two CRLFs.
The entire contents of the file, byte for byte. It's OK to include binary content here. Don't base-64 encode it or anything, just stream it on in.
One CRLF.
#>
<# Upload #1: XFA #>
$xfabuffer = [System.IO.File]::ReadAllBytes("$path\P7-T.xml")
<# part-header #>
$header = "--$boundary`r`nContent-Disposition: form-data; name=`"xfa`"; filename=`"xfa`"`r`nContent-Type: text/xml`r`n`r`n"
$buffer = [Text.Encoding]::ascii.getbytes($header)
$reqst.write($buffer, 0, $buffer.length)
$ContentLength = $ContentLength + $buffer.length
<# part-data #>
$reqst.write($xfabuffer, 0, $xfabuffer.length)
$ContentLength = $ContentLength + $xfabuffer.length
<# part-separator "One CRLF" #>
$terminal = "`r`n"
$buffer = [Text.Encoding]::ascii.getbytes($terminal)
$reqst.write($buffer, 0, $buffer.length)
$ContentLength = $ContentLength + $buffer.length
<# Upload #1: PDF template #>
$pdfbuffer = [System.IO.File]::ReadAllBytes("$path\P7-T.pdf")
<# part-header #>
$header = "--$boundary`r`nContent-Disposition: form-data; name=`"pdf`"; filename=`"pdf`"`r`nContent-Type: application/pdf`r`n`r`n"
$buffer = [Text.Encoding]::ascii.getbytes($header)
$reqst.write($buffer, 0, $buffer.length)
$ContentLength = $ContentLength + $buffer.length
<# part-data #>
$reqst.write($pdfbuffer, 0, $pdfbuffer.length)
$ContentLength = $ContentLength + $pdfbuffer.length
<# part-separator "One CRLF" #>
$terminal = "`r`n"
$buffer = [Text.Encoding]::ascii.getbytes($terminal)
$reqst.write($buffer, 0, $buffer.length)
$ContentLength = $ContentLength + $buffer.length
<#
At the end of your request, after writing all of your fields and files to the request, you'll write:
Two dashes.
Your boundary.
Two more dashes.
#>
$terminal = "--$boundary--"
$buffer = [Text.Encoding]::ascii.getbytes($terminal)
$reqst.write($buffer, 0, $buffer.length)
$ContentLength = $ContentLength + $buffer.length
$reqst.flush()
$reqst.close()
# Dump request to console
#$req
[net.httpWebResponse] $res = $req.getResponse()
# Dump result to console
#$res
# Dump result-body to filesystem
<#
$resst = $res.getResponseStream()
$sr = New-Object IO.StreamReader($resst)
$result = $sr.ReadToEnd()
$res.close()
#>
$null = New-Item -ItemType Directory -Force -Path "$path\result"
$target = "$path\result\P7-T.pdf"
# Create a stream to write to the file system.
$targetfile = [System.IO.File]::Create($target)
# Create the buffer for copying data.
$buffer = New-Object Byte[] 1024
# Get a reference to the response stream (System.IO.Stream).
$resst = $res.GetResponseStream()
# In an iteration...
Do {
# ...attemt to read one kilobyte of data from the web response stream.
$read = $resst.Read($buffer, 0, $buffer.Length)
# Write the just-read bytes to the target file.
$targetfile.Write($buffer, 0, $read)
# Iterate while there's still data on the web response stream.
} While ($read -gt 0)
# Close the stream.
$resst.Close()
$resst.Dispose()
# Flush and close the writer.
$targetfile.Flush()
$targetfile.Close()
$targetfile.Dispose()
I've remixed #akauppi's answer into a more generic solution, a cmdlet that:
Can take pipeline input from Get-ChildItem for files to upload
Takes an URL as a positional parameter
Takes a dictionary as a positional parameter, which it sends as additional form data
Takes an (optional) -Credential parameter
Takes an (optional) -FilesKey parameter to specify the formdata key for the files upload part
Supports -WhatIf
Has -Verbose logging
Exits with an error if something goes wrong
It can be called like this:
$url ="http://localhost:12345/home/upload"
$form = #{ description = "Test 123." }
$pwd = ConvertTo-SecureString "s3cr3t" -AsPlainText -Force
$creds = New-Object System.Management.Automation.PSCredential ("john", $pwd)
Get-ChildItem *.txt | Send-MultiPartFormToApi $url $form $creds -Verbose -WhatIf
Here's the code to the full cmdlet:
function Send-MultiPartFormToApi {
# Attribution: [#akauppi's post](https://stackoverflow.com/a/25083745/419956)
# Remixed in: [#jeroen's post](https://stackoverflow.com/a/41343705/419956)
[CmdletBinding(SupportsShouldProcess = $true)]
param (
[Parameter(Position = 0)]
[string]
$Uri,
[Parameter(Position = 1)]
[HashTable]
$FormEntries,
[Parameter(Position = 2, Mandatory = $false)]
[System.Management.Automation.Credential()]
[System.Management.Automation.PSCredential]
$Credential,
[Parameter(
ParameterSetName = "FilePath",
Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true
)]
[Alias("Path")]
[string[]]
$FilePath,
[Parameter()]
[string]
$FilesKey = "files"
);
begin {
$LF = "`n"
$boundary = [System.Guid]::NewGuid().ToString()
Write-Verbose "Setting up body with boundary $boundary"
$bodyArray = #()
foreach ($key in $FormEntries.Keys) {
$bodyArray += "--$boundary"
$bodyArray += "Content-Disposition: form-data; name=`"$key`""
$bodyArray += ""
$bodyArray += $FormEntries.Item($key)
}
Write-Verbose "------ Composed multipart form (excl files) -----"
Write-Verbose ""
foreach($x in $bodyArray) { Write-Verbose "> $x"; }
Write-Verbose ""
Write-Verbose "------ ------------------------------------ -----"
$i = 0
}
process {
$fileName = (Split-Path -Path $FilePath -Leaf)
Write-Verbose "Processing $fileName"
$fileBytes = [IO.File]::ReadAllBytes($FilePath)
$fileDataAsString = ([System.Text.Encoding]::GetEncoding("iso-8859-1")).GetString($fileBytes)
$bodyArray += "--$boundary"
$bodyArray += "Content-Disposition: form-data; name=`"$FilesKey[$i]`"; filename=`"$fileName`""
$bodyArray += "Content-Type: application/x-msdownload"
$bodyArray += ""
$bodyArray += $fileDataAsString
$i += 1
}
end {
Write-Verbose "Finalizing and invoking rest method after adding $i file(s)."
if ($i -eq 0) { throw "No files were provided from pipeline." }
$bodyArray += "--$boundary--"
$bodyLines = $bodyArray -join $LF
# $bodyLines | Out-File data.txt # Uncomment for extra debugging...
try {
if (!$WhatIfPreference) {
Invoke-RestMethod `
-Uri $Uri `
-Method Post `
-ContentType "multipart/form-data; boundary=`"$boundary`"" `
-Credential $Credential `
-Body $bodyLines
} else {
Write-Host "WHAT IF: Would've posted to $Uri body of length " + $bodyLines.Length
}
} catch [Exception] {
throw $_ # Terminate CmdLet on this situation.
}
Write-Verbose "Finished!"
}
}

Split TCP packet into smaller ones in AutoIT

I try to send a simple HTTP GET Request using AutoIT TCPSend() command. The problem is that when I check the traffic using SmartSniff, everything is on the same line without linebreaks.
My code is as follows:
TCPStartup()
$ip = "195.143.118.23"
$port = "80"
Global $tcp = TCPConnect($ip, $port)
TCPSend($tcp, "GET HTTP/1.1")
TCPSend($tcp, "Host: ")
TCPSend($tcp, "Connection: keep-alive")
TCPSend($tcp, "Accept: ")
TCPSend($tcp, "User-Agent: ")
TCPSend($tcp, "Referer: ")
TCPSend($tcp, "Accept-Encoding: ")
TCPSend($tcp, "Accept-Language: ")
TCPSend($tcp, "Cookie: ")
TCPSend($tcp, "Connection: keep-alive")
TCPCloseSocket($tcp)
Use #CRLF at the end of every string
Like
TCPSend($tcp,"Hello Line"&#CRLF)
It would also be better to send it once as one big string and not line by line
And don't close the socket.
If you want to get a respone fron the server, you have to use the function TCPRecv. After this you can close the socket.

Resources