1 var REST = library.REST.REST();
  2 var Request = library.Request.build();
  3 var myDownloadTempDir = null;
  4 
  5 var kS3Enabled = args("aws.defaultBucket", "") !== "";
  6 
  7 /**
  8  * Used to increment files with the same name  test.txt becomes test 1.txt
  9  * @param theParentPath
 10  * @param theOldName
 11  * @returns {string}
 12  */
 13 function incrementName(theParentPath, theOldName) {
 14   var anExtension = theOldName.substring(
 15     theOldName.lastIndexOf("."),
 16     theOldName.length
 17   );
 18   var aFileNumber;
 19   var aNewName;
 20   if (theOldName.lastIndexOf(" ") !== -1) {
 21     aNewName = theOldName.substring(0, theOldName.lastIndexOf(" "));
 22     aFileNumber = theOldName.substring(
 23       theOldName.lastIndexOf(" "),
 24       theOldName.lastIndexOf(anExtension)
 25     );
 26     if (aFileNumber == null || aFileNumber === " ") {
 27       aFileNumber = 0;
 28     } else {
 29       var aMightBeNumber = aFileNumber;
 30       aFileNumber = parseInt(aFileNumber);
 31       if (isNaN(aFileNumber)) {
 32         //aFileNumber isn't a number. add the original back to the name
 33         aNewName += aMightBeNumber;
 34         aFileNumber = 0;
 35       }
 36     }
 37   } else {
 38     aFileNumber = 0;
 39     aNewName = theOldName.substring(0, theOldName.lastIndexOf("."));
 40   }
 41   aFileNumber++;
 42   while (
 43     fileManager.isFile(
 44       theParentPath + aNewName + " " + aFileNumber + anExtension
 45     )
 46   ) {
 47     aFileNumber++;
 48   }
 49   return aNewName + " " + aFileNumber + anExtension;
 50 }
 51 
 52 function upload() {
 53   var aDest = REST.getFullPath(context.getParameter("dest"));
 54   var aDestId = context.getParameter("destId");
 55   var aManagedFolder;
 56   if (!!aDestId) {
 57     aManagedFolder = fileManager.getFolderObjectById(aDestId);
 58     if (!aManagedFolder) {
 59       REST.pushError(
 60         REST.errors.e404,
 61         "The dest folder does not exist by id: " + aDestId
 62       );
 63       return;
 64     }
 65   }
 66 
 67   var aUnzip = context.getParameter("unzip");
 68   var aVersion = context.getParameter("version");
 69   if (!aVersion) {
 70     aVersion = context.getParameter("replace");
 71     if (!!aVersion) {
 72       logger.info(
 73         "'replace' parameter is deprecated, should be using 'version'."
 74       );
 75     }
 76   }
 77   var aDownloadedFiles = downloadFiles(context.getParameter("urls"));
 78   try {
 79     var anUploadedFile = context.getUploadedFile();
 80     if (anUploadedFile == null && aDownloadedFiles.length === 0) {
 81       REST.pushError(
 82         REST.errors.e400,
 83         "A file was not attached to the form-data of the Body of the POST"
 84       );
 85       return;
 86     }
 87     var aFiles = [];
 88     if (anUploadedFile != null) {
 89       if (anUploadedFile.constructor === Array) {
 90         for (var i = 0; i < anUploadedFile.length; i++) {
 91           aFiles.push(anUploadedFile[i]);
 92         }
 93       } else {
 94         aFiles.push(anUploadedFile);
 95       }
 96     }
 97     if (aDownloadedFiles.length > 0) {
 98       for (i = 0; i < aDownloadedFiles.length; i++) {
 99         aFiles.push(aDownloadedFiles[i]);
100       }
101     }
102     // Create the destination directory path if it does not already exist
103     if (!aManagedFolder) {
104       var aPathArray = aDest.toString().split("/");
105       var aPath = "";
106       for (i = 0; i < aPathArray.length; i++) {
107         // Continue on empty paths.
108         if (aPathArray[i] === "") {
109           continue;
110         }
111         aPath += aPathArray[i] + "/";
112 
113         //Only import unmanaged folders
114         var aDoImport = !fileManager.isManagedFolderPath(aPath);
115         var importingFolder = fileManager.folderNewByPath(aPath);
116 
117         //Exclude the renditions folder from import
118         if (aDoImport && importingFolder.path.indexOf("Renditions") !== 0) {
119           fileManager.folderImport(importingFolder, false);
120         }
121       }
122       aManagedFolder = new ManagedFolder(aDest);
123     }
124     if (aManagedFolder == null) {
125       REST.pushError(REST.errors.e404, "Folder not found: " + aDest);
126       return;
127     }
128 
129     if (kS3Enabled) {
130       uploadToS3(
131         aFiles.length === 1 ? aFiles[0] : aFiles,
132         aManagedFolder.path,
133         aUnzip,
134         aVersion
135       );
136     } else {
137       doUpload(aFiles, aManagedFolder.path, aUnzip, aVersion);
138     }
139 
140     if (myDownloadTempDir != null) {
141       fileManager.folderDelete(myDownloadTempDir);
142       myDownloadTempDir = null;
143     }
144   } catch (err) {
145     REST.pushError(REST.errors.e500, "Failed to upload to: " + aDest, err);
146   }
147 }
148 
149 /**
150  * Copies the xmp from the xmpAsset to theAsset and keeps both assets unmanaged.
151  *
152  * @param theAsset the destination asset.
153  * @param theXmpAsset the asset with the source XMP.
154  */
155 function setXmp(theAsset, theXmpAsset) {
156   var aSourceMeta =
157     theXmpAsset.xmp && theXmpAsset.xmp.meta ? theXmpAsset.xmp.meta : null;
158   var aDestMeta = theAsset.xmp && theAsset.xmp.meta ? theAsset.xmp.meta : null;
159 
160   if (aSourceMeta != null && aDestMeta != null) {
161     logger.info("---------- Apply source meta");
162     var aProperties = aSourceMeta.getFields();
163     for (var i = 0; i < aProperties.length; i++) {
164       var aProperty = aProperties[i];
165       var aField = aSourceMeta.getField(aProperty);
166       aDestMeta.addField(aProperty, aField);
167     }
168   } else {
169     logger.info("---------- Still o source meta");
170   }
171   theAsset.writeXmp();
172 }
173 
174 /**
175  * Stamps the metadata provided via rest api to the asset uploaded.
176  * @param theAsset
177  */
178 var stampMetadata = function (theAsset) {
179   //key value pairs of field name to field value
180   var aMetadata = context.getParameter("metadata");
181   if (!!aMetadata) {
182     var aMeta = theAsset.xmp && theAsset.xmp.meta ? theAsset.xmp.meta : null;
183     if (aMeta != null) {
184       for (var aKey in aMetadata) {
185         var aValue = aMetadata[aKey];
186         if (aValue != null)
187         {
188           aValue = aValue.trim();
189         }
190         var aParamName = aKey.toLowerCase();
191         var aSplit = aParamName.split(" ");
192         var aProperty = getPropertyCaseInsensitive(aSplit[0], aSplit[1]);
193         if (!!aProperty) {
194           var aField = aMeta.getField(aProperty);
195           if (fieldManager.isContainer(aProperty)) {
196             logger.info("Is container");
197             var anArray = aValue.split(",");
198             if (aField instanceof Bag) {
199               aValue = theAsset.xmp.newBag();
200             } else {
201               aValue = theAsset.xmp.newSeq();
202             }
203             for (j = 0; j < anArray.length; j++) {
204               logger.info(anArray[j].trim());
205               aValue.addItem(anArray[j].trim());
206             }
207           }
208           aMeta.addProperty(aProperty, aValue);
209         } else {
210           logger.warning(
211               "Unknown field and unable to create default: " + aParamName
212           );
213         }
214       }
215       var anIsWritten = theAsset.writeXmp();
216       if (!anIsWritten) {
217         logger.warning(
218             "Metadata was not written properly to file: " + theAsset.path
219         );
220         throw "Failed to write metadata";
221       }
222     }
223   }
224 };
225 
226 /**
227  * Gets the property value based on namespace and field id
228  * @param theNamespace
229  * @param theField
230  * @returns {Property|null}
231  */
232 function getPropertyCaseInsensitive(theNamespace, theField)
233 {
234   try {
235     var aQuery =
236         "SELECT j56c_id, j56c_namespace FROM j56t_xmp_namespaces WHERE lower(j56c_namespace) = '" +
237         escapeSql(theNamespace.toLowerCase()) +
238         "' ORDER BY j56c_id asc";
239     var aSQL = new SQL();
240     var aRes = aSQL.queryForMap(aQuery);
241     if (aRes.length > 0)
242     {
243       // we ordered by oldest so we want the first row
244       aQuery =
245           "SELECT j57c_name FROM j57t_xmp_fields WHERE lower(j57c_name) = '" +
246           escapeSql(theField.toLowerCase()) +
247           "' AND j57c_namespace = " +
248           aRes[0]["j56c_id"];
249       var aField = aSQL.queryForString(aQuery);
250       if (!!aField && aField != "undefined" && aField != "null")
251       {
252         theField = aField;
253         theNamespace = aRes[0]["j56c_namespace"]; // set only if above call succeeded as well
254       }
255     }
256   } catch (e)
257   {
258     logger.warning("Unable to query for case-insensitive field name\n" + getErrorMessage(e));
259     return null;
260   }
261   logger.info('Using "' + theNamespace + " " + theField + '"');
262   return new Property(theNamespace, theField);
263 }
264 
265 function escapeSql(theString)
266 {
267   return theString.replace(/'/g, "''");
268 }
269 
270 /**
271  * Formats the exception so its pretty for logging or the response
272  * @param theException
273  * @returns {string}
274  */
275 function getErrorMessage(theException)
276 {
277   return ( theException.toString() + (!!theException.stack ? "\n\n" + theException.stack : ""));
278 }
279 
280 /**
281  *  Perform S3 upload(s)
282  *
283  * @param theUploadedFile the file(s) being uploaded
284  * @param theDestination the destination folder
285  * @param theUnzip should we unzip?
286  * @param theVersion should we create a new version?
287  */
288 function uploadToS3(theUploadedFile, theDestination, theUnzip, theVersion) {
289   if (theUploadedFile.constructor === Array) {
290     for (var i = 0; i < theUploadedFile.length; i++) {
291       logger.warning("Uploading: " + i);
292       var aUploadedFile = theUploadedFile[i];
293 
294       var aFileExtension = aUploadedFile.name.split(".").pop();
295       var isLoneFile = true;
296 
297       if (aFileExtension !== "xmp") {
298         // look for side-car .xmp files and apply them
299         for (var j = 0; j < theUploadedFile.length; j++) {
300           if (
301             theUploadedFile[j].name ===
302             aUploadedFile.name.split(".")[0] + ".xmp"
303           ) {
304             // Found a side-car xmp file, apply it and continue on
305             setXmp(aUploadedFile, theUploadedFile[j]);
306             break;
307           }
308         }
309       } else {
310         for (var k = 0; k < theUploadedFile.length; k++) {
311           if (
312             aUploadedFile.name ===
313             theUploadedFile[k].name.split(".")[0] + ".xmp"
314           ) {
315             // File is a side-car xmp, ignore it
316             isLoneFile = false;
317             break;
318           }
319         }
320       }
321 
322       // Upload to S3 if it's not a side-car xmp
323       if (isLoneFile) {
324         uploadS3File(aUploadedFile, theDestination, theUnzip, theVersion);
325       }
326     }
327   } else {
328     uploadS3File(theUploadedFile, theDestination, theUnzip, theVersion);
329   }
330 }
331 
332 /**
333  * Upload a single file or archive to S3
334  *
335  * @param theUploadedFile the file to upload
336  * @param theDestination the destination
337  * @param theUnzip should we unzip?
338  * @param theVersion should we create a new version?
339  */
340 function uploadS3File(theUploadedFile, theDestination, theUnzip, theVersion) {
341   // Handle Unzipping
342   var anIsAsyncImport = isAsyncImport();
343   if (theUnzip && theUploadedFile.name.endsWith(".zip")) {
344     if (theUploadedFile.name.indexOf(".zip") > 0 && theUnzip === true) {
345       var aDecompressedDir = fileManager.decompressFiles(
346         theUploadedFile,
347         new ManagedFolder(theDestination),
348         "UTF-8",
349         false,
350         "",
351         anIsAsyncImport
352       );
353       fileManager.fileDelete(theUploadedFile);
354 
355       if (REST.isFile(aDecompressedDir)) {
356         // Upload unzipped asset to S3
357         doS3Upload(
358           aDecompressedDir,
359           theDestination,
360           anIsAsyncImport,
361           theVersion
362         );
363         return;
364       }
365       var anIterator = aDecompressedDir.getContents();
366       while (anIterator.hasNext()) {
367         var aFileOrFolder = anIterator.next();
368         if (REST.isFile(aFileOrFolder)) {
369           // Upload unzipped asset to S3
370           doS3Upload(
371             aFileOrFolder,
372             theDestination,
373             anIsAsyncImport,
374             theVersion
375           );
376           fileManager.fileDelete(aFileOrFolder);
377         }
378       }
379       return;
380     }
381   }
382 
383   // Upload asset to S3
384   doS3Upload(
385     theUploadedFile,
386     theDestination,
387     anIsAsyncImport,
388     theVersion
389   );
390 }
391 
392 /**
393  * Performs S3 upload.
394  *
395  * @param theFile the file to upload
396  * @param theDestination the destination folder
397  * @param theIsAsyncImport is asynchronous import
398  * @param theVersion should we create a new version?
399  */
400 function doS3Upload(theFile, theDestination, theIsAsyncImport, theVersion) {
401   if (fileManager.isManagedFilePath(theDestination + theFile.name) && theVersion)
402   {
403     var aDestFile = new ManagedFile(theDestination + theFile.name);
404     checkin(aDestFile, theFile);
405     pushUploadedAsset(fileManager.getFileObjectById(aDestFile.assetId));
406     return;
407   }
408   if (!!theIsAsyncImport)
409   {
410     var anActionId = s3Manager.doS3UploadAsync(
411             context.getUser().username,
412             theFile,
413             theDestination
414     );
415     pushAsyncUploadedAsset(theFile, theDestination, anActionId);
416   } else
417   {
418     var anAssetId = s3Manager.doS3Upload(
419             context.getUser().username,
420             theFile,
421             theDestination
422     );
423     var aNewAsset = fileManager.getFileObjectById(anAssetId);
424     pushUploadedAsset(aNewAsset);
425   }
426 }
427 
428 function doUpload(theUploadedFiles, theDestination, theUnzip, theVersion) {
429   var anIsAsyncImport = isAsyncImport();
430   // Versioning parameters
431   var aComment =
432     context.getParameter("message") === undefined
433       ? ""
434       : context.getParameter("comment");
435   var replaceName =
436     context.getParameter("replaceName") === "true" ||
437     context.getParameter("replaceName") === true;
438   var aReplaceMetadata =
439     context.getParameter("replaceMetadata") === "true" ||
440     context.getParameter("replaceMetadata") === true;
441 
442   for (var i = 0; i < theUploadedFiles.length; i++) {
443     var aFileExtension = theUploadedFiles[i].name.split(".").pop();
444     var isStandAloneAsset = true;
445     var aFileToUpload = theUploadedFiles[i];
446 
447     if (aFileExtension !== "xmp") {
448       // if it's not an XMP file, see if it has a matching XMP to stick to it.
449       for (var j = 0; j < theUploadedFiles.length; j++) {
450         // Found a matching .XMP add to the mapping to apply it later
451         if (
452           theUploadedFiles[j].name ===
453           aFileToUpload.name.split(".")[0] + ".xmp"
454         ) {
455           setXmp(aFileToUpload, theUploadedFiles[j]);
456           break;
457         }
458       }
459     } else {
460       // If it is an XMP file, see if it's a lone XMP (no matching asset), if so, upload like a normal data asset
461       for (var j = 0; j < theUploadedFiles.length; j++) {
462         if (
463           theUploadedFiles[j].name.split(".").pop() !== "xmp" &&
464           theUploadedFiles[j].name.split(".")[0] ===
465             aFileToUpload.name.split(".")[0]
466         ) {
467           isStandAloneAsset = false;
468           break;
469         }
470       }
471     }
472 
473     // this file is an xmp for another asset we'll process later
474     if (!isStandAloneAsset) {
475       continue;
476     }
477 
478     if (
479       fileManager.isManagedFilePath(theDestination + aFileToUpload.name) &&
480       theVersion &&
481       !theUnzip
482     ) {
483       var aDestFile = new ManagedFile(theDestination + aFileToUpload.name);
484       versionManager.createNewVersion(
485         aDestFile.assetId,
486         aFileToUpload,
487         aComment,
488         replaceName,
489         aReplaceMetadata
490       );
491       pushUploadedAsset(fileManager.getFileObjectById(aDestFile.assetId));
492     } else if (aFileToUpload.name.indexOf(".zip") > 0 && theUnzip === true) {
493       var aDecompressedDir = fileManager.decompressFiles(
494         aFileToUpload,
495         new ManagedFolder(theDestination),
496         "UTF-8",
497         false,
498         "",
499         anIsAsyncImport
500       );
501       fileManager.fileDelete(aFileToUpload);
502       if (REST.isFile(aDecompressedDir)) {
503         finishUpload(aDecompressedDir, theDestination, anIsAsyncImport);
504         return;
505       }
506       var anIterator = aDecompressedDir.getContents();
507       while (anIterator.hasNext()) {
508         var aFileOrFolder = anIterator.next();
509         if (REST.isFile(aFileOrFolder)) {
510           finishUpload(aFileOrFolder, theDestination, anIsAsyncImport);
511         }
512       }
513     } else {
514       var anUploaded = fileManager.fileMove(
515         aFileToUpload,
516         new ManagedFolder(theDestination),
517         theVersion,
518         null,
519         anIsAsyncImport
520       );
521       finishUpload(anUploaded, theDestination, anIsAsyncImport);
522     }
523   }
524 }
525 
526 /**
527  * Finishes the standard upload.
528  *
529  * @param theFile the uploaded file
530  * @param theDestination the destination folder
531  * @param theIsAsyncImport is asynchronous import
532  */
533 function finishUpload(theFile, theDestination, theIsAsyncImport)
534 {
535   if (!!theIsAsyncImport)
536   {
537     var anActionId = fileManager.fileImportAsync(theFile);
538     pushAsyncUploadedAsset(theFile, theDestination, anActionId);
539   } else
540   {
541     pushUploadedAsset(theFile);
542   }
543 }
544 
545 /**
546  * Check in the working copy as a new version of the asset.
547  *
548  * @param theDestFile the destination file
549  * @param theSourceFile the source file
550  */
551 function checkin(theDestFile, theSourceFile)
552 {
553   try
554   {
555     var aStartTime = new Date();
556     if (theSourceFile == null)
557     {
558       throw "A file was not attached to the form-data of the Body of the POST";
559     }
560     logger.info("Checking in upload: " + theSourceFile.path);
561     checkoutManager.checkout(theDestFile);
562     if (
563             !checkoutManager.checkinWorkingCopy(
564                     theDestFile,
565                     "Checkin from REST API",
566                     theSourceFile
567             )
568     )
569     {
570       checkoutManager.cancelCheckout(theDestFile);
571       throw "Unable to checkin file, check MediaBeacon logs";
572     }
573     var i = 0;
574     while (i < 10)
575     {
576       try
577       {
578         fileManager.fileDelete(theSourceFile);
579         break;
580       } catch (theE)
581       {
582         logger.info("Failed to delete, trying again");
583         workflowManager.sleep(1);
584         i++;
585       }
586     }
587     var aMetric = {
588       action: "Checkin from 'REST API'",
589       subject: "Asset",
590       user_name: "System",
591       datestamp: new Date(),
592       elapsed_time: new Date() - aStartTime,
593       path: theDestFile.path,
594       asset_id: theDestFile.assetId,
595       description:
596               "REST API Checkin: " + theDestFile.path + ", SIZE: " + theDestFile.length,
597     };
598     metricsManager.addMetric(aMetric);
599   } catch (theE)
600   {
601     throw "Failed to upload: " + theE;
602   }
603 }
604 
605 /**
606  * Writes the uploaded user to the file and adds it to the response
607  * @param theAsset
608  */
609 function pushUploadedAsset(theAsset) {
610   addUploadData(theAsset);
611   var aStub = {};
612   var anAssetAdded = REST.triggerAssetBasedWorkflows(
613     REST.AssetTriggers.Added,
614     theAsset
615   );
616   if (anAssetAdded) {
617     theAsset = fileManager.getFileObjectById(theAsset.assetId);
618     aStub.triggerAssetAdded = anAssetAdded.ok;
619   }
620   REST.push(theAsset, aStub);
621 }
622 
623 /**
624  * Writes data to the response.
625  *
626  * @param theAsset the uploaded asset
627  * @param theDestination the destination folder
628  * @param theActionId the action id
629  */
630 function pushAsyncUploadedAsset(theAsset, theDestination, theActionId) {
631   addUploadData(theAsset);
632   REST.push(
633           null,
634           {
635             name: theAsset.name,
636             path: theDestination + theAsset.name,
637             actionId: theActionId
638           }
639   );
640 }
641 
642 /**
643  * Is asynchronous import?
644  */
645 function isAsyncImport() {
646   return context.getParameter("asyncImport") === "true" ||
647           context.getParameter("asyncImport") === true;
648 }
649 
650 /**
651  * Downloads files by given url(s).
652  *
653  * @param theUrls the url(s)
654  */
655 function downloadFiles(theUrls) {
656   var aRes = [];
657   if (theUrls == null) {
658     return aRes;
659   }
660   for (var i = 0; i < theUrls.length; i++) {
661     var aFileName = theUrls[i].substr(theUrls[i].lastIndexOf("/") + 1);
662     aFileName = aFileName.split("?")[0];
663     var aFile = Request.get(theUrls[i], { dataType: "file", selfSigned: true, fileName: aFileName });
664     myDownloadTempDir = aFile.parent;
665     aRes.push(aFile);
666   }
667   return aRes;
668 }
669 
670 /**
671  * Adds the upload id and upload username to the xmp of the asset.
672  * @param theAsset
673  */
674 function addUploadData(theAsset)
675 {
676   var aProperty = new Property(
677       "http://mediabeacon.com/ns/default/1.0/",
678       "uploadUser"
679   );
680   theAsset.xmp.meta.addField(aProperty, context.getUser().username);
681   var aUploadIDProperty = new Property(
682       "http://mediabeacon.com/ns/default/1.0/",
683       "uploadId"
684   );
685   theAsset.xmp.meta.addField(aUploadIDProperty, Date.now().toString());
686   theAsset.writeXmp();
687   stampMetadata(theAsset);
688 }
689 
690 /**
691  * @name Upload
692  * @class Upload an asset into MediaBeacon
693  * @description For every asset defined in "data", this endpoint sets the xmp from the given xmp string
694  * @param file  the file needs to be added to the body of the POST in the form data with the name "file"
695  * @param dest the path to the destination folder "upload/here/"
696  * @param destId the directory id of the destination folder.
697  * @param [unzip=false] unzip the uploaded files
698  * @param [version=false] if a file already exists in the destination, new version it with the uploaded file
699  * @param [verbose=false] Setting this to true will collect a variety of default values for each asset.
700  * @param [fields] An array of field id's to collect the values for each asset
701  * @param [message] Comment for versioned file
702  * @param [replaceMetadata=false] Replace metadata for versioned file
703  * @param [replaceName=false] Replace name for versioned file
704  * @param [triggerAssetBasedWorkflow=true] set to false to avoid triggering other asset added workflows
705  * @param [asyncImport=false] set to true to import the uploaded file asynchronously
706  * @param metadata a JSON object contains key as field Id and value as value of the field to be stamped to the uploaded asset.
707  * metadata =
708  * {
709  *    "http://purl.org/dc/elements/1.1/ subject" : "newValue"
710  * }
711  * @returns [{assetInfo}, ... ]
712  *
713  * @deprecated [replace=false] use 'version' instead, now it creates a new version if the file already exists.
714  *
715  * @example /wf/restapi/v2/upload
716  *
717  * Parameters:
718  * file={{FILE DATA IN POST BODY}}
719  * dest="upload/here/"
720  * verbose=true
721  *
722  * Response:
723  [
724  {
725 		 "id": 201629401,
726 		 "name": "heic0006a_orig.tif",
727 		 "path": "upload/here/heic0006a_orig.tif",
728 		 "height": 746,
729 		 "width": 1500,
730 		 "bytes": 1459026,
731 		 "lastModified": 1508187662000,
732 		 "mimeType": "image/tiff",
733 		 "previews": {
734 			 "thumbnail": "../servlet/jb.view?table=thumbnails&col=thumbnails&id=pe_323031363239343031",
735 			 "viewex": "../servlet/jb.view?table=viewex&col=viewex&id=pe_323031363239343031",
736 			 "downloadUrl": "../servlet/dload?id=pe_323031363239343031"
737 		 }
738 	 }
739  ]
740 
741  * @example /wf/restapi/v2/upload
742  *
743  * Parameters:
744  * file={{ZIPPED FILE}}
745  * dest="upload/here/"
746  * unzip=true
747  * verbose=true
748  *
749  * Response:
750  [
751  {
752 		 "name": "hubble",
753 		 "path": "upload/here/",
754 		 "resolver": "directory://162",
755 		 "assets": [
756 			 {
757 				 "id": 201629404,
758 				 "name": "heic0006a_orig.tif",
759 				 "path": "upload/here/heic0006a_orig.tif",
760 				 "height": 746,
761 				 "width": 1500,
762 				 "bytes": 1459026,
763 				 "lastModified": 1262635000,
764 				 "mimeType": "image/tiff",
765 				 "previews": {
766 					 "thumbnail": "../servlet/jb.view?table=thumbnails&col=thumbnails&id=pe_323031363239343034",
767 					 "viewex": "../servlet/jb.view?table=viewex&col=viewex&id=pe_323031363239343034",
768 					 "downloadUrl": "../servlet/dload?id=pe_323031363239343034"
769 				 }
770 			 },
771 			 {
772 				 "id": 201629405,
773 				 "name": "heic0106a1_orig.tif",
774 				 "path": "upload/here/heic0106a1_orig.tif",
775 				 "height": 573,
776 				 "width": 564,
777 				 "bytes": 817080,
778 				 "lastModified": 1262635000,
779 				 "mimeType": "image/tiff",
780 				 "previews": {
781 					 "thumbnail": "../servlet/jb.view?table=thumbnails&col=thumbnails&id=pe_323031363239343035",
782 					 "viewex": "../servlet/jb.view?table=viewex&col=viewex&id=pe_323031363239343035",
783 					 "downloadUrl": "../servlet/dload?id=pe_323031363239343035"
784 				 }
785 			 }
786 		 ]
787 	 }
788  ]
789 
790  * @example /wf/restapi/v2/upload
791  *
792  * Parameters:
793  * file={{FILE DATA IN POST BODY}}
794  * destId=123
795  * verbose=true
796  *
797  * Response:
798  [
799     {
800 		 "id": 201629401,
801 		 "name": "heic0006a_orig.tif",
802 		 "path": "upload/here/heic0006a_orig.tif",
803 		 "directoryId": 123
804 		 "height": 746,
805 		 "width": 1500,
806 		 "bytes": 1459026,
807 		 "lastModified": 1508187662000,
808 		 "mimeType": "image/tiff",
809 		 "previews": {
810 			 "thumbnail": "../servlet/jb.view?table=thumbnails&col=thumbnails&id=pe_323031363239343031",
811 			 "viewex": "../servlet/jb.view?table=viewex&col=viewex&id=pe_323031363239343031",
812 			 "downloadUrl": "../servlet/dload?id=pe_323031363239343031"
813 		 }
814 	 }
815  ]
816 
817  * @example /wf/restapi/v2/upload
818  *
819  * Parameters:
820  * file={{FILE DATA IN POST BODY}}
821  * dest="upload/here/"
822  * verbose=true
823  * asyncImport=true
824  *
825  * Response:
826  [
827 	 {
828 		 "name": "heic0006a_orig.tif",
829 		 "path": "upload/here/heic0006a_orig.tif",
830 		 "actionId": "12AF58A0-6C54-4413-91D7-D1A521B9D6D2"
831 	 }
832  ]
833  */
834 function main() {
835   if (!context.getUser().isPermissionEnabled("fileUpload")) {
836     REST.pushError(REST.errors.e403, "upload permission is not enabled");
837     return REST.execute();
838   }
839   REST.setCallback(upload);
840   return REST.execute();
841 }
842 main();
843