
Box: Upload File
This item uploads files to the specified folder on Box.
Configs: Common
- Step Name
- Note
Configs
- C1: OAuth2 Setting *
- C2: File type data item whose attached files will be uploaded *
- C3: Folder ID that files will be uploaded (Root folder if blank)#{EL}
- C4: String type data item that will save uploaded file ids
- C5: String type data item that will save uploaded file urls
Notes
- Folder ID is contained in the URL: https://{sub-domain}.app.box.com/folder/(Folder ID)
- If there are filename conflicts, it will result in an error
- When the conflicted file is over 50MB, no files will be uploaded
- When it is 50MB or under, files set before it will be uploaded
- Box refresh tokens have an expiration date
- You must use them regularly to ensure that the expiration date is not exceeded (Box: Token & URL Expiration)
- A File over 50MB will be divided into parts with the size of each part specified by Box
- In this case, the number of sending requests is expressed by (file size ÷ specified size) + 2 (you should round up the result of the calculation in parenthesis)
Capture

See also
Script (click to open)
- An XML file that contains the code below is available to download
- box-file-upload.xml (C) Questetra, Inc. (MIT License)
- If you are using Professional, you can modify the contents of this file and use it as your own add-on
// OAuth2 config
// Authorization Endpoint URL: https://app.box.com/api/oauth2/authorize
// Token Endpoint URL: https://app.box.com/api/oauth2/token
// Scope:
// Consumer Key: (Get by box Application on box Developers)
// Consumer Secret: (Get by box Application on box Developers)
const SIZE_LIMIT = 52428800; //File size border of Box
//50MB
main();
function main() {
//// == 工程コンフィグの参照 / Config Retrieving ==
const oauth2 = configs.get("conf_OAuth2");
const idDataDef = configs.getObject("fileId");
const urlDataDef = configs.getObject("fileUrl");
//// == ワークフローデータの参照 / Data Retrieving ==
const folderId = decideFolderId();
const files = engine.findData(configs.getObject("uploadedFile"));
if (files === null) {
setData(idDataDef, [""]);
setData(urlDataDef, [""]);
return;
}
//// == 演算 / Calculating ==
checkFile(files, idDataDef, urlDataDef);
let uploadedFileData = [[], []]; //0:ID,1:URL
const session = prepareSessionsAndCheckRequestNumber(oauth2, files, folderId);
uploadAllFiles(oauth2, files, folderId, uploadedFileData, session);
//// == ワークフローデータへの代入 / Data Updating ==
setData(idDataDef, uploadedFileData[0]);
setData(urlDataDef, uploadedFileData[1]);
}
/**
* フォルダのIDをconfigから読み出して出力する。空の場合は"0"とする。
* @return {String} フォルダのID
*/
function decideFolderId() {
let folderId = configs.get("uploadedFolderId");
if (folderId === "" || folderId === null) {
//when folder ID isn't set, set "0"
folderId = "0";
}
return folderId;
}
/**
* アップロードしようとするファイルの数・名前が適切かどうかチェックする
* ファイル数が通信制限を超えたらエラーを出し、その後各関数を呼び出す
* @param {Array<File>} files アップロードしようとするファイルの配列
* @param {String} folderId アップロード先フォルダのID
* @param {ProcessDataDefinitionView} idDataDef ID を保存するデータ項目の ProcessDataDefinitionView
* @param {ProcessDataDefinitionView} urlDataDef URL を保存するデータ項目の ProcessDataDefinitionView
*/
function checkFile(files, idDataDef, urlDataDef) {
const fileNum = files.size(); //number of files
if (fileNum > httpClient.getRequestingLimit()) {
throw "Number of requests is over the limit";
}
checkfileNum(idDataDef, fileNum);
checkfileNum(urlDataDef, fileNum);
checkFileName(files);
checkFileNameOverlap(files);
}
/**
* アップロードするファイルが複数で パス・URL 出力先のデータ項目が単一行ならエラーにする
* @param {ProcessDataDefinitionView} dataDef データ項目の ProcessDataDefinitionView
* @param {Number} fileNum アップロードしようとするファイルの個数
*/
function checkfileNum(dataDef, fileNum) {
if (dataDef !== null) {
//Multiple Judge
if (dataDef.matchDataType("STRING_TEXTFIELD") && fileNum > 1) {
throw "Multiple files are set though the Data Item to save the output is Single-line String."
}
}
}
/**
* アップロードするファイルの名前がBoxにおいて無効なファイル名となるものならエラーにする
* Boxにおいて無効なファイル名となる条件
* ・ファイル名に\ / のいずれかが含まれている
* ・ファイル名の先頭か末尾にスペースがある
* ・ファイル名が. または .. である
* @param {Array<File>} files アップロードしようとするファイルの配列
*/
function checkFileName(files) {
for (let i = 0; i < files.size(); i++) {
let fileName = files[i].getName();
if (fileName.indexOf("/") !== -1 || fileName.indexOf("\\") !== -1 || fileName === "." || fileName === ".." || fileName.endsWith(" ") === true || fileName.startsWith(" ") === true) {
throw "Invalid File Name";
}
}
}
/**
* アップロードするファイルの中に同じファイル名のものが2つ以上あればエラー
* @param {Array<File>} アップロードしようとするファイルの配列
*/
function checkFileNameOverlap(files) {
let fileNames = [];
const fileNum = files.size();
for (let i = 0; i < fileNum; i++) {
if (fileNames.includes(files[i].getName())) {
throw "Two or more files to upload have the same name.";
}
fileNames[i] = files[i].getName();
}
}
/**
* 50MBを超えるファイルについてセッションを作成し、通信数を確認する
* 通信数が制限を超えたらエラーを出す
* 通信数=50MB以下のファイルの数 + <50MBを超えるファイルそれぞれについて>(ceil(ファイルサイズ/パケットサイズ) + 2)
* パケットサイズはセッション作成時に指定される
* @param {String} oauth2 OAuth2 設定
* @param {Array<File>} files アップロードしようとするファイルの配列
* @param {String} folderId アップロード先フォルダのID
* @return {Array<Object>} アップロードセッションの配列(50MB以下のファイルについては空オブジェクト)
*/
function prepareSessionsAndCheckRequestNumber(oauth2, files, folderId) {
let requestNum = 0;
let session = [];
for (let i = 0; i < files.size(); i++) {
requestNum++;
if (files.get(i).getLength() > SIZE_LIMIT) {
//over 50MB
session[i] = createSession(oauth2, files.get(i), folderId);
requestNum += (Math.ceil(files.get(i).getLength() / session[i].part_size + 1));
} else {
session[i] = {};
}
}
if (requestNum > httpClient.getRequestingLimit()) {
throw "Number of requests is over the limit";
}
return session;
}
/**
* 各ファイルのアップロード処理を行う
* ファイルのサイズごとに工程は異なる
* @param {String} oauth2 OAuth2 設定
* @param {Array<File>} files アップロードしようとするファイルの配列
* @param {String} folderId アップロード先のフォルダのパス
* @param {Array<Array<String>>} uploadedFileData アップロードしたファイルの情報を格納する二次元配列
* @param {Array<Object>} session アップロードセッションの配列(50MB以下のファイルについては空オブジェクト)
*/
function uploadAllFiles(oauth2, files, folderId, uploadedFileData, session) {
for (let i = 0; i < files.size(); i++) {
engine.log(files.get(i).getName());
if (files.get(i).getLength() > SIZE_LIMIT) {
//over 50MB
processLargeFile(oauth2, files.get(i), folderId, uploadedFileData, session[i]);
} else {
//under 50MB
upload(oauth2, files.get(i), folderId, uploadedFileData);
}
}
}
/**
* 50MB以下のファイルをアップロードする。一回につき一つのみ。
* @param {String} oauth2 OAuth2 設定
* @param {File} file アップロードするファイル
* @param {String} folderId アップロード先フォルダのID
* @param {Array<Array<String>>} uploadedFileData アップロードしたファイルの情報を格納する二次元配列
*/
function upload(oauth2, file, folderId, uploadedFileData) {
const url = "https://upload.box.com/api/2.0/files/content";
let attributes = {
parent: {id: folderId}
};
attributes["name"] = String(file.getName());
let response = httpClient.begin()
.authSetting(oauth2)
.multipart('attributes', JSON.stringify(attributes))
.multipart('file', file)
.queryParam("fields", "id")
.post(url);
//when error thrown
const responseStr = response.getResponseAsString();
const status = response.getStatusCode();
if (status >= 300) {
engine.log(` upload failure\n${responseStr}`);
throw `failed to upload\nstatus: ${status}\nfile: ${file.getName()}`;
}
engine.log(" upload successful");
addOutputToArray(responseStr, uploadedFileData);
}
/**
* 50MBを超えるファイルのアップロード処理を行う
* @param {String} oauth2 OAuth2 設定
* @param {File} file アップロードするファイル
* @param {String} folderId アップロード先のフォルダのパス
* @param {Array<Array<String>>} uploadedFileData アップロードしたファイルの情報を格納する二次元配列
* @param {Object} session アップロードセッション
*/
function processLargeFile(oauth2, file, folderId, uploadedFileData, session) {
const sessionId = session.id;
const fileSize = file.getLength();
let part;
let partObj = {"parts": []};
let range = 0;
fileRepository.readFile(file, session.part_size, function (packet) {
//upload each fragment of file
part = uploadLargeFile(oauth2, sessionId, range, packet, fileSize, uploadedFileData, file.getName());
partObj.parts.push(part);
range += part.size;
});
commitSession(oauth2, sessionId, partObj, file, uploadedFileData);
}
/**
* アップロード用のセッションを作成する
* @param {String} oauth2 OAuth2 設定
* @param {File} file アップロードするファイル
* @param {String} folderId アップロード先のフォルダのID
* @return {JSON Object} アップロードセッション
*/
function createSession(oauth2, file, folderId) {
const url = "https://upload.box.com/api/2.0/files/upload_sessions";
const body = {
"file_name": file.getName(),
"file_size": file.getLength(),
"folder_id": folderId
};
let response = httpClient.begin()
.authSetting(oauth2)
.body(JSON.stringify(body), "application/json; charset=UTF-8")
.post(url);
const status = response.getStatusCode();
const jsonStr = response.getResponseAsString();
if (status >= 300) {
engine.log(file.getName())
engine.log(` upload failure\n${jsonStr}`);
throw `failed to create upload session \nstatus: ${status}\nfile: ${file.getName()}`;
}
return JSON.parse(jsonStr);
}
/**
* 50MBを超えるファイルについて、各部分のアップロードを実行する
* @param {String} oauth2 OAuth2 設定
* @param {String} sessionId アップロードセッションの ID
* @param {Number} range range
* @param {ByteArrayWrapper} packet アップロードするバイナリ
* @param {Number} fileSize アップロードするファイルのサイズ
* @param {Array<Array<String>>} uploadedFileData アップロードしたファイルの情報を格納する二次元配列
* @return {Object} Uploaded Part
*/
function uploadLargeFile(oauth2, sessionId, range, packet, fileSize, uploadedFileData, fileName) {
const packetSize = packet.getLength();
const url = `https://upload.box.com/api/2.0/files/upload_sessions/${sessionId}`;
const rangetxt = `bytes ${range}-${range + packetSize - 1}/${fileSize}`;
const hash = `sha=${base64.encodeToString(digest.sha1(packet))}`;
let sending = httpClient.begin()
.authSetting(oauth2)
.header("Content-Range", rangetxt)
.header("Digest", hash)
.body(packet, "application/octet-stream")
.put(url);
const status = sending.getStatusCode();
const responseStr = sending.getResponseAsString();
const json = JSON.parse(responseStr);
if (status >= 300) {
engine.log(` upload failure\n${responseStr}`);
throw `failed to upload\nstatus: ${status}\nfile: ${fileName}`;
} else {
return json.part;
}
}
/**
* 50MBを超えるファイルについて、アップロードをコミットする
* @param {String} oauth2 OAuth2 設定
* @param {String} sessionId アップロードセッションの ID
* @param {Object} partObj これまでのアップロートパートをまとめたオブジェクト
* @param {File} file アップロードするファイル
* @param {Array<Array<String>>} uploadedFileData アップロードしたファイルの情報を格納する二次元配列
*/
function commitSession(oauth2, sessionId, partObj, file, uploadedFileData) {
const url = `https://upload.box.com/api/2.0/files/upload_sessions/${sessionId}/commit`;
const hash = `sha=${base64.encodeToString(digest.sha1(file))}`;
let commit = httpClient.begin()
.authSetting(oauth2)
.header("Digest", hash)
.queryParam("fields", "id")
.body(JSON.stringify(partObj), "application/json")
.post(url);
const status = commit.getStatusCode();
const responseStr = commit.getResponseAsString();
if (status >= 300) {
engine.log(` upload failure\n${responseStr}`);
throw `failed to commit upload session \nstatus: ${status}\nfile: ${file.getName()}`;
}
engine.log(" upload successful");
addOutputToArray(responseStr, uploadedFileData);
}
/**
* アップロードしたデータのIDとURLを配列にセットする。
* @param {String} responseStr 送信時のレスポンスをテキスト出力したもの
* @param {Array<String>} uploadedFileData アップロードしたファイルの情報が格納される配列
*/
function addOutputToArray(responseStr, uploadedFileData) {
const json = JSON.parse(responseStr);
uploadedFileData[0].push(json.entries[0].id);
uploadedFileData[1].push(`https://app.box.com/file/${json.entries[0].id}`);
}
/**
* アップロードしたデータのパスとURLをデータ項目に出力する。
* @param {ProcessDataDefinitionView} dataDef データ項目の ProcessDataDefinitionView
* @param {Array<String>} uploadedFileData アップロードしたファイルの情報が格納されている配列
*/
function setData(dataDef, uploadedFileData) {
if (dataDef !== null) {
engine.setData(dataDef, uploadedFileData.join('\n'));
}
}