summaryrefslogtreecommitdiffstats
path: root/debian/missing-sources/plupload/javascript/plupload.html5.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 07:56:53 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 07:56:53 +0000
commita9c818418b81b93680170e1a84d4e221e578ad2f (patch)
tree5b883aa428f1edb12f5d40f9768438ee16a7ed6b /debian/missing-sources/plupload/javascript/plupload.html5.js
parentAdding upstream version 6.4.3+dfsg1. (diff)
downloadwordpress-a9c818418b81b93680170e1a84d4e221e578ad2f.tar.xz
wordpress-a9c818418b81b93680170e1a84d4e221e578ad2f.zip
Adding debian version 6.4.3+dfsg1-1.debian/6.4.3+dfsg1-1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'debian/missing-sources/plupload/javascript/plupload.html5.js')
-rw-r--r--debian/missing-sources/plupload/javascript/plupload.html5.js1525
1 files changed, 1525 insertions, 0 deletions
diff --git a/debian/missing-sources/plupload/javascript/plupload.html5.js b/debian/missing-sources/plupload/javascript/plupload.html5.js
new file mode 100644
index 0000000..4b154b8
--- /dev/null
+++ b/debian/missing-sources/plupload/javascript/plupload.html5.js
@@ -0,0 +1,1525 @@
+/**
+ * plupload.html5.js
+ *
+ * Copyright 2009, Moxiecode Systems AB
+ * Released under GPL License.
+ *
+ * License: http://www.plupload.com/license
+ * Contributing: http://www.plupload.com/contributing
+ */
+
+// JSLint defined globals
+/*global plupload:false, File:false, window:false, atob:false, FormData:false, FileReader:false, ArrayBuffer:false, Uint8Array:false, BlobBuilder:false, unescape:false */
+
+(function(window, document, plupload, undef) {
+ var html5files = {}, // queue of original File objects
+ fakeSafariDragDrop;
+
+ /**
+ * Detect subsampling in loaded image.
+ * In iOS, larger images than 2M pixels may be subsampled in rendering.
+ */
+ function detectSubsampling(img) {
+ var iw = img.naturalWidth, ih = img.naturalHeight;
+ if (iw * ih > 1024 * 1024) { // subsampling may happen over megapixel image
+ var canvas = document.createElement('canvas');
+ canvas.width = canvas.height = 1;
+ var ctx = canvas.getContext('2d');
+ ctx.drawImage(img, -iw + 1, 0);
+ // subsampled image becomes half smaller in rendering size.
+ // check alpha channel value to confirm image is covering edge pixel or not.
+ // if alpha value is 0 image is not covering, hence subsampled.
+ return ctx.getImageData(0, 0, 1, 1).data[3] === 0;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Detecting vertical squash in loaded image.
+ * Fixes a bug which squash image vertically while drawing into canvas for some images.
+ */
+ function detectVerticalSquash(img, iw, ih) {
+ var canvas = document.createElement('canvas');
+ canvas.width = 1;
+ canvas.height = ih;
+ var ctx = canvas.getContext('2d');
+ ctx.drawImage(img, 0, 0);
+ var data = ctx.getImageData(0, 0, 1, ih).data;
+ // search image edge pixel position in case it is squashed vertically.
+ var sy = 0;
+ var ey = ih;
+ var py = ih;
+ while (py > sy) {
+ var alpha = data[(py - 1) * 4 + 3];
+ if (alpha === 0) {
+ ey = py;
+ } else {
+ sy = py;
+ }
+
+ py = (ey + sy) >> 1;
+ }
+
+ var ratio = (py / ih);
+ return (ratio === 0) ? 1 : ratio;
+ }
+
+ /**
+ * Rendering image element (with resizing) into the canvas element
+ */
+ function renderImageToCanvas(img, canvas, options) {
+ var iw = img.naturalWidth, ih = img.naturalHeight;
+ var width = options.width, height = options.height;
+ var ctx = canvas.getContext('2d');
+ ctx.save();
+ var subsampled = detectSubsampling(img);
+ if (subsampled) {
+ iw /= 2;
+ ih /= 2;
+ }
+
+ var d = 1024; // size of tiling canvas
+ var tmpCanvas = document.createElement('canvas');
+ tmpCanvas.width = tmpCanvas.height = d;
+ var tmpCtx = tmpCanvas.getContext('2d');
+ var vertSquashRatio = detectVerticalSquash(img, iw, ih);
+ var sy = 0;
+ while (sy < ih) {
+ var sh = sy + d > ih ? ih - sy : d;
+ var sx = 0;
+ while (sx < iw) {
+ var sw = sx + d > iw ? iw - sx : d;
+ tmpCtx.clearRect(0, 0, d, d);
+ tmpCtx.drawImage(img, -sx, -sy);
+ var dx = (sx * width / iw) << 0;
+ var dw = Math.ceil(sw * width / iw);
+ var dy = (sy * height / ih / vertSquashRatio) << 0;
+ var dh = Math.ceil(sh * height / ih / vertSquashRatio);
+ ctx.drawImage(tmpCanvas, 0, 0, sw, sh, dx, dy, dw, dh);
+ sx += d;
+ }
+
+ sy += d;
+ }
+
+ ctx.restore();
+ tmpCanvas = tmpCtx = null;
+ }
+
+ function readFileAsDataURL(file, callback) {
+ var reader;
+
+ // Use FileReader if it's available
+ if ("FileReader" in window) {
+ reader = new FileReader();
+ reader.readAsDataURL(file);
+ reader.onload = function() {
+ callback(reader.result);
+ };
+ } else {
+ return callback(file.getAsDataURL());
+ }
+ }
+
+ function readFileAsBinary(file, callback) {
+ var reader;
+
+ // Use FileReader if it's available
+ if ("FileReader" in window) {
+ reader = new FileReader();
+ reader.readAsBinaryString(file);
+ reader.onload = function() {
+ callback(reader.result);
+ };
+ } else {
+ return callback(file.getAsBinary());
+ }
+ }
+
+ function scaleImage(file, resize, mime, callback) {
+ var canvas, context, img, scale,
+ up = this;
+
+ readFileAsDataURL(html5files[file.id], function(data) {
+ // Setup canvas and context
+ canvas = document.createElement("canvas");
+ canvas.style.display = 'none';
+ document.body.appendChild(canvas);
+
+ // Load image
+ img = new Image();
+ img.onerror = img.onabort = function() {
+ // Failed to load, the image may be invalid
+ callback({success : false});
+ };
+ img.onload = function() {
+ var width, height, percentage, jpegHeaders, exifParser;
+
+ if (!resize['width']) {
+ resize['width'] = img.width;
+ }
+
+ if (!resize['height']) {
+ resize['height'] = img.height;
+ }
+
+ scale = Math.min(resize.width / img.width, resize.height / img.height);
+
+ if (scale < 1) {
+ width = Math.round(img.width * scale);
+ height = Math.round(img.height * scale);
+ } else if (resize['quality'] && mime === 'image/jpeg') {
+ // do not upsize, but drop the quality for jpegs
+ width = img.width;
+ height = img.height;
+ } else {
+ // Image does not need to be resized
+ callback({success : false});
+ return;
+ }
+
+ // Scale image and canvas
+ canvas.width = width;
+ canvas.height = height;
+ renderImageToCanvas(img, canvas, { width: width, height: height });
+
+ // Preserve JPEG headers
+ if (mime === 'image/jpeg') {
+ jpegHeaders = new JPEG_Headers(atob(data.substring(data.indexOf('base64,') + 7)));
+ if (jpegHeaders['headers'] && jpegHeaders['headers'].length) {
+ exifParser = new ExifParser();
+
+ if (exifParser.init(jpegHeaders.get('exif')[0])) {
+ // Set new width and height
+ exifParser.setExif('PixelXDimension', width);
+ exifParser.setExif('PixelYDimension', height);
+
+ // Update EXIF header
+ jpegHeaders.set('exif', exifParser.getBinary());
+
+ // trigger Exif events only if someone listens to them
+ if (up.hasEventListener('ExifData')) {
+ up.trigger('ExifData', file, exifParser.EXIF());
+ }
+
+ if (up.hasEventListener('GpsData')) {
+ up.trigger('GpsData', file, exifParser.GPS());
+ }
+ }
+ }
+ }
+
+ if (resize['quality'] && mime === 'image/jpeg') {
+ // Try quality property first
+ try {
+ data = canvas.toDataURL(mime, resize['quality'] / 100); // used to throw an exception in Firefox
+ } catch (ex) {
+ data = canvas.toDataURL(mime);
+ }
+ } else {
+ data = canvas.toDataURL(mime);
+ }
+
+
+ // Remove data prefix information and grab the base64 encoded data and decode it
+ data = data.substring(data.indexOf('base64,') + 7);
+ data = atob(data);
+
+ // Restore JPEG headers if applicable
+ if (jpegHeaders && jpegHeaders['headers'] && jpegHeaders['headers'].length) {
+ data = jpegHeaders.restore(data);
+ jpegHeaders.purge(); // free memory
+ }
+
+ // Remove canvas and execute callback with decoded image data
+ canvas.parentNode.removeChild(canvas);
+ callback({success : true, data : data});
+ };
+
+ img.src = data;
+ });
+ }
+
+ /**
+ * HMTL5 implementation. This runtime supports these features: dragdrop, jpgresize, pngresize.
+ *
+ * @static
+ * @class plupload.runtimes.Html5
+ * @extends plupload.Runtime
+ */
+ plupload.runtimes.Html5 = plupload.addRuntime("html5", {
+ /**
+ * Returns a list of supported features for the runtime.
+ *
+ * @return {Object} Name/value object with supported features.
+ */
+ getFeatures : function() {
+ var xhr, hasXhrSupport, hasProgress, canSendBinary, dataAccessSupport, sliceSupport;
+
+ hasXhrSupport = hasProgress = dataAccessSupport = sliceSupport = false;
+
+ if (window.XMLHttpRequest) {
+ xhr = new XMLHttpRequest();
+ hasProgress = !!xhr.upload;
+ hasXhrSupport = !!(xhr.sendAsBinary || xhr.upload);
+ }
+
+ // Check for support for various features
+ if (hasXhrSupport) {
+ canSendBinary = !!(xhr.sendAsBinary || (window.Uint8Array && window.ArrayBuffer));
+
+ // Set dataAccessSupport only for Gecko since BlobBuilder and XHR doesn't handle binary data correctly
+ dataAccessSupport = !!(File && (File.prototype.getAsDataURL || window.FileReader) && canSendBinary);
+ sliceSupport = !!(File && (File.prototype.mozSlice || File.prototype.webkitSlice || File.prototype.slice));
+ }
+
+ // sniff out Safari for Windows and fake drag/drop
+ fakeSafariDragDrop = plupload.ua.safari && plupload.ua.windows;
+
+ return {
+ html5: hasXhrSupport, // This is a special one that we check inside the init call
+ dragdrop: (function() {
+ // this comes directly from Modernizr: http://www.modernizr.com/
+ var div = document.createElement('div');
+ return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div);
+ }()),
+ jpgresize: dataAccessSupport,
+ pngresize: dataAccessSupport,
+ multipart: dataAccessSupport || !!window.FileReader || !!window.FormData,
+ canSendBinary: canSendBinary,
+ // gecko 2/5/6 can't send blob with FormData: https://bugzilla.mozilla.org/show_bug.cgi?id=649150
+ // Android browsers (default one and Dolphin) seem to have the same issue, see: #613
+ cantSendBlobInFormData: !!(plupload.ua.gecko && window.FormData && window.FileReader && !FileReader.prototype.readAsArrayBuffer) || plupload.ua.android,
+ progress: hasProgress,
+ chunks: sliceSupport,
+ // Safari on Windows has problems when selecting multiple files
+ multi_selection: !(plupload.ua.safari && plupload.ua.windows),
+ // WebKit and Gecko 2+ can trigger file dialog progrmmatically
+ triggerDialog: (plupload.ua.gecko && window.FormData || plupload.ua.webkit)
+ };
+ },
+
+ /**
+ * Initializes the upload runtime.
+ *
+ * @method init
+ * @param {plupload.Uploader} uploader Uploader instance that needs to be initialized.
+ * @param {function} callback Callback to execute when the runtime initializes or fails to initialize. If it succeeds an object with a parameter name success will be set to true.
+ */
+ init : function(uploader, callback) {
+ var features, xhr;
+
+ function addSelectedFiles(native_files) {
+ var file, i, files = [], id, fileNames = {};
+
+ // Add the selected files to the file queue
+ for (i = 0; i < native_files.length; i++) {
+ file = native_files[i];
+
+ // Safari on Windows will add first file from dragged set multiple times
+ // @see: https://bugs.webkit.org/show_bug.cgi?id=37957
+ if (fileNames[file.name] && plupload.ua.safari && plupload.ua.windows) {
+ continue;
+ }
+ fileNames[file.name] = true;
+
+ // Store away gears blob internally
+ id = plupload.guid();
+ html5files[id] = file;
+
+ // Expose id, name and size
+ files.push(new plupload.File(id, file.fileName || file.name, file.fileSize || file.size)); // fileName / fileSize depricated
+ }
+
+ // Trigger FilesAdded event if we added any
+ if (files.length) {
+ uploader.trigger("FilesAdded", files);
+ }
+ }
+
+ // No HTML5 upload support
+ features = this.getFeatures();
+ if (!features.html5) {
+ callback({success : false});
+ return;
+ }
+
+ uploader.bind("Init", function(up) {
+ var inputContainer, browseButton, mimes = [], i, y, filters = up.settings.filters, ext, type, container = document.body, inputFile;
+
+ // Create input container and insert it at an absolute position within the browse button
+ inputContainer = document.createElement('div');
+ inputContainer.id = up.id + '_html5_container';
+
+ plupload.extend(inputContainer.style, {
+ position : 'absolute',
+ background : uploader.settings.shim_bgcolor || 'transparent',
+ width : '100px',
+ height : '100px',
+ overflow : 'hidden',
+ zIndex : 99999,
+ opacity : uploader.settings.shim_bgcolor ? '' : 0 // Force transparent if bgcolor is undefined
+ });
+ inputContainer.className = 'plupload html5';
+
+ if (uploader.settings.container) {
+ container = document.getElementById(uploader.settings.container);
+ if (plupload.getStyle(container, 'position') === 'static') {
+ container.style.position = 'relative';
+ }
+ }
+
+ container.appendChild(inputContainer);
+
+ // Convert extensions to mime types list
+ no_type_restriction:
+ for (i = 0; i < filters.length; i++) {
+ ext = filters[i].extensions.split(/,/);
+
+ for (y = 0; y < ext.length; y++) {
+
+ // If there's an asterisk in the list, then accept attribute is not required
+ if (ext[y] === '*') {
+ mimes = [];
+ break no_type_restriction;
+ }
+
+ type = plupload.mimeTypes[ext[y]];
+
+ if (type && plupload.inArray(type, mimes) === -1) {
+ mimes.push(type);
+ }
+ }
+ }
+
+
+ // Insert the input inside the input container
+ inputContainer.innerHTML = '<input id="' + uploader.id + '_html5" ' + ' style="font-size:999px"' +
+ ' type="file" accept="' + mimes.join(',') + '" ' +
+ (uploader.settings.multi_selection && uploader.features.multi_selection ? 'multiple="multiple"' : '') + ' />';
+
+ inputContainer.scrollTop = 100;
+ inputFile = document.getElementById(uploader.id + '_html5');
+
+ if (up.features.triggerDialog) {
+ plupload.extend(inputFile.style, {
+ position: 'absolute',
+ width: '100%',
+ height: '100%'
+ });
+ } else {
+ // shows arrow cursor instead of the text one, bit more logical
+ plupload.extend(inputFile.style, {
+ cssFloat: 'right',
+ styleFloat: 'right'
+ });
+ }
+
+ inputFile.onchange = function() {
+ // Add the selected files from file input
+ addSelectedFiles(this.files);
+
+ // Clearing the value enables the user to select the same file again if they want to
+ this.value = '';
+ };
+
+ /* Since we have to place input[type=file] on top of the browse_button for some browsers (FF, Opera),
+ browse_button loses interactivity, here we try to neutralize this issue highlighting browse_button
+ with a special classes
+ TODO: needs to be revised as things will change */
+ browseButton = document.getElementById(up.settings.browse_button);
+ if (browseButton) {
+ var hoverClass = up.settings.browse_button_hover,
+ activeClass = up.settings.browse_button_active,
+ topElement = up.features.triggerDialog ? browseButton : inputContainer;
+
+ if (hoverClass) {
+ plupload.addEvent(topElement, 'mouseover', function() {
+ plupload.addClass(browseButton, hoverClass);
+ }, up.id);
+ plupload.addEvent(topElement, 'mouseout', function() {
+ plupload.removeClass(browseButton, hoverClass);
+ }, up.id);
+ }
+
+ if (activeClass) {
+ plupload.addEvent(topElement, 'mousedown', function() {
+ plupload.addClass(browseButton, activeClass);
+ }, up.id);
+ plupload.addEvent(document.body, 'mouseup', function() {
+ plupload.removeClass(browseButton, activeClass);
+ }, up.id);
+ }
+
+ // Route click event to the input[type=file] element for supporting browsers
+ if (up.features.triggerDialog) {
+ plupload.addEvent(browseButton, 'click', function(e) {
+ var input = document.getElementById(up.id + '_html5');
+ if (input && !input.disabled) { // for some reason FF (up to 8.0.1 so far) lets to click disabled input[type=file]
+ input.click();
+ }
+ e.preventDefault();
+ }, up.id);
+ }
+ }
+ });
+
+ // Add drop handler
+ uploader.bind("PostInit", function() {
+ var dropElm = document.getElementById(uploader.settings.drop_element);
+
+ if (dropElm) {
+ // Lets fake drag/drop on Safari by moving a input type file in front of the mouse pointer when we drag into the drop zone
+ // TODO: Remove this logic once Safari has official drag/drop support
+ if (fakeSafariDragDrop) {
+ plupload.addEvent(dropElm, 'dragenter', function(e) {
+ var dropInputElm, dropPos, dropSize;
+
+ // Get or create drop zone
+ dropInputElm = document.getElementById(uploader.id + "_drop");
+ if (!dropInputElm) {
+ dropInputElm = document.createElement("input");
+ dropInputElm.setAttribute('type', "file");
+ dropInputElm.setAttribute('id', uploader.id + "_drop");
+ dropInputElm.setAttribute('multiple', 'multiple');
+
+ plupload.addEvent(dropInputElm, 'change', function() {
+ // Add the selected files from file input
+ addSelectedFiles(this.files);
+
+ // Remove input element
+ plupload.removeEvent(dropInputElm, 'change', uploader.id);
+ dropInputElm.parentNode.removeChild(dropInputElm);
+ }, uploader.id);
+
+ // avoid event propagation as Safari cancels the whole capability of dropping files if you are doing a preventDefault of this event on the document body
+ plupload.addEvent(dropInputElm, 'dragover', function(e) {
+ e.stopPropagation();
+ }, uploader.id);
+
+ dropElm.appendChild(dropInputElm);
+ }
+
+ dropPos = plupload.getPos(dropElm, document.getElementById(uploader.settings.container));
+ dropSize = plupload.getSize(dropElm);
+
+ if (plupload.getStyle(dropElm, 'position') === 'static') {
+ plupload.extend(dropElm.style, {
+ position : 'relative'
+ });
+ }
+
+ plupload.extend(dropInputElm.style, {
+ position : 'absolute',
+ display : 'block',
+ top : 0,
+ left : 0,
+ width : dropSize.w + 'px',
+ height : dropSize.h + 'px',
+ opacity : 0
+ });
+ }, uploader.id);
+
+ return;
+ }
+
+ // Block browser default drag over
+ plupload.addEvent(dropElm, 'dragover', function(e) {
+ e.preventDefault();
+ }, uploader.id);
+
+ // Attach drop handler and grab files
+ plupload.addEvent(dropElm, 'drop', function(e) {
+ var dataTransfer = e.dataTransfer;
+
+ // Add dropped files
+ if (dataTransfer && dataTransfer.files) {
+ addSelectedFiles(dataTransfer.files);
+ }
+
+ e.preventDefault();
+ }, uploader.id);
+ }
+ });
+
+ uploader.bind("Refresh", function(up) {
+ var browseButton, browsePos, browseSize, inputContainer, zIndex;
+
+ browseButton = document.getElementById(uploader.settings.browse_button);
+ if (browseButton) {
+ browsePos = plupload.getPos(browseButton, document.getElementById(up.settings.container));
+ browseSize = plupload.getSize(browseButton);
+ inputContainer = document.getElementById(uploader.id + '_html5_container');
+
+ plupload.extend(inputContainer.style, {
+ top : browsePos.y + 'px',
+ left : browsePos.x + 'px',
+ width : browseSize.w + 'px',
+ height : browseSize.h + 'px'
+ });
+
+ // for WebKit place input element underneath the browse button and route onclick event
+ // TODO: revise when browser support for this feature will change
+ if (uploader.features.triggerDialog) {
+ if (plupload.getStyle(browseButton, 'position') === 'static') {
+ plupload.extend(browseButton.style, {
+ position : 'relative'
+ });
+ }
+
+ zIndex = parseInt(plupload.getStyle(browseButton, 'zIndex'), 10);
+ if (isNaN(zIndex)) {
+ zIndex = 0;
+ }
+
+ plupload.extend(browseButton.style, {
+ zIndex : zIndex
+ });
+
+ plupload.extend(inputContainer.style, {
+ zIndex : zIndex - 1
+ });
+ }
+ }
+ });
+
+ uploader.bind("DisableBrowse", function(up, disabled) {
+ var input = document.getElementById(up.id + '_html5');
+ if (input) {
+ input.disabled = disabled;
+ }
+ });
+
+ uploader.bind("CancelUpload", function() {
+ if (xhr && xhr.abort) {
+ xhr.abort();
+ }
+ });
+
+ uploader.bind("UploadFile", function(up, file) {
+ var settings = up.settings, nativeFile, resize;
+
+ function w3cBlobSlice(blob, start, end) {
+ var blobSlice;
+
+ if (File.prototype.slice) {
+ try {
+ blob.slice(); // depricated version will throw WRONG_ARGUMENTS_ERR exception
+ return blob.slice(start, end);
+ } catch (e) {
+ // depricated slice method
+ return blob.slice(start, end - start);
+ }
+ // slice method got prefixed: https://bugzilla.mozilla.org/show_bug.cgi?id=649672
+ } else if (blobSlice = File.prototype.webkitSlice || File.prototype.mozSlice) {
+ return blobSlice.call(blob, start, end);
+ } else {
+ return null; // or throw some exception
+ }
+ }
+
+ function sendBinaryBlob(blob) {
+ var chunk = 0, loaded = 0;
+
+
+ function uploadNextChunk() {
+ var chunkBlob, br, chunks, args, chunkSize, curChunkSize, mimeType, url = up.settings.url;
+
+ function sendAsBinaryString(bin) {
+ if (xhr.sendAsBinary) { // Gecko
+ xhr.sendAsBinary(bin);
+ } else if (up.features.canSendBinary) { // WebKit with typed arrays support
+ var ui8a = new Uint8Array(bin.length);
+ for (var i = 0; i < bin.length; i++) {
+ ui8a[i] = (bin.charCodeAt(i) & 0xff);
+ }
+ xhr.send(ui8a.buffer);
+ }
+ }
+
+ function prepareAndSend(bin) {
+ var multipartDeltaSize = 0,
+ boundary = '----pluploadboundary' + plupload.guid(), formData, dashdash = '--', crlf = '\r\n', multipartBlob = '';
+
+ xhr = new XMLHttpRequest;
+
+ // Do we have upload progress support
+ if (xhr.upload) {
+ xhr.upload.onprogress = function(e) {
+ file.loaded = Math.min(file.size, loaded + e.loaded - multipartDeltaSize); // Loaded can be larger than file size due to multipart encoding
+ up.trigger('UploadProgress', file);
+ };
+ }
+
+ xhr.onreadystatechange = function() {
+ var httpStatus, chunkArgs;
+
+ if (xhr.readyState == 4 && up.state !== plupload.STOPPED) {
+ // Getting the HTTP status might fail on some Gecko versions
+ try {
+ httpStatus = xhr.status;
+ } catch (ex) {
+ httpStatus = 0;
+ }
+
+ // Is error status
+ if (httpStatus >= 400) {
+ up.trigger('Error', {
+ code : plupload.HTTP_ERROR,
+ message : plupload.translate('HTTP Error.'),
+ file : file,
+ status : httpStatus
+ });
+ } else {
+ // Handle chunk response
+ if (chunks) {
+ chunkArgs = {
+ chunk : chunk,
+ chunks : chunks,
+ response : xhr.responseText,
+ status : httpStatus
+ };
+
+ up.trigger('ChunkUploaded', file, chunkArgs);
+ loaded += curChunkSize;
+
+ // Stop upload
+ if (chunkArgs.cancelled) {
+ file.status = plupload.FAILED;
+ return;
+ }
+
+ file.loaded = Math.min(file.size, (chunk + 1) * chunkSize);
+ } else {
+ file.loaded = file.size;
+ }
+
+ up.trigger('UploadProgress', file);
+
+ bin = chunkBlob = formData = multipartBlob = null; // Free memory
+
+ // Check if file is uploaded
+ if (!chunks || ++chunk >= chunks) {
+ file.status = plupload.DONE;
+
+ up.trigger('FileUploaded', file, {
+ response : xhr.responseText,
+ status : httpStatus
+ });
+ } else {
+ // Still chunks left
+ uploadNextChunk();
+ }
+ }
+ }
+ };
+
+
+ // Build multipart request
+ if (up.settings.multipart && features.multipart) {
+
+ args.name = file.target_name || file.name;
+
+ xhr.open("post", url, true);
+
+ // Set custom headers
+ plupload.each(up.settings.headers, function(value, name) {
+ xhr.setRequestHeader(name, value);
+ });
+
+
+ // if has FormData support like Chrome 6+, Safari 5+, Firefox 4, use it
+ if (typeof(bin) !== 'string' && !!window.FormData) {
+ formData = new FormData();
+
+ // Add multipart params
+ plupload.each(plupload.extend(args, up.settings.multipart_params), function(value, name) {
+ formData.append(name, value);
+ });
+
+ // Add file and send it
+ formData.append(up.settings.file_data_name, bin);
+ xhr.send(formData);
+
+ return;
+ } // if no FormData we can still try to send it directly as last resort (see below)
+
+
+ if (typeof(bin) === 'string') {
+ // Trying to send the whole thing as binary...
+
+ // multipart request
+ xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
+
+ // append multipart parameters
+ plupload.each(plupload.extend(args, up.settings.multipart_params), function(value, name) {
+ multipartBlob += dashdash + boundary + crlf +
+ 'Content-Disposition: form-data; name="' + name + '"' + crlf + crlf;
+
+ multipartBlob += unescape(encodeURIComponent(value)) + crlf;
+ });
+
+ mimeType = plupload.mimeTypes[file.name.replace(/^.+\.([^.]+)/, '$1').toLowerCase()] || 'application/octet-stream';
+
+ // Build RFC2388 blob
+ multipartBlob += dashdash + boundary + crlf +
+ 'Content-Disposition: form-data; name="' + up.settings.file_data_name + '"; filename="' + unescape(encodeURIComponent(file.name)) + '"' + crlf +
+ 'Content-Type: ' + mimeType + crlf + crlf +
+ bin + crlf +
+ dashdash + boundary + dashdash + crlf;
+
+ multipartDeltaSize = multipartBlob.length - bin.length;
+ bin = multipartBlob;
+
+ sendAsBinaryString(bin);
+ return; // will return from here only if shouldn't send binary
+ }
+ }
+
+ // if no multipart, or last resort, send as binary stream
+ url = plupload.buildUrl(up.settings.url, plupload.extend(args, up.settings.multipart_params));
+
+ xhr.open("post", url, true);
+
+ xhr.setRequestHeader('Content-Type', 'application/octet-stream'); // Binary stream header
+
+ // Set custom headers
+ plupload.each(up.settings.headers, function(value, name) {
+ xhr.setRequestHeader(name, value);
+ });
+
+ if (typeof(bin) === 'string') {
+ sendAsBinaryString(bin);
+ } else {
+ xhr.send(bin);
+ }
+ } // prepareAndSend
+
+
+ // File upload finished
+ if (file.status == plupload.DONE || file.status == plupload.FAILED || up.state == plupload.STOPPED) {
+ return;
+ }
+
+ // Standard arguments
+ args = {name : file.target_name || file.name};
+
+ // Only add chunking args if needed
+ if (settings.chunk_size && file.size > settings.chunk_size && (features.chunks || typeof(blob) == 'string')) { // blob will be of type string if it was loaded in memory
+ chunkSize = settings.chunk_size;
+ chunks = Math.ceil(file.size / chunkSize);
+ curChunkSize = Math.min(chunkSize, file.size - (chunk * chunkSize));
+
+ // Blob is string so we need to fake chunking, this is not
+ // ideal since the whole file is loaded into memory
+ if (typeof(blob) == 'string') {
+ chunkBlob = blob.substring(chunk * chunkSize, chunk * chunkSize + curChunkSize);
+ } else {
+ // Slice the chunk
+ chunkBlob = w3cBlobSlice(blob, chunk * chunkSize, chunk * chunkSize + curChunkSize);
+ }
+
+ // Setup query string arguments
+ args.chunk = chunk;
+ args.chunks = chunks;
+ } else {
+ curChunkSize = file.size;
+ chunkBlob = blob;
+ }
+
+ // workaround for Android and Gecko 2,5,6 FormData+Blob bug: https://bugzilla.mozilla.org/show_bug.cgi?id=649150
+ if (up.settings.multipart && features.multipart && typeof(chunkBlob) !== 'string' && window.FileReader && features.cantSendBlobInFormData && features.chunks && up.settings.chunk_size) { // Gecko 2,5,6
+ (function() {
+ var fr = new FileReader(); // we need to recreate FileReader object in Android, otherwise it hangs
+ fr.onload = function() {
+ prepareAndSend(fr.result);
+ fr = null; // maybe give a hand to GC (Gecko had problems with this)
+ }
+ fr.readAsBinaryString(chunkBlob);
+ }());
+ } else {
+ prepareAndSend(chunkBlob);
+ }
+ }
+
+ // Start uploading chunks
+ uploadNextChunk();
+ }
+
+ nativeFile = html5files[file.id];
+
+ // Resize image if it's a supported format and resize is enabled
+ if (features.jpgresize && up.settings.resize && /\.(png|jpg|jpeg)$/i.test(file.name)) {
+ scaleImage.call(up, file, up.settings.resize, /\.png$/i.test(file.name) ? 'image/png' : 'image/jpeg', function(res) {
+ // If it was scaled send the scaled image if it failed then
+ // send the raw image and let the server do the scaling
+ if (res.success) {
+ file.size = res.data.length;
+ sendBinaryBlob(res.data);
+ } else if (features.chunks) {
+ sendBinaryBlob(nativeFile);
+ } else {
+ readFileAsBinary(nativeFile, sendBinaryBlob); // for browsers not supporting File.slice (e.g. FF3.6)
+ }
+ });
+ // if there's no way to slice file without preloading it in memory, preload it
+ } else if (!features.chunks && features.jpgresize) {
+ readFileAsBinary(nativeFile, sendBinaryBlob);
+ } else {
+ sendBinaryBlob(nativeFile);
+ }
+ });
+
+
+ uploader.bind('Destroy', function(up) {
+ var name, element, container = document.body,
+ elements = {
+ inputContainer: up.id + '_html5_container',
+ inputFile: up.id + '_html5',
+ browseButton: up.settings.browse_button,
+ dropElm: up.settings.drop_element
+ };
+
+ // Unbind event handlers
+ for (name in elements) {
+ element = document.getElementById(elements[name]);
+ if (element) {
+ plupload.removeAllEvents(element, up.id);
+ }
+ }
+ plupload.removeAllEvents(document.body, up.id);
+
+ if (up.settings.container) {
+ container = document.getElementById(up.settings.container);
+ }
+
+ // Remove mark-up
+ container.removeChild(document.getElementById(elements.inputContainer));
+ });
+
+ callback({success : true});
+ }
+ });
+
+ function BinaryReader() {
+ var II = false, bin;
+
+ // Private functions
+ function read(idx, size) {
+ var mv = II ? 0 : -8 * (size - 1), sum = 0, i;
+
+ for (i = 0; i < size; i++) {
+ sum |= (bin.charCodeAt(idx + i) << Math.abs(mv + i*8));
+ }
+
+ return sum;
+ }
+
+ function putstr(segment, idx, length) {
+ var length = arguments.length === 3 ? length : bin.length - idx - 1;
+
+ bin = bin.substr(0, idx) + segment + bin.substr(length + idx);
+ }
+
+ function write(idx, num, size) {
+ var str = '', mv = II ? 0 : -8 * (size - 1), i;
+
+ for (i = 0; i < size; i++) {
+ str += String.fromCharCode((num >> Math.abs(mv + i*8)) & 255);
+ }
+
+ putstr(str, idx, size);
+ }
+
+ // Public functions
+ return {
+ II: function(order) {
+ if (order === undef) {
+ return II;
+ } else {
+ II = order;
+ }
+ },
+
+ init: function(binData) {
+ II = false;
+ bin = binData;
+ },
+
+ SEGMENT: function(idx, length, segment) {
+ switch (arguments.length) {
+ case 1:
+ return bin.substr(idx, bin.length - idx - 1);
+ case 2:
+ return bin.substr(idx, length);
+ case 3:
+ putstr(segment, idx, length);
+ break;
+ default: return bin;
+ }
+ },
+
+ BYTE: function(idx) {
+ return read(idx, 1);
+ },
+
+ SHORT: function(idx) {
+ return read(idx, 2);
+ },
+
+ LONG: function(idx, num) {
+ if (num === undef) {
+ return read(idx, 4);
+ } else {
+ write(idx, num, 4);
+ }
+ },
+
+ SLONG: function(idx) { // 2's complement notation
+ var num = read(idx, 4);
+
+ return (num > 2147483647 ? num - 4294967296 : num);
+ },
+
+ STRING: function(idx, size) {
+ var str = '';
+
+ for (size += idx; idx < size; idx++) {
+ str += String.fromCharCode(read(idx, 1));
+ }
+
+ return str;
+ }
+ };
+ }
+
+ function JPEG_Headers(data) {
+
+ var markers = {
+ 0xFFE1: {
+ app: 'EXIF',
+ name: 'APP1',
+ signature: "Exif\0"
+ },
+ 0xFFE2: {
+ app: 'ICC',
+ name: 'APP2',
+ signature: "ICC_PROFILE\0"
+ },
+ 0xFFED: {
+ app: 'IPTC',
+ name: 'APP13',
+ signature: "Photoshop 3.0\0"
+ }
+ },
+ headers = [], read, idx, marker = undef, length = 0, limit;
+
+
+ read = new BinaryReader();
+ read.init(data);
+
+ // Check if data is jpeg
+ if (read.SHORT(0) !== 0xFFD8) {
+ return;
+ }
+
+ idx = 2;
+ limit = Math.min(1048576, data.length);
+
+ while (idx <= limit) {
+ marker = read.SHORT(idx);
+
+ // omit RST (restart) markers
+ if (marker >= 0xFFD0 && marker <= 0xFFD7) {
+ idx += 2;
+ continue;
+ }
+
+ // no headers allowed after SOS marker
+ if (marker === 0xFFDA || marker === 0xFFD9) {
+ break;
+ }
+
+ length = read.SHORT(idx + 2) + 2;
+
+ if (markers[marker] &&
+ read.STRING(idx + 4, markers[marker].signature.length) === markers[marker].signature) {
+ headers.push({
+ hex: marker,
+ app: markers[marker].app.toUpperCase(),
+ name: markers[marker].name.toUpperCase(),
+ start: idx,
+ length: length,
+ segment: read.SEGMENT(idx, length)
+ });
+ }
+ idx += length;
+ }
+
+ read.init(null); // free memory
+
+ return {
+
+ headers: headers,
+
+ restore: function(data) {
+ read.init(data);
+
+ // Check if data is jpeg
+ var jpegHeaders = new JPEG_Headers(data);
+
+ if (!jpegHeaders['headers']) {
+ return false;
+ }
+
+ // Delete any existing headers that need to be replaced
+ for (var i = jpegHeaders['headers'].length; i > 0; i--) {
+ var hdr = jpegHeaders['headers'][i - 1];
+ read.SEGMENT(hdr.start, hdr.length, '')
+ }
+ jpegHeaders.purge();
+
+ idx = read.SHORT(2) == 0xFFE0 ? 4 + read.SHORT(4) : 2;
+
+ for (var i = 0, max = headers.length; i < max; i++) {
+ read.SEGMENT(idx, 0, headers[i].segment);
+ idx += headers[i].length;
+ }
+
+ return read.SEGMENT();
+ },
+
+ get: function(app) {
+ var array = [];
+
+ for (var i = 0, max = headers.length; i < max; i++) {
+ if (headers[i].app === app.toUpperCase()) {
+ array.push(headers[i].segment);
+ }
+ }
+ return array;
+ },
+
+ set: function(app, segment) {
+ var array = [];
+
+ if (typeof(segment) === 'string') {
+ array.push(segment);
+ } else {
+ array = segment;
+ }
+
+ for (var i = ii = 0, max = headers.length; i < max; i++) {
+ if (headers[i].app === app.toUpperCase()) {
+ headers[i].segment = array[ii];
+ headers[i].length = array[ii].length;
+ ii++;
+ }
+ if (ii >= array.length) break;
+ }
+ },
+
+ purge: function() {
+ headers = [];
+ read.init(null);
+ }
+ };
+ }
+
+
+ function ExifParser() {
+ // Private ExifParser fields
+ var data, tags, offsets = {}, tagDescs;
+
+ data = new BinaryReader();
+
+ tags = {
+ tiff : {
+ /*
+ The image orientation viewed in terms of rows and columns.
+
+ 1 - The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side.
+ 2 - The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side.
+ 3 - The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side.
+ 4 - The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side.
+ 5 - The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side.
+ 6 - The 0th row is the visual left-hand side of the image, and the 0th column is the visual top.
+ 7 - The 0th row is the visual right-hand side of the image, and the 0th column is the visual top.
+ 8 - The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom.
+ 9 - The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom.
+ */
+ 0x0112: 'Orientation',
+ 0x8769: 'ExifIFDPointer',
+ 0x8825: 'GPSInfoIFDPointer'
+ },
+ exif : {
+ 0x9000: 'ExifVersion',
+ 0xA001: 'ColorSpace',
+ 0xA002: 'PixelXDimension',
+ 0xA003: 'PixelYDimension',
+ 0x9003: 'DateTimeOriginal',
+ 0x829A: 'ExposureTime',
+ 0x829D: 'FNumber',
+ 0x8827: 'ISOSpeedRatings',
+ 0x9201: 'ShutterSpeedValue',
+ 0x9202: 'ApertureValue' ,
+ 0x9207: 'MeteringMode',
+ 0x9208: 'LightSource',
+ 0x9209: 'Flash',
+ 0xA402: 'ExposureMode',
+ 0xA403: 'WhiteBalance',
+ 0xA406: 'SceneCaptureType',
+ 0xA404: 'DigitalZoomRatio',
+ 0xA408: 'Contrast',
+ 0xA409: 'Saturation',
+ 0xA40A: 'Sharpness'
+ },
+ gps : {
+ 0x0000: 'GPSVersionID',
+ 0x0001: 'GPSLatitudeRef',
+ 0x0002: 'GPSLatitude',
+ 0x0003: 'GPSLongitudeRef',
+ 0x0004: 'GPSLongitude'
+ }
+ };
+
+ tagDescs = {
+ 'ColorSpace': {
+ 1: 'sRGB',
+ 0: 'Uncalibrated'
+ },
+
+ 'MeteringMode': {
+ 0: 'Unknown',
+ 1: 'Average',
+ 2: 'CenterWeightedAverage',
+ 3: 'Spot',
+ 4: 'MultiSpot',
+ 5: 'Pattern',
+ 6: 'Partial',
+ 255: 'Other'
+ },
+
+ 'LightSource': {
+ 1: 'Daylight',
+ 2: 'Fliorescent',
+ 3: 'Tungsten',
+ 4: 'Flash',
+ 9: 'Fine weather',
+ 10: 'Cloudy weather',
+ 11: 'Shade',
+ 12: 'Daylight fluorescent (D 5700 - 7100K)',
+ 13: 'Day white fluorescent (N 4600 -5400K)',
+ 14: 'Cool white fluorescent (W 3900 - 4500K)',
+ 15: 'White fluorescent (WW 3200 - 3700K)',
+ 17: 'Standard light A',
+ 18: 'Standard light B',
+ 19: 'Standard light C',
+ 20: 'D55',
+ 21: 'D65',
+ 22: 'D75',
+ 23: 'D50',
+ 24: 'ISO studio tungsten',
+ 255: 'Other'
+ },
+
+ 'Flash': {
+ 0x0000: 'Flash did not fire.',
+ 0x0001: 'Flash fired.',
+ 0x0005: 'Strobe return light not detected.',
+ 0x0007: 'Strobe return light detected.',
+ 0x0009: 'Flash fired, compulsory flash mode',
+ 0x000D: 'Flash fired, compulsory flash mode, return light not detected',
+ 0x000F: 'Flash fired, compulsory flash mode, return light detected',
+ 0x0010: 'Flash did not fire, compulsory flash mode',
+ 0x0018: 'Flash did not fire, auto mode',
+ 0x0019: 'Flash fired, auto mode',
+ 0x001D: 'Flash fired, auto mode, return light not detected',
+ 0x001F: 'Flash fired, auto mode, return light detected',
+ 0x0020: 'No flash function',
+ 0x0041: 'Flash fired, red-eye reduction mode',
+ 0x0045: 'Flash fired, red-eye reduction mode, return light not detected',
+ 0x0047: 'Flash fired, red-eye reduction mode, return light detected',
+ 0x0049: 'Flash fired, compulsory flash mode, red-eye reduction mode',
+ 0x004D: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected',
+ 0x004F: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected',
+ 0x0059: 'Flash fired, auto mode, red-eye reduction mode',
+ 0x005D: 'Flash fired, auto mode, return light not detected, red-eye reduction mode',
+ 0x005F: 'Flash fired, auto mode, return light detected, red-eye reduction mode'
+ },
+
+ 'ExposureMode': {
+ 0: 'Auto exposure',
+ 1: 'Manual exposure',
+ 2: 'Auto bracket'
+ },
+
+ 'WhiteBalance': {
+ 0: 'Auto white balance',
+ 1: 'Manual white balance'
+ },
+
+ 'SceneCaptureType': {
+ 0: 'Standard',
+ 1: 'Landscape',
+ 2: 'Portrait',
+ 3: 'Night scene'
+ },
+
+ 'Contrast': {
+ 0: 'Normal',
+ 1: 'Soft',
+ 2: 'Hard'
+ },
+
+ 'Saturation': {
+ 0: 'Normal',
+ 1: 'Low saturation',
+ 2: 'High saturation'
+ },
+
+ 'Sharpness': {
+ 0: 'Normal',
+ 1: 'Soft',
+ 2: 'Hard'
+ },
+
+ // GPS related
+ 'GPSLatitudeRef': {
+ N: 'North latitude',
+ S: 'South latitude'
+ },
+
+ 'GPSLongitudeRef': {
+ E: 'East longitude',
+ W: 'West longitude'
+ }
+ };
+
+ function extractTags(IFD_offset, tags2extract) {
+ var length = data.SHORT(IFD_offset), i, ii,
+ tag, type, count, tagOffset, offset, value, values = [], hash = {};
+
+ for (i = 0; i < length; i++) {
+ // Set binary reader pointer to beginning of the next tag
+ offset = tagOffset = IFD_offset + 12 * i + 2;
+
+ tag = tags2extract[data.SHORT(offset)];
+
+ if (tag === undef) {
+ continue; // Not the tag we requested
+ }
+
+ type = data.SHORT(offset+=2);
+ count = data.LONG(offset+=2);
+
+ offset += 4;
+ values = [];
+
+ switch (type) {
+ case 1: // BYTE
+ case 7: // UNDEFINED
+ if (count > 4) {
+ offset = data.LONG(offset) + offsets.tiffHeader;
+ }
+
+ for (ii = 0; ii < count; ii++) {
+ values[ii] = data.BYTE(offset + ii);
+ }
+
+ break;
+
+ case 2: // STRING
+ if (count > 4) {
+ offset = data.LONG(offset) + offsets.tiffHeader;
+ }
+
+ hash[tag] = data.STRING(offset, count - 1);
+
+ continue;
+
+ case 3: // SHORT
+ if (count > 2) {
+ offset = data.LONG(offset) + offsets.tiffHeader;
+ }
+
+ for (ii = 0; ii < count; ii++) {
+ values[ii] = data.SHORT(offset + ii*2);
+ }
+
+ break;
+
+ case 4: // LONG
+ if (count > 1) {
+ offset = data.LONG(offset) + offsets.tiffHeader;
+ }
+
+ for (ii = 0; ii < count; ii++) {
+ values[ii] = data.LONG(offset + ii*4);
+ }
+
+ break;
+
+ case 5: // RATIONAL
+ offset = data.LONG(offset) + offsets.tiffHeader;
+
+ for (ii = 0; ii < count; ii++) {
+ values[ii] = data.LONG(offset + ii*4) / data.LONG(offset + ii*4 + 4);
+ }
+
+ break;
+
+ case 9: // SLONG
+ offset = data.LONG(offset) + offsets.tiffHeader;
+
+ for (ii = 0; ii < count; ii++) {
+ values[ii] = data.SLONG(offset + ii*4);
+ }
+
+ break;
+
+ case 10: // SRATIONAL
+ offset = data.LONG(offset) + offsets.tiffHeader;
+
+ for (ii = 0; ii < count; ii++) {
+ values[ii] = data.SLONG(offset + ii*4) / data.SLONG(offset + ii*4 + 4);
+ }
+
+ break;
+
+ default:
+ continue;
+ }
+
+ value = (count == 1 ? values[0] : values);
+
+ if (tagDescs.hasOwnProperty(tag) && typeof value != 'object') {
+ hash[tag] = tagDescs[tag][value];
+ } else {
+ hash[tag] = value;
+ }
+ }
+
+ return hash;
+ }
+
+ function getIFDOffsets() {
+ var Tiff = undef, idx = offsets.tiffHeader;
+
+ // Set read order of multi-byte data
+ data.II(data.SHORT(idx) == 0x4949);
+
+ // Check if always present bytes are indeed present
+ if (data.SHORT(idx+=2) !== 0x002A) {
+ return false;
+ }
+
+ offsets['IFD0'] = offsets.tiffHeader + data.LONG(idx += 2);
+ Tiff = extractTags(offsets['IFD0'], tags.tiff);
+
+ offsets['exifIFD'] = ('ExifIFDPointer' in Tiff ? offsets.tiffHeader + Tiff.ExifIFDPointer : undef);
+ offsets['gpsIFD'] = ('GPSInfoIFDPointer' in Tiff ? offsets.tiffHeader + Tiff.GPSInfoIFDPointer : undef);
+
+ return true;
+ }
+
+ // At the moment only setting of simple (LONG) values, that do not require offset recalculation, is supported
+ function setTag(ifd, tag, value) {
+ var offset, length, tagOffset, valueOffset = 0;
+
+ // If tag name passed translate into hex key
+ if (typeof(tag) === 'string') {
+ var tmpTags = tags[ifd.toLowerCase()];
+ for (hex in tmpTags) {
+ if (tmpTags[hex] === tag) {
+ tag = hex;
+ break;
+ }
+ }
+ }
+ offset = offsets[ifd.toLowerCase() + 'IFD'];
+ length = data.SHORT(offset);
+
+ for (i = 0; i < length; i++) {
+ tagOffset = offset + 12 * i + 2;
+
+ if (data.SHORT(tagOffset) == tag) {
+ valueOffset = tagOffset + 8;
+ break;
+ }
+ }
+
+ if (!valueOffset) return false;
+
+
+ data.LONG(valueOffset, value);
+ return true;
+ }
+
+
+ // Public functions
+ return {
+ init: function(segment) {
+ // Reset internal data
+ offsets = {
+ tiffHeader: 10
+ };
+
+ if (segment === undef || !segment.length) {
+ return false;
+ }
+
+ data.init(segment);
+
+ // Check if that's APP1 and that it has EXIF
+ if (data.SHORT(0) === 0xFFE1 && data.STRING(4, 5).toUpperCase() === "EXIF\0") {
+ return getIFDOffsets();
+ }
+ return false;
+ },
+
+ EXIF: function() {
+ var Exif;
+
+ // Populate EXIF hash
+ Exif = extractTags(offsets.exifIFD, tags.exif);
+
+ // Fix formatting of some tags
+ if (Exif.ExifVersion && plupload.typeOf(Exif.ExifVersion) === 'array') {
+ for (var i = 0, exifVersion = ''; i < Exif.ExifVersion.length; i++) {
+ exifVersion += String.fromCharCode(Exif.ExifVersion[i]);
+ }
+ Exif.ExifVersion = exifVersion;
+ }
+
+ return Exif;
+ },
+
+ GPS: function() {
+ var GPS;
+
+ GPS = extractTags(offsets.gpsIFD, tags.gps);
+
+ // iOS devices (and probably some others) do not put in GPSVersionID tag (why?..)
+ if (GPS.GPSVersionID) {
+ GPS.GPSVersionID = GPS.GPSVersionID.join('.');
+ }
+
+ return GPS;
+ },
+
+ setExif: function(tag, value) {
+ // Right now only setting of width/height is possible
+ if (tag !== 'PixelXDimension' && tag !== 'PixelYDimension') return false;
+
+ return setTag('exif', tag, value);
+ },
+
+
+ getBinary: function() {
+ return data.SEGMENT();
+ }
+ };
+ };
+})(window, document, plupload);