gmail add on multipart/form-data app script - google-apps-script

i'm building a gmail add on application to read a current email and retrieve the gmail attachment associated with the email.
Once I get the blob of the attachment I try to upload it to my API via a multipart/form-data post request. Currently, my request is failing with a 500 while my postman request work just fine.
my back end is giving the following exception:
Exception has been thrown by the target of an invocation. Either BinaryRead, Form, Files, or InputStream was accessed before the internal storage was filled by the caller of HttpRequest.GetBufferedInputStream.
with postman I don't get any error.
Not sure if i'm creating the request in app script currently. Here is my code.
var blob =
Utilities.newBlob(parsedBlob,payload.contentType,payload.name);
Logger.log('encode url ' + encodedUrl)
var metadata = {
'account_ref.id': payload.metaData.account_ref.id,
'matter_ref.id': payload.metaData.matter_ref.id,
description: "uploaded from gmail add on.",
content_type: payload.contentType,
file_name: 'test.png',
size: JSON.parse(payload.size).toString(),
};
for (var i in metadata) {
data += "--" + boundary + "\r\n";
data += "Content-Disposition: form-data; name=\"" + i + "\"
\r\n\r\n" + metadata[i] + "\r\n";
}
data += "--" + boundary + "\r\n";
data += "Content-Disposition: form-data; name=\"\"; filename=\"" +
blob.getName() + "\"\r\n";
data += "Content-Type:" + blob.getContentType() + "\r\n\r\n";
var payload = Utilities.newBlob(data).getBytes()
.concat(blob.getBytes())
.concat(Utilities.newBlob("\r\n--" + boundary + "--\r\n").getBytes());
var headers = {
Authorization: Utilities.formatString("Bearer %s", this.oauthService.getAccessToken())
};
var options = {
method: "post",
contentLength: blob.getBytes().length,
payload: payload,
contentType : "multipart/form-data;charset=utf-8; boundary=--" +
boundary,
muteHttpExceptions: true,
headers: headers,
};
Anything i'm doing wrong in preparing the payload?

Related

Google App Script fetch request with Digest auth issue

I am trying to retrieve some info from the camera system DVR from HIK Vision with GAS' UrlFetchApp. I am able to perform a successful request with Postman but having trouble getting this to work with the Google App Script's UrlFetchApp.fetch. I confirmed that my Digest calculation is correct. I did that by substituting values for nonce, cnonce, qop etc in my GAS with the ones which were in Postman and got the same Response string as the Postman did. See images of the Postman below:
Postman gets a 200 response and all the data that I need.
The way my app works is it sends a first request and gets 401 response. Then it sends a second request with the auth header calculated from the data received from the first request. I always get 401 no matter what I do. I played with the double quotes in the headers properties etc.
Here's the code:
function getRecordedDaysCount(){
const url = 'my_server_url';
const userName = 'user';
const pass = "password";
const uri = "/ISAPI/ContentMgmt/record/tracks/101/dailyDistribution";
const method = "POST"
const updatedUrl = url + uri;
let year = '2021';
let month = '03';
//payload required by the server for this request
let payload = '<?xml version: "1.0" encoding="utf-8"?><trackDailyParam><year>' + year + '</year><monthOfYear>' + month + '</monthOfYear></trackDailyParam>';
let options = {
"method" : method,
"muteHttpExceptions": true,
"headers":{
"Accept": "application/xml, text/plain, */*",
}
}
let data = UrlFetchApp.fetch(updatedUrl, options);
if(data.getResponseCode() == 401){
let wwwAuthenticate = data.getAllHeaders()["WWW-Authenticate"];
// Example WWWAuthenticate
// "Digest realm="493b21e13dddb4ef7745edaa", domain="::", qop="auth",
// nonce="17fbf10682dc4a7ceb04206cbcd95d8d:1616709300571", opaque="", algorithm="MD5", stale="FALSE""
let authData = wwwAuthenticate.split(',');
let authType = authData[0].split(' ')[0]; // Digest
if(authType === "Digest"){
let realm = authData[0].split('"')[1]; // "493b21e13dddb4ef7745edaa"
let qop=authData[2].split('"')[1]; // "auth"
let nonce = authData[3].split('"')[1]; // "17fbf10682dc4a7ceb04206cbcd95d8d:1616709300571"
let algorithm = authData[5].split('"')[1]; // "MD5"
let nc="00000001";
let cnonce= new Date().getTime().toString(16);
let hash1 = signMD5(userName +':'+ realm +':'+ pass);
let hash2 = signMD5(method+':'+ uri);
let response = signMD5(hash1+':'+nonce+':'+nc+':'+cnonce+':'+qop+':'+hash2);
let digestAuth = "Digest username=\"" + userName + "\"" +
", realm=\"" + realm + "\"" +
", nonce=\"" + nonce + "\"" +
", uri=\"" + uri + "\"" +
", qop=auth" +
", nc=" + nc +
", algorithm=\"MD5\"" +
", cnonce=\"" + cnonce + "\"" +
", response=\"" + response + "\"";
let headers = {
"Content-Type": "text/plain", // copied from Postman
"Accept-Encoding": "gzip, deflate, br", // copied from Postman
"Authorization": digestAuth,
}
let options = {
"method" : method,
"muteHttpExceptions": true,
"headers": headers,
"payload": (payload)
}
logUrlFetch(updatedUrl, options);
}
}
}
function signMD5(message){
let signature = Utilities.computeDigest(
Utilities.DigestAlgorithm.MD5,
message,
Utilities.Charset.UTF_8);
let signatureStr = '';
for (i = 0; i < signature.length; i++) {
let byte = signature[i];
if (byte < 0)
byte += 256;
let byteStr = byte.toString(16);
// Ensure we have 2 chars in our byte, pad with 0
if (byteStr.length == 1) byteStr = '0'+byteStr;
signatureStr += byteStr;
}
Logger.log(signatureStr);
return signatureStr;
}
function logUrlFetch(url, opt_params) {
let params = opt_params || {};
params.muteHttpExceptions = true;
let request = UrlFetchApp.getRequest(url, params);
Logger.log('Request: >>> ' + JSON.stringify(request));
let response = UrlFetchApp.fetch(url, params);
Logger.log('Response Code: <<< ' + response.getResponseCode());
Logger.log('Response text: <<< ' + response.getContentText());
if (response.getResponseCode() >= 400) {
throw Error('Error in response: ' + response);
}
return response;
}
These are the logs I'm getting:
headers after the first request
log after the second request
I compared the headers in the Postman and my app multiple times and they are mostly the same with some differences. The UrlFetchApp.fetch adds a few headers to the request however I added them also in Postman and it still worked so I concluded these additional headers were not a problem. Also the UrlFetchApp.fetch sends a request method as 'post' (lower case) and the Postman uses all caps - 'POST'. The http method is a part of the Digest HA2 calculation, which is case sensitive. This was the only idea that I had for why my requests didn't work. I changed my app's method to lower case 'post' for the digest calculation and for the headers and this did not help.
Is there a way to send requests in the Google App Script other than UrlFetchApp.fetch?
Thanks a lot!

Xero API signature invalid using GAS

I am accessing API using Google Apps Script. I am looking for a https://developer.xero.com/documentation/api/reports#TrialBalance
I have tried with the API code as but I get signature invaid as reposnse result [19-09-03 19:45:46:402 IST] oauth_problem=signature_invalid&oauth_problem_advice=Failed%20to%20validate%20signature
function doGet(e) {
getTrialBalances();
}
function getTrialBalances() {
var oauth_nonce = createGuid();
var oauth_timestamp = (new Date().valueOf() / 1000).toFixed(0);
var CONSUMER_KEY = 'B7D5YA8D1HWHUZIGXL1AZS44N'
var PEM_KEY = '-----BEGIN RSA PRIVATE KEY-----' +
'ANIICXAIBAAKBgQC2WiSrkljVAZIgNUe/nBZ+PGJzauBJ6szlzPow1XoySkVikswui1IX4wUzgLmvnCmnQkRPgA43oiZqmK1H68MvirYzQkMa3sETViQAOiRPOrDEUTkemKiDXpaIKedD8T6/P9qzgtgU5hlP/R45POanIuNFvYPdpkm2yybOmI+1TwIjAQABAoGADt/3kc9UU7vXEa2G9shixVVjqoqTVTREFpLL7ePcHfIVCt9yrHFM9wnbyMG9uRZRIyDmbpumClROJImuADxc6reamXdTMX0OwEPogAREnY2diadjVjicoMYYEcdbb6pgDSOWcYtamNmzD5tkPI0bPFU+fTdpzGCOCECQQDvZTha0SRcCZPZipCs7PtAOWtMP1FBe140+cvsWiq2eHMmYDtIi7Mx210i3wzz4+Izl4jXeICKprppaBlJxSFZAkEAwwALfSnpqWeop86nnUICOPmksbK2rTtNVd+WGiAK4reUDJArOOXdDm7fYqppQNA35hxcRmvxeKK7jSYLQYHO5wJAeLFubRL+IszNVqLud9Buh52rQ+C0RbA9+bVqozl+SUqGu3VOzi9oY5114kvUCu38MAiY/BELtVuDpfrOrQuO2QJAHrZZGOOLC8VpyNRBjgEhfHvFNr+hCfO3IHlQmNjHHiIvzTK/u/xoLqfDwzR30194DmQVHHpP0+I9i+OcDjs1rQJBAJMY6h4QdYSFpTPxUOPA/s1lKVvJUIzgzX6oMfvc4TDb0RCz4nCvjJ1NEqPjveB6ze5TzC8BzfRW/aUh49vmgRA=' +
'-----END RSA PRIVATE KEY-----';
var payload = '';
var URL = 'https://api.xero.com/api.xro/2.0/Reports/TrialBalance';
var signatureBase = "GET" + "&" +
encodeURIComponent(URL) + "&" +
encodeURIComponent('date=2019-02-01') + "&" +
encodeURIComponent("oauth_consumer_key=" + CONSUMER_KEY +
"&oauth_nonce=" + oauth_nonce + "&oauth_signature_method=RSA-SHA1&oauth_timestamp=" +
oauth_timestamp + "&oauth_token=" + CONSUMER_KEY + "&oauth_version=1.0");
var rsa = new RSAKey();
rsa.readPrivateKeyFromPEMString(PEM_KEY);
var hashAlg = "sha1";
var hSig = rsa.signString(signatureBase, hashAlg);
var oauth_signature = encodeURIComponent(hextob64(hSig));
var authHeader = "OAuth oauth_token=\"" + CONSUMER_KEY + "\",oauth_nonce=\"" + oauth_nonce +
"\",oauth_consumer_key=\"" + CONSUMER_KEY + "\",oauth_signature_method=\"RSA-SHA1\",oauth_timestamp=\"" +
oauth_timestamp + "\",oauth_version=\"1.0\",oauth_signature=\"" + oauth_signature + "\"";
var headers = {
"Authorization": authHeader,
"Accept": "application/json"
};
var options = {
"headers": headers,
'method': 'GET',
'payload': payload,
'muteHttpExceptions': true,
};
var requestURL = URL + '?date=2019-02-01';
var response = UrlFetchApp.fetch(requestURL, options);
var responseXml = response.getContentText();
Logger.log(responseXml);
}
function createGuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0,
v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16)
});
}
For RSA signing I have used https://github.com/csi-lk/google-app-script-xero-api/blob/master/jsrsasign.gs
UPDATE 2:
I code this, but still not able to get the result
var signatureBase = encodeURIComponent("GET" + "&" + URL + "&" + 'date=2019-02-01' + "&" + "oauth_consumer_key=" + CONSUMER_KEY +
"&oauth_nonce=" + oauth_nonce + "&oauth_signature_method=RSA-SHA1&oauth_timestamp=" +
oauth_timestamp + "&oauth_token=" + CONSUMER_KEY + "&oauth_version=1.0");
Before reading the rest of my answer can you please urgently reset your applications Consumer Key/Secret, as well as create and upload a new public certificate to the developer portal as you've provided both in your question.
At least one issue you're running into that I can spot is how you're building up the signature base string.
Only the initial & should be left unencoded, however the rest of them in the signature base string should be encoded. It looks like the & after the encoded URL and encoded date query param are being left unencoded.
Edit:
The following two lines are leaving the &s out ouf encoding, but they need to be included in the uri encoding
encodeURIComponent(URL) + "&" +
encodeURIComponent('date=2019-02-01') + "&" +

Google App Script - Telegram Bot - setChatPhoto

i want a telegram bot that set a specific photo as a group image. Here you can see the bot api about that method(https://core.telegram.org/bots/api#setchatphoto).
I'm using this function but it doesn't work.
function myFunction(){
var chatId = SUPERGOUP_CHAT_ID
var photo = {
file_id: FILE_ID,
file_size: 425707,
file_path: 'documents/file_2.png'
}
var data = {
method: "post",
payload: {
method: "setChatPhoto",
chat_id: String(zone),
photo: JSON.stringify(photo)
}
}
UrlFetchApp.fetch('https://api.telegram.org/bot' + token + '/', data);
}
Telegram answer to the function up here:
{"ok":false,"error_code":400,"description":"Bad Request: photo should be uploaded as an InputFile"}
Searching online i found this solution but it also doesn't work.
function uploadFile() {
var group = SUPERGOUP_CHAT_ID
var boundary = "labnol";
var blob = DriveApp.getFileById('FILE_ID').getBlob();
var attributes = "{\"name\":\"asd.jpg\", \"parent\":{\"id\":\"FOLDER_ID\"}}";
var requestBody = Utilities.newBlob(
"--"+boundary+"\r\n"
+ "Content-Disposition: form-data; name=\"attributes\"\r\n\r\n"
+ attributes+"\r\n"+"--"+boundary+"\r\n"
+ "Content-Disposition: form-data; name=\"file\"; filename=\""+blob.getName()+"\"\r\n"
+ "Content-Type: " + blob.getContentType()+"\r\n\r\n").getBytes()
.concat(blob.getBytes())
.concat(Utilities.newBlob("\r\n--"+boundary+"--\r\n").getBytes());
var options = {
method: "post",
contentType: "multipart/form-data; boundary="+boundary,
payload: {
method: "setChatPhoto",
chat_id: String(group),
photo: requestBody
}
};
var request = UrlFetchApp.fetch('https://api.telegram.org/bot' + token , options);
Logger.log(request.getContentText());
}
Telegram answer to the function up here:
Timeout: https://api.telegram.org/bot_TOKEN
Can you maybe find a solution?
Thanks in advance.

gmail api error in apps scripting + Insufficient permissions error code 403 domain global

I have the Gmail API enabled and looked up all questions on stackoverflow to make sure I am not missing anything. Even after conforming all the options for the gmail API, I am getting the following error:
{
"error": {
"errors": [
{
"domain": "global",
"reason": "insufficientPermissions",
"message": "Insufficient Permission"
}
],
"code": 403,
"message": "Insufficient Permission"
}
}
I am out of options and would like to understand where I am going wrong.
var body = "<html><body>"
+ "<p>" + "Hi Team," + "<br/><br/>"
+ "<p>" + "Can you please review the bid for the following opportunity:" + "<br/>"
+ "<p>" + "<u><b>Opportunity Details: </b></u>"+ "<br/>"
+ "</body></html>";
Logger.log(body);
var con = ContactsApp.getContactsByGroup(ContactsApp.getContactGroup('Australia'));
var toList = [];
for (var i in con) {
toList[i] = "<" + con[i].getPrimaryEmail() + ">";
}
toList = toList.join(',');
var msg = {
to: toList,
from: {
name: ContactsApp.getContact(Session.getEffectiveUser().getEmail()).getFullName(),
email: Session.getEffectiveUser().getEmail(),
signature: Gmail.Users.Settings.SendAs.list("me").sendAs[0].signature
},
body: body,
subject: sub
};
// var htmlBody = '<p>Hello, I am an HTML message</p><br/>' + 'Click here';
// var signature = Gmail.Users.Settings.SendAs.list("me").sendAs[0].signature ; //sendAs.filter(function(account){if(account.isDefault){return true}})[0].signature;
var raw = 'From: <' + msg.from.email + '>\r\n' +
'To: ' + msg.to + '\r\n' +
'Subject:' + msg.subject + '\r\n' +
'Content-Type: text/html; charset=UTF-8\r\n' +
'\r\n' + msg.body + msg.from.signature;
var draftBody = Utilities.base64Encode(raw, Utilities.Charset.UTF_8).replace(/\//g,'_').replace(/\+/g,'-');
var params = {
method : "post",
contentType : "application/json",
headers : {"Authorization": "Bearer " + ScriptApp.getOAuthToken()},
devMode : true,
muteHttpExceptions:true,
payload:JSON.stringify({
"message": {
"raw": draftBody
}
})
};
var resp = UrlFetchApp.fetch("https://www.googleapis.com/gmail/v1/users/me/drafts", params);
Logger.log(resp);
Logger.log(resp.getContentText());
I solved this problem by manually modifying the manifest file in the Google Scripts interface.
If you look under File > Project Properties > Scopes, you will find there are certain scopes for which the app is already requesting an authorization. You can manually add to these scopes by changing the json manifest file, under View > Show manifest file to add the ones in your Project Properties + the one you need for Gmail API, in my case, "https://www.googleapis.com/auth/gmail.compose".
Hope it helps.

Google Apps Script UrlFetchApp.fetch HTTP PUT Request to firebase not working

I'm trying to write data to a firebase database using Google Apps Script HTTP PUT Request. Here is what I have for code. I've debugged every line and everything works except for the actual PUT request.
Web page with one button in it.
<section>
<input onclick="WriteInput()" type="button" value="Save Input">
</section>
<script>
function onSuccess() {
alert("on success ran");
};
function onFailure() {
alert("on failure ran");
};
function WriteInput() {
alert("it ran");
google.script.run.withFailureHandler(onFailure)
.withSuccessHandler(onSuccess)
.putToFire();
}
</script>
This is the server side .gs code:
function doGet(){
return HtmlService.createTemplateFromFile('WriteToFirebase')
.evaluate() // evaluate MUST come before setting the NATIVE mode
.setTitle('Test Write')
.setSandboxMode(HtmlService.SandboxMode.NATIVE);
}
function putToFire() {
var payload =
{
"first" : "Jack",
"last" : "Sparrow"
};
var options =
{
"method" : "put",
"payload" : payload
};
UrlFetchApp.fetch("https://SampleChat.firebaseIO-demo.com/users/fred/name.json", options );
Logger.log(options);
}
This is the Google documentation for issuing a HTTP request with Apps Script:
fetch url Google Documentation
This is the Firebase REST API documentation:
Firebase REST API
in cURL -X is to set the type of request. E.g. PUT, POST, etc and -d is the indicate that data is to follow: The match up with Google Fetch is to set the params.
I must be just missing a syntax setting or something.
I actually found an answer to my problem. I can't remember what the error message was. I changed
THIS:
var options =
{
"method" : "put",
"payload" : payload
};
TO THIS:
var options = {"method" : "put", "payload" : payload};
And it started working. I'm guessing that the multi-line code had end of line return characters in it, that Firebase would not parse. That's just my guess.
This example shows payload, options and the Apps Script UrlFetchApp.fetch service.
var payload = "{\"aa\" : \"" + UserID + "\", \"ab\" : \"" + Maker + "\", \"ac\" : " +
AskingPrice + ", \"ad\" : \"" + Type +
"\", \"ae\" : \"" + Description + "\", \"af\" : \"" + Function + "\", \"ag\" : \"" +
Cosmetic + "\", \"ah\" : \"" + dbID +
"\", \"ai\" : \"" + IPaddr + "\"}";
var options = {"method" : "put", "payload" : payload};
UrlFetchApp.fetch("https://MyFireBaseDatabaseName.com/" + Ctgry + "/"
+ dbID + "/.json", options );