Files are uploading to Google drive with Untitled name in Delphi - rest

I am trying to upload files to Google drive using
RestAPI in Delphi.Every thing is working fine but files are Uploading into Google drive with Untitled name.
Below is code i written for uploading into drive.
local_filename:= 'D:/Capture.jpg';
{$ENDIF}
RESTResponseDataSetAdapter.AutoUpdate :=false;
RESTRequest.Params.Clear;
RESTRequest.Method := TRESTRequestMethod.rmPOST;
RESTRequest.AddParameter('application/json; charset=utf-8','{"title": "Capture.jpg"}',TRESTRequestParameterKind.pkREQUESTBODY);
RESTClient.BaseURL := 'https://www.googleapis.com/upload/drive/v2';
RESTRequest.Resource := '/files?uploadType=multipart';
upload_stream := TFileStream.Create(local_filename,fmOpenRead);
upload_stream.Position := 0
RESTRequest.Addbody(upload_stream, TRESTContentType.ctIMAGE_JPEG);
RESTRequest.Execute;
Can some one suggest how to give a file name/ upload the file with the same name to google drive.

AddBody() appears to supercede AddParameter(), so you would be wiping out your metadata JSON. This is stated in the AddBody() documentation:
Generally, a call to AddBody replaces any previous value of the body parameter. However, if you pass ABodyContent as a string, the specified value is appended, and the resulting request contains several body parameters
The JSON metadata and the JPG file need to be sent together in the request body, in multipart/related format. However, looking at TRESTRequest, I don't see an easy way to send requests in that format (I may be wrong here). You might have to put the entire multipart data in a single TStream and pass that to AddBody() with a ContentType of TRESTContentType.ctMULTIPART_RELATED. If you try to add the various pieces as separate parameters, it won't send the right content type:
A single-parameter request uses application/x-www-form-urlencoded, while a multiple-parameter request uses multipart/mixed instead.

Check Google Drive REST API reference
https://developers.google.com/drive/v2/web/manage-uploads#multipart.
You have to send "metadata". There are 2 options. You complete that simple upload and then call another request to update metada of this file. Or you can do multi-part upload and add a parameter.
This one should work but it seems that REST request does not support this method.
RESTRequest.Method := TRESTRequestMethod.rmPOST;
RESTRequest.Params.AddItem('metadata', '{"title": "YourFileName.dat"}', TRESTRequestParameterKind.pkREQUESTBODY, [], TRESTContentType.ctAPPLICATION_JSON);
So the solution is to update metadata after your request
var
s: string
RESTRequest1.Response.GetSimpleValue('id', s);
RESTRequest1.Params.Clear;
RESTClient1.BaseURL := 'https://www.googleapis.com/drive/v2/files/'+s;
RESTRequest1.Resource := '';
RESTRequest1.Method:=TRESTRequestMethod.rmPUT;
RESTRequest1.AddBody('{"title": "Capture.jpg"}', TRESTContentType.ctAPPLICATION_JSON);
RESTRequest1.Execute;

Related

Sending a Word document over HTTP Post in Business Central without OnPrem scope

I need to be able to send a Word document over HTTP POST in Business Central. It needs to be sent to an Azure Function that takes in two Word documents.
How would I go about uploading the Word documents to Business Central (I Assume through UploadIntoStream) and then sending the files over HTTP?
You are correct, you need to upload the file into a stream and send it over HTTP. Depending on your azure function you need to either base64 encode it or send it as a binary. This should help you on your way.
The below is mockup code that tells you how to insert an uploaded file into a HTTP request but it does not contain a fully working, authenticated HTTP request. That depends on you Azure Function setup.
procedure SendUploadedFileToAPI() result: JsonObject
var
Base64Convert: Codeunit "Base64 Convert";
Instr: InStream;
jObject: JsonObject;
Client: HttpClient;
Response: HttpResponseMessage;
Content: HttpContent;
ContentHeaders: HttpHeaders;
UploadFilename: Text;
SelectFileLbl: Label 'Select a file';
CannotConnectErr: Label 'Cannot connect';
begin
UploadIntoStream(SelectFileLbl, '', 'All files (*.*)|*.*', UploadFilename, InStr);
// Use a json with base64
jObject.Add('file', Base64Convert.ToBase64(Instr));
Content.WriteFrom(Format(jObject));
Content.GetHeaders(ContentHeaders);
ContentHeaders.Clear();
ContentHeaders.Add('Content-Type', 'application/json');
// Or send as binary
Content.WriteFrom(Instr);
ContentHeaders.Add('Content-Type', 'application/json');
if not Client.Post('url', Content, Response) then
Error(CannotConnectErr);
end;

Cannot get values for query parameters when using POST in Delphi REST Server

I have written a REST server with an action POSTCameras to update. In the client i have te following
RESTClient.ResetToDefaults;
RESTClient.BaseURL := EditERServiceUrl.Text;
RESTRequest.Method := TRESTRequestMethod.rmGET;
RESTRequest.Resource := 'GetUpdateCameras';
RESTRequest.Params.AddItem('thislocn', '0000', TRESTRequestParameterKind.pkGETorPOST);
RESTRequest.Params.AddItem('cameras', '{"id":1,"name":"Camera 1"}',
RESTRequestParameterKind.pkGETorPOST);
RESTRequest.Execute;
If I use rmGET, I can easily extract the parameters in the REST server using
ThisStore = Request.QueryFields.Values['thislocn'];
ThisCamera = Request.QueryFields.Values['camera'];
If I use rmPOST, the Query Fields are blank.
I'm not sure what I have to do to get the query parameters from the TWebRequest.
You didn't specify what kind of server technology do you use, but I guess it's DataSnap (REST server).
In your REST client code you manually add parameters of kind pkGETorPOST to the request. This is BTW the default kind when adding a parameter via method AddItem, so you don't need to specify it at all and use an invariant with just 2 parameters function AddItem(const AName, AValue: string): TRESTRequestParameter; overload;.
The documentation for pkGETorPOST clearly states
pkGETorPOST: Sends the parameter as a URL parameter (for GET requests) or as a body parameter (for POST/PUT requests).
That means in case of GET request, the parameters are transferred in query string of the URL, while in case of POST request they're sent in the body as x-www-form-urlencoded string. To read such values from the request's body in server code you have to use property ContentFields instead.
ThisStore = Request.ContentFields.Values['thislocn'];
ThisCamera = Request.ContentFields.Values['camera'];
If you insist on sending parameters in URL even for POST requests then add them as pkQUERY.
pkQUERY: Sends the Parameter explicitly as a URL parameter (for all requests), in contrast to pkGETorPOST, when parameter location depends on the request type.
Unfortunatelly pkQUERY wasn't available prior to Delphi 10.3 Rio.

TRESTRequest: Is it possible to use custom media types in a POST request?

We have an API that expects our own vendor specific content type for example application/vnd.xxxx.custom.custom-data+json but looking through the source code of REST.Client it seems to always default to one of the ContentTypes in REST.Types for example when assigning ctNone in my body request it will default to ctAPPLICATION_X_WWW_FORM_URLENCODED.
I've tried assigning the content type directly to the TRESTClient.ContentType property but that gets overwritten by the TRESTRequest.ContentType value. I've also added the custom content type as a parameter on TRESTRequest which does get recognised but still appends ctAPPLICATION_X_WWW_FORM_URLENCODED on the end causing an invalid mime type exception.
begin
APIClient := TRESTClient.Create(API_URL);
APIRequest := TRESTRequest.Create(nil);
try
JsonToSend := TStringStream.Create(strJson, TEncoding.UTF8);
APIClient.Accept := 'application/vnd.xxxx.custom.custom-data+json';
// Below line gets overwritten
APIClient.ContentType := 'application/vnd.xxxx.custom.custom-data+json';
APIRequest.Client := APIClient;
APIRequest.Resource := 'ENDPOINT_URL';
APIRequest.Accept := 'application/vnd.xxxx.custom.custom-data+json';
APIRequest.AddParameter(
'Content-Type',
'application/vnd.xxxx.custom.custom-data+json',
pkHTTPHEADER,
[poDoNotEncode]
); // This includes the custom CT in the request but appends the preset one as well so in this case ctAPPLICATION_X_WWW_FORM_URLENCODED when ctNone is set
APIRequest.AddBody(JsonToSend, ctNone);
APIRequest.Method := rmPost;
try
APIRequest.Execute;
except
on E: Exception do
ShowMessage('Error on request: '#13#10 + e.Message);
end;
finally
JsonToSend.Free;
end;
end;
To me I would expect there to be a scenario where if a content type has been provided in the header parameters that it would use the one specified rather than any of the preset ones. However, an API exception is raised because an unknown media type was provided. The API exception reads:
Invalid mime type "application/vnd.xxxx.custom.custom-data+json, application/x-www-form-urlencoded": Invalid token character ',' in token "vnd.xxxx.custom.custom-data+json, application/x-www-form-urlencoded"
My understanding is it's recognising my custom content type provided in the params but is also still appending one of the preset content types from REST.Types in that request header causing it to fail. I would expect it to send the body with request header of just application/vnd.xxxx.custom.custom-data+json excluding application/x-www-form-urlencoded.
Aparently TRestCLient trying to act too smart in your scenario. However there is a regular way around that. The key is:
to add single content to request body that must not be any of ctNone, ctMULTIPART_FORM_DATA or ctAPPLICATION_X_WWW_FORM_URLENCODED.
to override Content-Type using custom header value.
Sample code:
uses
System.NetConsts;
RESTClient1.BaseURL := 'https://postman-echo.com/post';
RESTRequest1.Method := rmPOST;
RESTRequest1.Body.Add('{ "some": "data" }', ctAPPLICATION_JSON);
RESTRequest1.AddParameter(sContentType, 'application/vnd.hmlr.corres.corres-data+json',
pkHTTPHEADER, [poDoNotEncode]);
RESTRequest1.Execute;
The response from echo service is:
{
"args":{
},
"data":{
"some":"data"
},
"files":{
},
"form":{
},
"headers":{
"x-forwarded-proto":"https",
"host":"postman-echo.com",
"content-length":"18",
"accept":"application/json, text/plain; q=0.9, text/html;q=0.8,",
"accept-charset":"UTF-8, *;q=0.8",
"content-type":"application/vnd.hmlr.corres.corres-data+json",
"user-agent":"Embarcadero RESTClient/1.0",
"x-forwarded-port":"443"
},
"json":{
"some":"data"
},
"url":"https://postman-echo.com/post"
}
Pay attention to echoed headers, especially Content-Type of course. I tested the sample in Delphi 10.2 Tokyo, so hopefully it will also work in XE8.
Edit
The behaviour you observe is a bug (RSP-14001) that was fixed in RAD Studio 10.2 Tokyo.
There are various ways to resolve that. To name a few:
Adapt your API to discard secondary mime type.
Change your client implementation to TNetHttpClient instead, if you can give up all additional benefits that TRestClient provides.
Upgrade to RAD Studio 10.2+.
Hack it! This option is however strongly discouraged, but it can help you better understand TRestClient implementation details.
The easiest way to hack it would be to patch method TCustomRESTRequest.ContentType (note we're talking about invariant with a single argument) to return ContentType of a parameter if its AParamsArray argument contains single parameter of kind pkREQUESTBODY. This would allow us to add body to request of type ctNone so that the patched method would return ctNone as well and this would effectively prevent appending another value to Content-Type header.
Another option would be to patch method TRESTHTTP.PrepareRequest to prefer custom Content-Type header before inferred content type of the request. This is BTW how the current implementation works after it was fixed in RAD Studio 10.2 Tokyo. This logic is also applied to other headers - Accept, Accept-Charset, Accept-Encoding, User-Agent. Patching method TRESTHTTP.PrepareRequest is slightly harder to achieve, because it has private visibility.
The hardest option would be patching TWinHTTPRequest.SetHeaderValue to discard secondary content type value. This is also the most dangerous one, because it would have impact to anything HTTP related (that relies on THTTPClient) in your application. It's also hard, however not impossible, to patch the class, because it's completely hidden in the implementation section of System.Net.HttpClient.Win.pas. This is a huge shame, because it also prevents you from creating custom subclasses. Maybe for a good reason .. who knows ;)

How to send multiple json body using jmeter?

I have written a REST API and now my requirement is to send the multiple JSON body to the API using POST method from JMeter. I have a csv file with four values(1,2,3,4). And in each of the four files I have the JSON body. I used :
Step-1) added the csv file to jmeter and create a reference and named it JSON_FILE
Step-2) ${__FileToString(C:Path_to_csv_file/${__eval(${JSON_FILE})}.txt,,)}
But from this I am able to access only first file i.e which is named with one. How do I send the body of all file to the API?
Help is highly appreciated.
You won't be able to use CSV Data Set Config as it will read the next value for each thread (virtual user) and/or Thread Group iteration.
If your requirement is to send all the files bodies at once you can go for an alternative approach
Add JSR223 PreProcessor as a child of the HTTP Request sampler which you use for sending the JSON payload
Put the following code into "Script" area:
def builder = new StringBuilder()
new File('/path/to/plans.csv').readLines().each { line ->
builder.append(new File(line).text).append(System.getProperty('line.separator'))
}
sampler.getArguments().removeAllArguments()
sampler.addNonEncodedArgument('', builder.toString(), '')
sampler.setPostBodyRaw(true)
the above code iterates through entries in plans.csv file, reads the file contents into a string and concatenates them altogether. Once done it sets the HTTP Request sampler body data to the generated cumulative string.
Check out The Groovy Templates Cheat Sheet for JMeter to learn more and what else could be achieved using Groovy scripting in JMeter.
Use Body data as follows in HTTP Sampler:
{__FileToString(${JSON_FILE},,)}
You have to put all the file path in your plan.csv file. At each line, there should be a file path.
Example:
Suppose, you have 4 files with JSON body which you want to use in your HTTP sampler.
Give the file path of these 4 files in your CSV file which is plan.csv. Each line contains a file path like this:
/User/file/file1.json
/User/file/file2.json
/User/file/file3.json
/User/file/file4.json
Now, in your CSV data set config, Use the proper file name of CSV file which contains all the file path and give it a variable name like JSON_FILE.
Now, Use {__FileToString(${JSON_FILE},,)} this line in your Body data. Also use the loop count value accordingly.

Can I fake uploaded image filesize?

I'm building a simple image file upload form. Programmatically, I'm using the Laravel 5 framework. Through the Input facade (through Illuminate), I can resolve the file object, which in itself is an UploadedFile (through Symfony).
The UploadedFile's API ref page (Symfony docs) says that
public integer | null getClientSize()
Returns the file size. It is extracted from the request from which the
file has been uploaded. It should not be considered as a safe
value. Return Value integer|null The file size
What will be these cases where the uploaded filesize is wrongly reported?
Are there known exploits using this?
How can the admin ensure this is detected (and hence logged as a trespass attempt)?
That method is using the "Content-Length" header, which can easily be forged. You'll want to use the easy construct $_FILES['myfile']['size']. As an answer to another question has already stated: Can $_FILES[...]['size'] be forged?
This value checks the actual size of the file, and is not modified by the provided headers.
If you'd like to check for people misbehaving, you can simply compare the content-length header to your $_FILES['myfile']['size'] value.