1/* Blob.js
2 * A Blob implementation.
3 * 2014-07-24
4 *
5 * By Eli Grey, http://eligrey.com
6 * By Devin Samarin, https://github.com/dsamarin
7 * License: X11/MIT
8 *   See https://github.com/eligrey/Blob.js/blob/master/LICENSE.md
9 */
10
11/*global self, unescape */
12/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
13  plusplus: true */
14
15/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */
16
17(function (view) {
18	"use strict";
19
20	view.URL = view.URL || view.webkitURL;
21
22	if (view.Blob && view.URL) {
23		try {
24			new Blob;
25			return;
26		} catch (e) {}
27	}
28
29	// Internally we use a BlobBuilder implementation to base Blob off of
30	// in order to support older browsers that only have BlobBuilder
31	var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) {
32		var
33			  get_class = function(object) {
34				return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
35			}
36			, FakeBlobBuilder = function BlobBuilder() {
37				this.data = [];
38			}
39			, FakeBlob = function Blob(data, type, encoding) {
40				this.data = data;
41				this.size = data.length;
42				this.type = type;
43				this.encoding = encoding;
44			}
45			, FBB_proto = FakeBlobBuilder.prototype
46			, FB_proto = FakeBlob.prototype
47			, FileReaderSync = view.FileReaderSync
48			, FileException = function(type) {
49				this.code = this[this.name = type];
50			}
51			, file_ex_codes = (
52				  "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
53				+ "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
54			).split(" ")
55			, file_ex_code = file_ex_codes.length
56			, real_URL = view.URL || view.webkitURL || view
57			, real_create_object_URL = real_URL.createObjectURL
58			, real_revoke_object_URL = real_URL.revokeObjectURL
59			, URL = real_URL
60			, btoa = view.btoa
61			, atob = view.atob
62
63			, ArrayBuffer = view.ArrayBuffer
64			, Uint8Array = view.Uint8Array
65
66			, origin = /^[\w-]+:\/*\[?[\w\.:-]+\]?(?::[0-9]+)?/
67		;
68		FakeBlob.fake = FB_proto.fake = true;
69		while (file_ex_code--) {
70			FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
71		}
72		// Polyfill URL
73		if (!real_URL.createObjectURL) {
74			URL = view.URL = function(uri) {
75				var
76					  uri_info = document.createElementNS("http://www.w3.org/1999/xhtml", "a")
77					, uri_origin
78				;
79				uri_info.href = uri;
80				if (!("origin" in uri_info)) {
81					if (uri_info.protocol.toLowerCase() === "data:") {
82						uri_info.origin = null;
83					} else {
84						uri_origin = uri.match(origin);
85						uri_info.origin = uri_origin && uri_origin[1];
86					}
87				}
88				return uri_info;
89			};
90		}
91		URL.createObjectURL = function(blob) {
92			var
93				  type = blob.type
94				, data_URI_header
95			;
96			if (type === null) {
97				type = "application/octet-stream";
98			}
99			if (blob instanceof FakeBlob) {
100				data_URI_header = "data:" + type;
101				if (blob.encoding === "base64") {
102					return data_URI_header + ";base64," + blob.data;
103				} else if (blob.encoding === "URI") {
104					return data_URI_header + "," + decodeURIComponent(blob.data);
105				} if (btoa) {
106					return data_URI_header + ";base64," + btoa(blob.data);
107				} else {
108					return data_URI_header + "," + encodeURIComponent(blob.data);
109				}
110			} else if (real_create_object_URL) {
111				return real_create_object_URL.call(real_URL, blob);
112			}
113		};
114		URL.revokeObjectURL = function(object_URL) {
115			if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
116				real_revoke_object_URL.call(real_URL, object_URL);
117			}
118		};
119		FBB_proto.append = function(data/*, endings*/) {
120			var bb = this.data;
121			// decode data to a binary string
122			if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
123				var
124					  str = ""
125					, buf = new Uint8Array(data)
126					, i = 0
127					, buf_len = buf.length
128				;
129				for (; i < buf_len; i++) {
130					str += String.fromCharCode(buf[i]);
131				}
132				bb.push(str);
133			} else if (get_class(data) === "Blob" || get_class(data) === "File") {
134				if (FileReaderSync) {
135					var fr = new FileReaderSync;
136					bb.push(fr.readAsBinaryString(data));
137				} else {
138					// async FileReader won't work as BlobBuilder is sync
139					throw new FileException("NOT_READABLE_ERR");
140				}
141			} else if (data instanceof FakeBlob) {
142				if (data.encoding === "base64" && atob) {
143					bb.push(atob(data.data));
144				} else if (data.encoding === "URI") {
145					bb.push(decodeURIComponent(data.data));
146				} else if (data.encoding === "raw") {
147					bb.push(data.data);
148				}
149			} else {
150				if (typeof data !== "string") {
151					data += ""; // convert unsupported types to strings
152				}
153				// decode UTF-16 to binary string
154				bb.push(unescape(encodeURIComponent(data)));
155			}
156		};
157		FBB_proto.getBlob = function(type) {
158			if (!arguments.length) {
159				type = null;
160			}
161			return new FakeBlob(this.data.join(""), type, "raw");
162		};
163		FBB_proto.toString = function() {
164			return "[object BlobBuilder]";
165		};
166		FB_proto.slice = function(start, end, type) {
167			var args = arguments.length;
168			if (args < 3) {
169				type = null;
170			}
171			return new FakeBlob(
172				  this.data.slice(start, args > 1 ? end : this.data.length)
173				, type
174				, this.encoding
175			);
176		};
177		FB_proto.toString = function() {
178			return "[object Blob]";
179		};
180		FB_proto.close = function() {
181			this.size = 0;
182			delete this.data;
183		};
184		return FakeBlobBuilder;
185	}(view));
186
187	view.Blob = function(blobParts, options) {
188		var type = options ? (options.type || "") : "";
189		var builder = new BlobBuilder();
190		if (blobParts) {
191			for (var i = 0, len = blobParts.length; i < len; i++) {
192				if (Uint8Array && blobParts[i] instanceof Uint8Array) {
193					builder.append(blobParts[i].buffer);
194				}
195				else {
196					builder.append(blobParts[i]);
197				}
198			}
199		}
200		var blob = builder.getBlob(type);
201		if (!blob.slice && blob.webkitSlice) {
202			blob.slice = blob.webkitSlice;
203		}
204		return blob;
205	};
206
207	var getPrototypeOf = Object.getPrototypeOf || function(object) {
208		return object.__proto__;
209	};
210	view.Blob.prototype = getPrototypeOf(new view.Blob());
211}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));