How to upload an audio file in apex code via MultiPart FormData - apex

I am working on a project in which I need to upload an audio file to the External webservice via Apex code. So I am using multipart form data to do the same but every time I am getting an error i.e ActionController::UnknownFormat
I am trying to do the same via multipart form data in apex.
String body = '--' + boundary + '\r\n';
body += 'Content-Disposition: form-data; name="recording[s3_location]"; filename="abc.mp3"\r\nContent-Type: audio/mpeg\r\n\r\n';
body += 'F:\Test.mp3\r\n';
body += '--' + boundary + '--';
Expected result code: 201(success)
Actual Result Code: 500(ActionController::UnknownFormat)
I tried with postman its working fine but whenever i am trying with my code i am getting the exception. Please help me. Thanks in advance :)

Here is a working solution for sending one file at a time:
// form boundary must be a string that will very likely never appear again in the form
public static final String BOUNDARY = '----FormBoundary4Amf13kZd';
public static final String EXTRABOUNDARY = '--' + BOUNDARY;
/**
*
*
* #param file The file that will be uploaded.
* #param contentType The content-type of the file. Will default to application/octet-stream.
* #param filename Name to give the file.
* #param formDataMap Any extra metadata that you want to send.
*
* #return Blob of the complete multipart form.
*/
public static Blob multiPartFormDataBody(Blob file, String contentType, String filename, Map<String, String> metadataMap) {
contentType = contentType == '' ? 'application/octet-stream' : contentType;
String bodyStartHex = EncodingUtil.convertToHex(Blob.valueOf(EXTRABOUNDARY + '\r\nContent-Disposition: form-data; name=\"file\"; filename="' + filename + '"\r\nContent-Type: ' + contentType + '\r\n\r\n'));
String bodyEndHex = EncodingUtil.convertToHex(Blob.valueOf(EXTRABOUNDARY + '--\r\n'));
bodyStartHex += EncodingUtil.convertToHex(file);
if (metadataMap != null && metadataMap.size() != 0) {
String formElements = '\r\n';
for (String key : metadataMap.keySet()) {
formElements += EXTRABOUNDARY + '\r\nContent-Disposition: form-data; name=\"' + key + '\"\r\n\r\n' + metadataMap.get(key) + '\r\n';
}
bodyStartHex += EncodingUtil.convertToHex(Blob.valueOf(formElements));
} else {
// the extra '--' at the end is crucial
bodyEndHex = EncodingUtil.convertToHex(Blob.valueOf('\r\n' + EXTRABOUNDARY + '--'));
}
return EncodingUtil.convertFromHex(bodyStartHex + bodyEndHex);
}
The important thing to note here is that you MUST convert your entire mutltipart-form string to a hexidecimal string and then back to Blob before sending to an external webservice, because you cannot mix the file binary and the UTF-8 boundary strings. You first get the Blob of the boundaries and metadata and convert them to hex. Then you take your input file Blob and convert it to hex. Then you combine the hex strings together and convert them back to a Blob to be sent via HttpRequest:
public static HttpResponse sendMultiPartForm(Blob file, String contentType, String filename, Map<String, String> metadataMap) {
HttpRequest request = new HttpRequest();
Http http = new Http();
request.setEndpoint('https://example.com/api/upload');
request.setMethod('POST');
// Boundary must be set to exactly what was set before
request.setHeader('Content-Type', 'multipart/form-data; boundary=' + BOUNDARY);
request.setBodyAsBlob(multiPartFormDataBody(file, contentType, filename, metadataMap));
request.setHeader('Content-Length', String.valueOf(request.getBodyAsBlob().size()));
return http.send(request);
}
I left out explanation for the carriage returns and newlines, because they need to be so exact here that honestly everyone should just copy and paste them. It took a lot of trial and error to get it right.

Related

How-to get the code for downloading metadata along with images in IBM content manager

I am trying to find the code in java API which works with cm IBM.. the sample code is there but it is just for logging in.. can anyone help to get the code to download the images along with metadata
as you said you have basic connection code, use the below function to download the document..
public String retrieveDocument(CMBConnection connection, CMBItem item)
throws CMBException, IOException, Exception
{
// Get an instance of data management bean
CMBDataManagement dataManagement = connection.getDataManagement();
// Set the current data item
dataManagement.setDataObject(item);
// Retrieve the original file name
CMBObject object = dataManagement.getContent(0);
String inputFileName = object.getOriginalFileName();
// Parse the file name from the full path
int pos=inputFileName.lastIndexOf("\\");
inputFileName = inputFileName.substring(pos+1);
// Write the document content to a new file
String fileName = System.getProperty("user.dir")
+ File.separator + inputFileName;
System.out.println("Output file name " + fileName);
FileOutputStream fileoutstream = new FileOutputStream(fileName);
fileoutstream.write(dataManagement.getContent(0).getData());
fileoutstream.close();
// Return file name
return fileName;
}

can't get ADLS Gen2 REST continuation token to work

I'm trying to retrieve list of files and folders form ADLS Gen2. I can get the first 5000 items, but when I use continuation to get the rest (about 17,000 items or so), I get Error 403 (Forbidden). According to documentation, I add the continuation token to URI and to Canonicalized Resource in signature string. However, I cannot get it to work.
I've read the documentation on ADLS Gen2 REST calls and whatever I could find on this, and I can't figure out the issue.
var date = System.DateTime.UtcNow.ToString("R");
string toSign = DefaultSignatureString(date);
toSign +=
$"/{storageaccountname}/{filesystemname}" + "\n" +
$"directory:{dir}" +"\n" +
"recursive:true" + "\n" +
"resource:filesystem";
var signedSignature = SignData(accessKey, toSign);
var uri = $"https://{storageaccountname}.dfs.core.windows.net/{filesystemname}?directory={dir}&recursive=true&resource=filesystem";
HttpWebResponse response = GetWebResponse(storageaccountname, date, signedSignature, uri);
var token_continuation = response.Headers["x-ms-continuation"];
//I get the token_continuation and repeat the previous steps, adding the continuation part:
while (token_continuation != null)
{
date = System.DateTime.UtcNow.ToString("R");
toSign = DefaultSignatureString(date);
toSign +=
$"/{storageaccountname}/{filesystemname}" + "\n" +
$"continuation:{token_continuation}" + "\n" +
$"directory:{dir}" + "\n" +
"recursive:true" + "\n" +
"resource:filesystem";
signedSignature = SignData(accessKey, toSign);
uri = $"https://{storageaccountname}.dfs.core.windows.net/{filesystemname}?directory={dir}&recursive=true&resource=filesystem&continuation={token_continuation}";
response = GetWebResponse(storageaccountname, date, signedSignature, uri);
token_continuation = response.Headers["x-ms-continuation"];
}
//this is my GetWebResponse method
private static HttpWebResponse GetWebResponse(string storageaccountname, string date, string signedSignature, string uri, string continuation = null)
{
WebRequest request = WebRequest.Create(uri);
if (continuation != null)
{
request.Headers.Add($"x-ms-continuation:{continuation}");
}
request.Headers.Add($"x-ms-date:{date}");
request.Headers.Add($"x-ms-version:2018-11-09");
request.Headers.Add($"Authorization:SharedKey {storageaccountname}:{signedSignature}");
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
return response;
}
As I said, I get the first response OK; when I get into while loop, I get the error. What am I doing wrong?
In most of the cases (if not all) continuation token returns with == at the end, which messes up the URI.
For URIs, escape them by replacing == with %3D%3D.
For canonicalized resources, leave the string as is.

Generate and download file from Word dialog

Is there a way to generate a file with some JSON content and prompt the user with a saveAs dialog.
This is from an open dialog in word.
The object could be like (will be quite a lot bigger in practice)
var obj = {a: 1, b: 2, c: 'qwerty'}
I tried to uri encode and using window.open without any luck.
content = JSON.stringify(obj);
uriContent = "data:application/octet-stream," + encodeURIComponent(content);
newWindow = window.open(uriContent, 'filename');
I did this for XML, but it requires some back-end and front-end work. On the backend, you need some ASPX like this:
string filename = "export.xml";
byte[] data = Convert.FromBase64String(Request.QueryString[0].Replace("\"", ""));
string decodedString = System.Text.Encoding.UTF8.GetString(data);
// set the http content type to "APPLICATION/OCTET-STREAM
Response.ContentType = "APPLICATION/OCTET-STREAM";
System.String disHeader = "Attachment; Filename=\"" + filename + "\"";
Response.AppendHeader("Content-Disposition", disHeader);
Response.Flush();
Response.Write(decodedString);
I called mine "download.aspx." Then on the front-end, you have to use AJAX. This created a form region on the page and the forces a submit of the form to start the download:
// Helper function to load a form and then send the post results to a
// new window so that we can get the download button
function ajax_download(url, data, input_name) {
try {
$('#form-div').append("<form method='GET' action='" +
url + "' target='_blank'>" +
"<input type=hidden name='" + input_name + "' value='" +
JSON.stringify(data) + "'/></form>");
$('#form-div').find('form').submit();
} catch (err) {
showNotification("error", err.description);
}
}
To call it, you simply make this this call from your JavaScript code where you want it:
xml = "<xml><data>12345</data></xml>";
ajax_download('./Download.aspx', btoa(xml), 'xml');
In this case, mine is targeting XML and always creates a file called "export.xml", but you can adjust it as needed.

Calculating an oauth signature

I am trying something a little specific, namely trying to call a REST API. I have been following these instructions.
I have been very careful to ensure that I am creating the "Signature base string" correctly. They define it to be created like this:
(HTTP Method)&(Request URL)&(Normalized Parameters)
You can double check if need be in my code, but I am very sure that it is fine.
The problem that I am having is creating what they call the "oauth signature" and mine isn't matching theirs. They it should be created like this:
Use the HMAC-SHA1 signature algorithm as defined by the [RFC2104] to sign the request where text is the Signature Base String and key is the concatenated values of the Consumer Secret and Access Secret separated by an '&' character (show '&' even if Access Secret is empty as some methods do not require an Access Token).
The calculated digest octet string, first base64-encoded per [RFC2045], then escaped using the [RFC3986] percent-encoding (%xx) mechanism is the oauth_signature.
I express this in my code like so:
var oauthSignature = CryptoJS.HmacSHA1(signatureBaseString, sharedSecret+"&");
var oauthSignature64 = encodeURIComponent(CryptoJS.enc.Base64.stringify(oauthSignature));
console.log("hash in 64: " + oauthSignature64);
I am using Google's CryptoJS library. I take the signature base string as the text, I then take my consumer secret as the key concatenated with "&", I have no Access key and it isn't required but that is OK. I then base 64 encode the result of that hash, after which I URI encode it, please could some guys sanity check my understanding of that and my usage/expressing of it in code using this library, I think this is where my problem is.
Here is my full code:
var fatSecretRestUrl = "http://platform.fatsecret.com/rest/server.api";
var d = new Date();
var sharedSecret = "xxxx";
var consumerKey = "xxxx";
//this is yet another test tyring to make this thing work
var baseUrl = "http://platform.fatsecret.com/rest/server.api?";
var parameters = "method=food.search&oauth_consumer_key="+consumerKey+"&oauth_nonce=123&oauth_signature_method=HMAC-SHA1&oauth_timestamp="+getTimeInSeconds()+"&oauth_version=1.0&search_expression=banana";
var signatureBaseString = "POST&" + encodeURIComponent(baseUrl) + "&" + encodeURIComponent(parameters);
console.log("signature base string: " + signatureBaseString);
var oauthSignature = CryptoJS.HmacSHA1(signatureBaseString, sharedSecret+"&");
var oauthSignature64 = encodeURIComponent(CryptoJS.enc.Base64.stringify(oauthSignature));
console.log("hash in 64: " + oauthSignature64);
var testUrl = baseUrl+"method=food.search&oauth_consumer_key=xxxx&oauth_nonce=123&oauth_signature="+oauthSignature64+"&oauth_signature_method=HMAC-SHA1&oauth_timestamp="+getTimeInSeconds()+"&oauth_version=1.0&search_expression=banana";
console.log("final URL: " + testUrl);
var request = $http({
method :"POST",
url: testUrl
});
I have taken care to ensure that the parameters that I am posting are in lexicographical order and I am very sure that it is correct.
The response that I am getting back is:
Invalid signature: oauth_signature 'RWeFME4w2Obzn2x50xsXujAs1yI='
So clearly either
I haven't understood the instructions provided in the API
Or I have understood them but I haven't expressed them in that way in my code
Or both of the above
Or I have made some subtle mistake somewhere that I can't see
I would really appreciate a sanity check, this has taken a while.
well...I did it, but not the way that I thought I would end up doing it, I spent hours trying it out with angular then JQuery, then finally I tried Node JS and it worked, here are two working examples, one with food.get and another with foods.search
food.get example
var rest = require('restler'),
crypto = require('crypto'),
apiKey = 'xxxx',
fatSecretRestUrl = 'http://platform.fatsecret.com/rest/server.api',
sharedSecret = 'xxxx',
date = new Date;
// keys in lexicographical order
var reqObj = {
food_id: '2395843', // test query
method: 'food.get',
oauth_consumer_key: apiKey,
oauth_nonce: Math.random().toString(36).replace(/[^a-z]/, '').substr(2),
oauth_signature_method: 'HMAC-SHA1',
oauth_timestamp: Math.floor(date.getTime() / 1000),
oauth_version: '1.0'
};
// make the string...got tired of writing that long thing
var paramsStr = '';
for (var i in reqObj) {
paramsStr += "&" + i + "=" + reqObj[i];
}
// had an extra '&' at the front
paramsStr = paramsStr.substr(1);
var sigBaseStr = "GET&"
+ encodeURIComponent(fatSecretRestUrl)
+ "&"
+ encodeURIComponent(paramsStr);
// no access token but we still have to append '&' according to the instructions
sharedSecret += "&";
var hashedBaseStr = crypto.createHmac('sha1', sharedSecret).update(sigBaseStr).digest('base64');
// Add oauth_signature to the request object
reqObj.oauth_signature = hashedBaseStr;
rest.get(fatSecretRestUrl, {
data: reqObj,
}).on('complete', function(data, response) {
console.log(response);
console.log("DATA: " + data + "\n");
});
foods.search example
var rest = require('restler'),
crypto = require('crypto'),
apiKey = 'xxxx',
fatSecretRestUrl = 'http://platform.fatsecret.com/rest/server.api',
sharedSecret = 'xxxx',
date = new Date;
// keys in lexicographical order
var reqObj = {
method: 'foods.search',
oauth_consumer_key: apiKey,
oauth_nonce: Math.random().toString(36).replace(/[^a-z]/, '').substr(2),
oauth_signature_method: 'HMAC-SHA1',
oauth_timestamp: Math.floor(date.getTime() / 1000),
oauth_version: '1.0',
search_expression: 'mcdonalds' // test query
};
// make the string...got tired of writing that long thing
var paramsStr = '';
for (var i in reqObj) {
paramsStr += "&" + i + "=" + reqObj[i];
}
// had an extra '&' at the front
paramsStr = paramsStr.substr(1);
var sigBaseStr = "POST&"
+ encodeURIComponent(fatSecretRestUrl)
+ "&"
+ encodeURIComponent(paramsStr);
// again there is no need for an access token, but we need an '&' according to the instructions
sharedSecret += "&";
var hashedBaseStr = crypto.createHmac('sha1', sharedSecret).update(sigBaseStr).digest('base64');
// Add oauth_signature to the request object
reqObj.oauth_signature = hashedBaseStr;
rest.post(fatSecretRestUrl, {
data: reqObj,
}).on('complete', function(data, response) {
console.log(response);
console.log("DATA: " + data + "\n");
});
really sorry to anyone using Angular or JQuery, if I ever have a spare minute or two I will try it with angular, also anyone using angular if you get CORS related errors just start chrome like this:
chromium-browser --disable-web-security - I do this on terminal
or add that extension to some chrome shortcut on windows, just as a quick work around, hope it helps anybody out there.

Unable to get Data from vendor entity using filter

When I am trying with Date I am able tp get data from filter. But when I am trying to get data from filter using DateTime, I am unable to get Data. Below is my request body:
Filter=LastUpdatedTime :AFTER: 2013-06-07T15:00:00-0700 :AND: LastUpdatedTime :BEFORE: 2013-06-07T16:56:35-0700&PageNum=1&ResultsPerPage=100
and code:
requestBody = String.Format(requestBody, pageNumber, pageSize, After.ToString("yyyy-MM-ddTHH:mm:ss"), Before.ToString("yyyy-MM-ddTHH:mm:ss"));
HttpWebRequest httpWebRequest = WebRequest.Create(commonService.ServiceContext.BaseUrl + "vendors/v2/" + commonService.ServiceContext.RealmId) as HttpWebRequest;
httpWebRequest.Method = "POST";
httpWebRequest.ContentType = "application/x-www-form-urlencoded";
string oAuthHeader = GetDevDefinedOAuthHeader(context, httpWebRequest, requestBody);
httpWebRequest.Headers.Add("Authorization", oAuthHeader);
requestXML.Append(requestBody);
UTF8Encoding encoding = new UTF8Encoding();
byte[] content = encoding.GetBytes(requestXML.ToString());
Please tell me which format of Datetime do I need to send?
This is the data format you need to use:
<StartCreatedTMS>2009-11-17T22:35:08.0Z</StartCreatedTMS>
<EndCreatedTMS>2010-01-08T20:54:56.0Z</EndCreatedTMS>
thanks
Jarred